/* * Copyright © 2019 Intel Corporation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice (including the next * paragraph) shall be included in all copies or substantial portions of the * Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS * IN THE SOFTWARE. * * Authors: Simon Ser */ #include "config.h" #include #include #include #include #include #include #include "igt_core.h" #include "igt_eld.h" #define ELD_PREFIX "eld#" #define ELD_DELIM " \t" /** * EDID-Like Data (ELD) is metadata parsed and exposed by ALSA for HDMI and * DisplayPort connectors supporting audio. This includes the monitor name and * the supported audio parameters (formats, sampling rates, sample sizes and so * on). * * Audio parameters come from Short Audio Descriptors (SAD) blocks in the * EDID. Enumerations from igt_edid are used since they are the same. */ static enum cea_sad_format parse_sad_coding_type(const char *value) { if (strcmp(value, "LPCM") == 0) return CEA_SAD_FORMAT_PCM; else return 0; } static enum cea_sad_sampling_rate parse_sad_rate(const char *value) { switch (atoi(value)) { case 32000: return CEA_SAD_SAMPLING_RATE_32KHZ; case 44100: return CEA_SAD_SAMPLING_RATE_44KHZ; case 48000: return CEA_SAD_SAMPLING_RATE_48KHZ; case 88000: return CEA_SAD_SAMPLING_RATE_88KHZ; case 96000: return CEA_SAD_SAMPLING_RATE_96KHZ; case 176000: return CEA_SAD_SAMPLING_RATE_176KHZ; case 192000: return CEA_SAD_SAMPLING_RATE_192KHZ; default: return 0; } } static enum cea_sad_pcm_sample_size parse_sad_bit(const char *value) { switch (atoi(value)) { case 16: return CEA_SAD_SAMPLE_SIZE_16; case 20: return CEA_SAD_SAMPLE_SIZE_20; case 24: return CEA_SAD_SAMPLE_SIZE_24; default: return 0; } } static void parse_sad_field(struct eld_sad *sad, const char *key, char *value) { char *tok; /* Some fields are prefixed with the raw hex value, strip it */ if (value[0] == '[') { value = strchr(value, ' '); igt_assert(value != NULL); value++; /* skip the space */ } /* Single-value fields */ if (strcmp(key, "coding_type") == 0) sad->coding_type = parse_sad_coding_type(value); else if (strcmp(key, "channels") == 0) sad->channels = atoi(value); /* Multiple-value fields */ tok = strtok(value, " "); while (tok) { if (strcmp(key, "rates") == 0) sad->rates |= parse_sad_rate(tok); else if (strcmp(key, "bits") == 0) sad->bits |= parse_sad_bit(tok); tok = strtok(NULL, " "); } } /** eld_parse_entry: parse an ELD entry * * Here is an example of an ELD entry: * * $ cat /proc/asound/card0/eld#0.2 * monitor_present 1 * eld_valid 1 * monitor_name U2879G6 * connection_type DisplayPort * eld_version [0x2] CEA-861D or below * edid_version [0x3] CEA-861-B, C or D * manufacture_id 0xe305 * product_id 0x2879 * port_id 0x800 * support_hdcp 0 * support_ai 0 * audio_sync_delay 0 * speakers [0x1] FL/FR * sad_count 1 * sad0_coding_type [0x1] LPCM * sad0_channels 2 * sad0_rates [0xe0] 32000 44100 48000 * sad0_bits [0xe0000] 16 20 24 * * Each entry contains one or more SAD blocks. Their contents is exposed in * sadN_* fields. */ static bool eld_parse_entry(const char *path, struct eld_entry *eld) { FILE *f; char buf[1024]; char *key, *value, *sad_key; size_t len; bool monitor_present = false; int sad_index; memset(eld, 0, sizeof(*eld)); f = fopen(path, "r"); if (!f) { igt_debug("Failed to open ELD file: %s\n", path); return false; } while ((fgets(buf, sizeof(buf), f)) != NULL) { len = strlen(buf); if (buf[len - 1] == '\n') buf[len - 1] = '\0'; key = strtok(buf, ELD_DELIM); value = strtok(NULL, ""); /* Skip whitespace at the beginning */ value += strspn(value, ELD_DELIM); if (strcmp(key, "monitor_present") == 0) monitor_present = strcmp(value, "1") == 0; else if (strcmp(key, "eld_valid") == 0) eld->valid = strcmp(value, "1") == 0; else if (strcmp(key, "monitor_name") == 0) snprintf(eld->monitor_name, sizeof(eld->monitor_name), "%s", value); else if (strcmp(key, "sad_count") == 0) eld->sads_len = atoi(value); else if (sscanf(key, "sad%d_%ms", &sad_index, &sad_key) == 2) { igt_assert(sad_index < ELD_SADS_CAP); igt_assert(sad_index < eld->sads_len); parse_sad_field(&eld->sads[sad_index], sad_key, value); free(sad_key); } } if (ferror(f) != 0) { igt_debug("Failed to read ELD file %s: %d\n", path, ferror(f)); return false; } fclose(f); if (!monitor_present) igt_debug("Monitor not present in ELD: %s\n", path); return monitor_present; } /** eld_get_igt: retrieve the ALSA ELD entry matching the IGT EDID */ bool eld_get_igt(struct eld_entry *eld) { DIR *dir; struct dirent *dirent; int i, n_elds; char card[64]; char path[PATH_MAX]; n_elds = 0; for (i = 0; i < 8; i++) { snprintf(card, sizeof(card), "/proc/asound/card%d", i); dir = opendir(card); if (!dir) continue; while ((dirent = readdir(dir))) { if (strncmp(dirent->d_name, ELD_PREFIX, strlen(ELD_PREFIX)) != 0) continue; n_elds++; snprintf(path, sizeof(path), "%s/%s", card, dirent->d_name); if (!eld_parse_entry(path, eld)) { continue; } if (!eld->valid) { igt_debug("Skipping invalid ELD: %s\n", path); continue; } if (strcmp(eld->monitor_name, "IGT") != 0) { igt_debug("Skipping non-IGT ELD: %s " "(monitor name: %s)\n", path, eld->monitor_name); continue; } closedir(dir); return true; } closedir(dir); } if (n_elds == 0) igt_debug("Found zero ELDs\n"); return false; } /** eld_has_igt: check whether ALSA has detected the audio-capable IGT EDID by * parsing ELD entries */ bool eld_has_igt(void) { struct eld_entry eld; return eld_get_igt(&eld); } /** eld_is_supported: check whether the ALSA procfs is enabled, audio cards * are found and ELDs are supported */ bool eld_is_supported(void) { glob_t glob_buf = {0}; int ret; bool has_elds; ret = glob("/proc/asound/card*/" ELD_PREFIX "*", GLOB_NOSORT, NULL, &glob_buf); if (ret == GLOB_NOMATCH) return false; igt_assert_f(ret == 0, "glob failed: %d\n", ret); has_elds = glob_buf.gl_pathc > 0; globfree(&glob_buf); return has_elds; } #define ELD_MAX_SIZE 256 #define MONITOR_NAME_OFFSET 20 #define MONITOR_NAME_MAX_SIZE 16 /** eld_is_igt: check whether the ELD blob comes from an IGT EDID */ bool eld_is_igt(const char *eld, size_t eld_size) { char name[MONITOR_NAME_MAX_SIZE + 1]; int name_size; igt_assert_f(eld_size >= MONITOR_NAME_OFFSET && eld_size <= ELD_MAX_SIZE, "Invalid ELD size: %zu\n", eld_size); name_size = eld[4] & 0x1F; igt_assert_f(name_size <= MONITOR_NAME_MAX_SIZE && MONITOR_NAME_OFFSET + name_size <= eld_size, "Invalid monitor name size in ELD: %d\n", name_size); if (name_size == 0) { igt_debug("ELD doesn't contain a monitor name\n"); return false; } memcpy(name, &eld[MONITOR_NAME_OFFSET], name_size); name[name_size] = '\0'; igt_debug("Checking ELD with monitor name: %s\n", name); return strcmp(name, "IGT") == 0; }