Hi Hans, On Mon, 6 Jan 2025 at 12:19, Hans Verkuil <hverkuil@xxxxxxxxx> wrote: > > Hi Dmitry, > > The patch looks good, but you also need to update edid-decode.1.in with the new option. Ack > > Also add an entry for the High Definition Audio spec to the STANDARD section > in that manpage. Ack > > Is this a freely available spec? If so, do you have a link? I found this: > > https://www.intel.com/content/dam/www/public/us/en/documents/product-specifications/high-definition-audio-specification.pdf I think that's the latest version. > But it's from 2010, so I don't know if there is a newer version, or if there are errata/updates on top. > > If the ELD data is exported to userspace, how is that done? Somewhere in /sys? > It is nice if that is also documented in the manpage. > > Regards, > > Hans > > On 06/01/2025 11:08, Dmitry Baryshkov wrote: > > High Definition Audio spec defines a special data block, ELD, used to > > provide audio-related information for the connected monitors. HDMI Codec > > in Linux reuses ELD to provide data to userspace. Add support for > > parsing ELD blobs. > > > > Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> > > --- > > utils/edid-decode/edid-decode.cpp | 218 ++++++++++++++++++++++++++++++++- > > utils/edid-decode/edid-decode.h | 6 + > > utils/edid-decode/meson.build | 1 + > > utils/edid-decode/parse-base-block.cpp | 2 +- > > utils/edid-decode/parse-cta-block.cpp | 4 +- > > utils/edid-decode/parse-eld.cpp | 97 +++++++++++++++ > > 6 files changed, 323 insertions(+), 5 deletions(-) > > > > diff --git a/utils/edid-decode/edid-decode.cpp b/utils/edid-decode/edid-decode.cpp > > index 595eb28777da2f1589b0f9083436156aa12a6036..e1cee92650e1b447bff5c58e5700cfe9cec2452e 100644 > > --- a/utils/edid-decode/edid-decode.cpp > > +++ b/utils/edid-decode/edid-decode.cpp > > @@ -43,6 +43,7 @@ enum Option { > > OptI2CAdapter = 'a', > > OptCheck = 'c', > > OptCheckInline = 'C', > > + OptEld = 'E', > > OptFBModeTimings = 'F', > > OptHelp = 'h', > > OptOnlyHexDump = 'H', > > @@ -129,6 +130,7 @@ static struct option long_options[] = { > > { "list-rid-timings", required_argument, 0, OptListRIDTimings }, > > { "list-rids", no_argument, 0, OptListRIDs }, > > { "infoframe", required_argument, 0, OptInfoFrame }, > > + { "eld", required_argument, 0, OptEld }, > > { 0, 0, 0, 0 } > > }; > > > > @@ -136,7 +138,8 @@ static void usage(void) > > { > > printf("Usage: edid-decode <options> [in [out]]\n" > > " [in] EDID file to parse. Read from standard input if none given\n" > > - " and --infoframe was not used, or if the input filename is '-'.\n" > > + " and neither --infoframe nor --eld was not used, or if the\n" > > + " input filename is '-'.\n" > > " [out] Output the read EDID to this file. Write to standard output\n" > > " if the output filename is '-'.\n" > > "\nOptions:\n" > > @@ -1600,6 +1603,9 @@ int edid_state::parse_edid() > > static unsigned char infoframe[32]; > > static unsigned if_size; > > > > +static unsigned char eld[128]; > > +static unsigned eld_size; > > + > > static bool if_add_byte(const char *s) > > { > > char buf[3]; > > @@ -1724,6 +1730,195 @@ static void show_if_msgs(bool is_warn) > > s_msgs[0][is_warn].c_str()); > > } > > > > +static bool eld_add_byte(const char *s) > > +{ > > + char buf[3]; > > + > > + if (eld_size == sizeof(eld)) > > + return false; > > + buf[0] = s[0]; > > + buf[1] = s[1]; > > + buf[2] = 0; > > + eld[eld_size++] = strtoul(buf, NULL, 16); > > + return true; > > +} > > + > > +static bool extract_eld_hex(const char *s) > > +{ > > + for (; *s; s++) { > > + if (isspace(*s)) > > + continue; > > + > > + /* Read one or two hex digits from the log */ > > + if (!isxdigit(s[0])) > > + break; > > + > > + if (!isxdigit(s[1])) { > > + odd_hex_digits = true; > > + return false; > > + } > > + if (!eld_add_byte(s)) > > + return false; > > + s++; > > + } > > + return eld_size; > > +} > > + > > +static bool extract_eld(int fd) > > +{ > > + std::vector<char> eld_data; > > + char buf[128]; > > + > > + for (;;) { > > + ssize_t i = read(fd, buf, sizeof(buf)); > > + > > + if (i < 0) > > + return false; > > + if (i == 0) > > + break; > > + eld_data.insert(eld_data.end(), buf, buf + i); > > + } > > + > > + if (eld_data.empty()) { > > + eld_size = 0; > > + return false; > > + } > > + // Ensure it is safely terminated by a 0 char > > + eld_data.push_back('\0'); > > + > > + const char *data = &eld_data[0]; > > + const char *start; > > + > > + /* Look for edid-decode output */ > > + start = strstr(data, "edid-decode ELD (hex):"); > > + if (start) > > + return extract_eld_hex(strchr(start, ':') + 1); > > + > > + // Drop the extra '\0' byte since we now assume binary data > > + eld_data.pop_back(); > > + > > + eld_size = eld_data.size(); > > + > > + /* Assume binary */ > > + if (eld_size > sizeof(eld)) { > > + fprintf(stderr, "Binary ELD length %u is greater than %zu.\n", > > + eld_size, sizeof(eld)); > > + return false; > > + } > > + memcpy(eld, data, eld_size); > > + return true; > > +} > > + > > +static int eld_from_file(const char *from_file) > > +{ > > +#ifdef O_BINARY > > + // Windows compatibility > > + int flags = O_RDONLY | O_BINARY; > > +#else > > + int flags = O_RDONLY; > > +#endif > > + int fd; > > + > > + memset(eld, 0, sizeof(eld)); > > + eld_size = 0; > > + > > + if ((fd = open(from_file, flags)) == -1) { > > + perror(from_file); > > + return -1; > > + } > > + > > + odd_hex_digits = false; > > + if (!extract_eld(fd)) { > > + if (!eld_size) { > > + fprintf(stderr, "ELD of '%s' was empty.\n", from_file); > > + return -1; > > + } > > + fprintf(stderr, "ELD extraction of '%s' failed: ", from_file); > > + if (odd_hex_digits) > > + fprintf(stderr, "odd number of hexadecimal digits.\n"); > > + else > > + fprintf(stderr, "unknown format.\n"); > > + return -1; > > + } > > + close(fd); > > + > > + return 0; > > +} > > + > > +static void show_eld_msgs(bool is_warn) > > +{ > > + printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures"); > > + if (s_msgs[0][is_warn].empty()) > > + return; > > + printf("ELD:\n%s", > > + s_msgs[0][is_warn].c_str()); > > +} > > + > > +int edid_state::parse_eld(const std::string &fname) > > +{ > > + int ret = eld_from_file(fname.c_str()); > > + unsigned int min_size = 4; > > + unsigned baseline_size; > > + unsigned char ver; > > + > > + if (ret) > > + return ret; > > + > > + if (!options[OptSkipHexDump]) { > > + printf("edid-decode ELD (hex):\n\n"); > > + hex_block("", eld, eld_size, false); > > + if (options[OptOnlyHexDump]) > > + return 0; > > + printf("\n----------------\n\n"); > > + } > > + > > + if (eld_size < min_size) { > > + fail("ELD is too small to parse.\n"); > > + return -1; > > + } > > + > > + ver = eld[0] >> 3; > > + switch (ver) { > > + case 1: > > + warn("Obsolete Baseline ELD version (%d)\n", ver); > > + break; > > + case 2: > > + printf("Baseline ELD version: 861.D or below\n"); > > + break; > > + default: > > + warn("Unsupported ELD version (%d)\n", ver); > > + break; > > + } > > + > > + baseline_size = eld[2] * 4; > > + if (baseline_size > 80) > > + warn("ELD too big\n"); > > + > > + parse_eld_baseline(&eld[4], baseline_size); > > + > > + if (!options[OptCheck] && !options[OptCheckInline]) > > + return 0; > > + > > + printf("\n----------------\n"); > > + > > + if (!options[OptSkipSHA] && strlen(STRING(SHA))) { > > + options[OptSkipSHA] = 1; > > + printf("\n"); > > + print_version(); > > + } > > + > > + if (options[OptCheck]) { > > + if (warnings) > > + show_eld_msgs(true); > > + if (failures) > > + show_eld_msgs(false); > > + } > > + > > + printf("\n%s conformity: %s\n", > > + state.data_block.empty() ? "ELD" : state.data_block.c_str(), > > + failures ? "FAIL" : "PASS"); > > + return failures ? -2 : 0; > > +} > > int edid_state::parse_if(const std::string &fname) > > { > > int ret = if_from_file(fname.c_str()); > > @@ -2370,6 +2565,7 @@ int main(int argc, char **argv) > > int adapter_fd = -1; > > double hdcp_ri_sleep = 0; > > std::vector<std::string> if_names; > > + std::vector<std::string> eld_names; > > unsigned test_rel_duration = 0; > > unsigned test_rel_msleep = 50; > > unsigned idx = 0; > > @@ -2514,6 +2710,9 @@ int main(int argc, char **argv) > > case OptInfoFrame: > > if_names.push_back(optarg); > > break; > > + case OptEld: > > + eld_names.push_back(optarg); > > + break; > > case ':': > > fprintf(stderr, "Option '%s' requires a value.\n", > > argv[optind]); > > @@ -2573,7 +2772,7 @@ int main(int argc, char **argv) > > ret = read_hdcp_ri(adapter_fd, hdcp_ri_sleep); > > if (options[OptI2CTestReliability]) > > ret = test_reliability(adapter_fd, test_rel_duration, test_rel_msleep); > > - } else if (options[OptInfoFrame] && !options[OptGTF]) { > > + } else if ((options[OptInfoFrame] || options[OptEld]) && !options[OptGTF]) { > > ret = 0; > > } else { > > ret = edid_from_file("-", stdout); > > @@ -2636,6 +2835,21 @@ int main(int argc, char **argv) > > if (r && !ret) > > ret = r; > > } > > + > > + for (const auto &n : eld_names) { > > + if (show_line) > > + printf("\n================\n\n"); > > + show_line = true; > > + > > + state.warnings = state.failures = 0; > > + for (unsigned i = 0; i < EDID_MAX_BLOCKS + 1; i++) { > > + s_msgs[i][0].clear(); > > + s_msgs[i][1].clear(); > > + } > > + int r = state.parse_eld(n); > > + if (r && !ret) > > + ret = r; > > + } > > return ret; > > } > > > > diff --git a/utils/edid-decode/edid-decode.h b/utils/edid-decode/edid-decode.h > > index 0d71d544145ee6e55d1e50270c3317bb69c0fcf4..e64143deb79a7e58aba87c41a0d582e7bb236a2f 100644 > > --- a/utils/edid-decode/edid-decode.h > > +++ b/utils/edid-decode/edid-decode.h > > @@ -423,6 +423,7 @@ struct edid_state { > > void check_base_block(const unsigned char *x); > > void list_dmts(); > > void list_established_timings(); > > + char *manufacturer_name(const unsigned char *x); > > > > void data_block_oui(std::string block_name, const unsigned char *x, unsigned length, unsigned *ouinum, > > bool ignorezeros = false, bool do_ascii = false, bool big_endian = false, > > @@ -449,6 +450,8 @@ struct edid_state { > > void cta_displayid_type_8(const unsigned char *x, unsigned length); > > void cta_displayid_type_10(const unsigned char *x, unsigned length); > > void cta_block(const unsigned char *x, std::vector<unsigned> &found_tags); > > + void cta_sadb(const unsigned char *x, unsigned length); > > + void cta_audio_block(const unsigned char *x, unsigned length); > > void preparse_cta_block(unsigned char *x); > > void parse_cta_block(const unsigned char *x); > > void cta_resolve_svr(timings_ext &t_ext); > > @@ -532,6 +535,9 @@ struct edid_state { > > void parse_if_mpeg_source(const unsigned char *x, unsigned size); > > void parse_if_ntsc_vbi(const unsigned char *x, unsigned size); > > void parse_if_drm(const unsigned char *x, unsigned size); > > + > > + int parse_eld(const std::string &fname); > > + void parse_eld_baseline(const unsigned char *x, unsigned size); > > }; > > > > static inline void add_str(std::string &s, const std::string &add) > > diff --git a/utils/edid-decode/meson.build b/utils/edid-decode/meson.build > > index c2ddd95837954b390adf3dbf66cc3e0b7c04087e..f7b10841b314c558685ed8ea19c0d87754bb9e4a 100644 > > --- a/utils/edid-decode/meson.build > > +++ b/utils/edid-decode/meson.build > > @@ -9,6 +9,7 @@ edid_decode_sources = [ > > 'parse-ls-ext-block.cpp', > > 'parse-vtb-ext-block.cpp', > > 'parse-if.cpp', > > + 'parse-eld.cpp', > > ] > > > > edid_decode_args = [] > > diff --git a/utils/edid-decode/parse-base-block.cpp b/utils/edid-decode/parse-base-block.cpp > > index a2f0e7408ef8c87fd218a2dfcf5e2ee3eb55e3f6..0d7f7c212f65d7a81e5e771387e4e679b1e2e2f5 100644 > > --- a/utils/edid-decode/parse-base-block.cpp > > +++ b/utils/edid-decode/parse-base-block.cpp > > @@ -14,7 +14,7 @@ > > > > #include "edid-decode.h" > > > > -static char *manufacturer_name(const unsigned char *x) > > +char *edid_state::manufacturer_name(const unsigned char *x) > > { > > static char name[4]; > > > > diff --git a/utils/edid-decode/parse-cta-block.cpp b/utils/edid-decode/parse-cta-block.cpp > > index 06bc07d30b0fb7a8ed73320e9cf91b9aca84cd73..ce47be7135272e8e81f3269f469e27c6c7b5e583 100644 > > --- a/utils/edid-decode/parse-cta-block.cpp > > +++ b/utils/edid-decode/parse-cta-block.cpp > > @@ -464,7 +464,7 @@ static std::string mpeg_h_3d_audio_level(unsigned char x) > > return std::string("Unknown MPEG-H 3D Audio Level (") + utohex(x) + ")"; > > } > > > > -static void cta_audio_block(const unsigned char *x, unsigned length) > > +void edid_state::cta_audio_block(const unsigned char *x, unsigned length) > > { > > unsigned i, format, ext_format; > > > > @@ -1824,7 +1824,7 @@ const char *cta_speaker_map[] = { > > NULL > > }; > > > > -static void cta_sadb(const unsigned char *x, unsigned length) > > +void edid_state::cta_sadb(const unsigned char *x, unsigned length) > > { > > unsigned sad_deprecated = 0x7f000; > > unsigned sad; > > diff --git a/utils/edid-decode/parse-eld.cpp b/utils/edid-decode/parse-eld.cpp > > new file mode 100644 > > index 0000000000000000000000000000000000000000..ba6275813ae56b6671af222b30f436d8998ba9ff > > --- /dev/null > > +++ b/utils/edid-decode/parse-eld.cpp > > @@ -0,0 +1,97 @@ > > +// SPDX-License-Identifier: MIT > > +/* > > + * Copyright 2024 Linaro Ltd. All rights reserved. > > + * > > + * Author: Dmitry Baryshkov <dmitry.baryshkov@xxxxxxxxxx> > > + */ > > + > > +#include <stdio.h> > > + > > +#include "edid-decode.h" > > + > > +void edid_state::parse_eld_baseline(const unsigned char *x, unsigned size) > > +{ > > + unsigned mnl, sad_count, data, i; > > + unsigned char dummy_sadb[3] = {}; > > + char *manufacturer; > > + > > + printf("Baseline ELD:\n"); > > + > > + if (size < 16) { > > + fail("Baseline ELD too short (%d)\n", size); > > + return; > > + } > > + > > + mnl = x[0] & 0x1f; > > + > > + data = x[0] >> 5; > > + switch (data) { > > + case 0: > > + printf(" no CEA EDID Timing Extension present\n"); > > + break; > > + case 1: > > + printf(" CEA EDID 861\n"); > > + break; > > + case 2: > > + printf(" CEA EDID 861.A\n"); > > + break; > > + case 3: > > + printf(" CEA EDID 861.B/C/D\n"); > > + break; > > + default: > > + warn("Unsupported CEA EDID version (%d)\n", data); > > + break; > > + } > > + > > + if (x[1] & 1) > > + printf(" HDCP Content Protection is supported\n"); > > + if (x[1] & 2) > > + printf(" ACP / ISRC / ISRC2 packets are handled\n"); > > + > > + data = (x[1] >> 2) & 3; > > + switch (data) { > > + case 0: > > + printf(" HDMI connection\n"); > > + break; > > + case 1: > > + printf(" DisplayPort connection\n"); > > + break; > > + default: > > + warn(" Unrecognized connection type (%d)\n", data); > > + } > > + > > + sad_count = x[1] >> 4; > > + > > + if (x[2]) > > + printf(" Audio latency: %d ms\n", x[2] * 2); > > + else > > + printf(" No Audio latency information\n"); > > + > > + printf(" Speaker Allocation:\n"); > > + dummy_sadb[0] = x[3]; > > + cta_sadb(dummy_sadb, sizeof(dummy_sadb)); > > + > > + printf(" Port ID:\n"); > > + hex_block(" ", x + 0x4, 8); > > + > > + manufacturer = manufacturer_name(x + 0x0c); > > + printf(" Manufacturer: %s\n", manufacturer); > > + printf(" Model: %u\n", (unsigned short)(x[0x0e] + (x[0x0f] << 8))); > > + > > + if (0x10 + mnl >= size) { > > + fail("Manufacturer name overflow (MNL = %d)\n", mnl); > > + return; > > + } > > + > > + printf(" Display Product Name: '%s'\n", extract_string(x + 0x10, mnl, true)); > > + > > + if (0x10 + mnl + (3 * sad_count) >= size) { > > + fail("Short Audio Descriptors overflow\n"); > > + return; > > + } > > + > > + if (sad_count != 0) { > > + printf(" Short Audio Descriptors:\n"); > > + cta_audio_block(x + 0x10 + mnl, 3 * sad_count); > > + } > > +} > > > > --- > > base-commit: f6270b7ce87eed140234db1119890cb90b781ed7 > > change-id: 20250106-eld-support-4a243e37981c > > > > Best regards, > -- With best wishes Dmitry