This is an automatic generated email to let you know that the following patch 
were queued at the 
http://git.linuxtv.org/cgit.cgi/v4l-utils.git tree:

Subject: edid-decode: support parsing EDID-Like Data
Author:  Dmitry Baryshkov <dmitry.barysh...@linaro.org>
Date:    Fri Jan 24 19:06:14 2025 +0200

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.barysh...@linaro.org>
Signed-off-by: Hans Verkuil <hverk...@xs4all.nl>

 utils/edid-decode/edid-decode.1.in     |  25 ++++
 utils/edid-decode/edid-decode.cpp      | 237 ++++++++++++++++++++++++++++++++-
 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 ++++++++++++++
 7 files changed, 367 insertions(+), 5 deletions(-)

---

http://git.linuxtv.org/cgit.cgi/v4l-utils.git/commit/?id=536674bf0bba5e6c185b0906eef1e87cb76feec8
diff --git a/utils/edid-decode/edid-decode.1.in 
b/utils/edid-decode/edid-decode.1.in
index 04edf5ff3fed..1e4cd6ae1418 100644
--- a/utils/edid-decode/edid-decode.1.in
+++ b/utils/edid-decode/edid-decode.1.in
@@ -161,6 +161,8 @@ CVT 1.2: VESA Coordinated Video Timings (CVT) Standard, 
Version 1.2
 CVT 1.2: VESA CVT v1.2 Errata E2
 .TP
 GTF 1.1: VESA Generalized Timing Formula Standard, Version: 1.1
+.TP
+HDA 1.0a: High Definition Audio Specification, Version 1.0a
 .RE
 
 .SH OPTIONS
@@ -215,6 +217,29 @@ HDMI Specification. Otherwise it is assumed to be a 
regular CTA-861 InfoFrame
 without a checksum.
 
 Note: this is still work-in-progress, specifically for the AVI and HDMI 
InfoFrames.
+.TP
+\fB\-E\fR, \fB\-\-eld\fR \fI<file>\fR
+Parse the given EDID-Like Data (ELD) file. This option can be used multiple
+times to parse multiple ELD files. Read data from stdin if '-' was passed as a
+filename.  If the EDID of the display to which these ELD files are generated is
+also given, then the conformity checks will take that EDID into account.
+
+On Linux systems ELD can be extracted via the amixer command (copy all hex 
after the 'values='):
+  $ amixer -c 0 controls | grep ELD
+  numid=6,iface=PCM,name='ELD',device=3
+  numid=12,iface=PCM,name='ELD',device=7
+  numid=18,iface=PCM,name='ELD',device=8
+  numid=24,iface=PCM,name='ELD',device=9
+  $ amixer -c 0 cget iface=PCM,name=ELD,device=3
+  numid=6,iface=PCM,name='ELD',device=3
+    ; type=BYTES,access=r--v----,values=95
+    : 
values=0x10,0x00,0x08,0x00,0x6d,0x10,0x00,0x01,0x00,0x02,0x00,0x00,0x00,0x00,
+    
0x00,0x00,0x30,0xae,0xf1,0x61,0x4c,0x45,0x4e,0x20,0x54,0x33,0x32,0x68,0x2d,0x32,
+    
0x30,0x0a,0x20,0x09,0x7f,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+    
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00
+
 .TP
 \fB\-\-diagonal\fR \fI<inches>\fR
 Specify the diagonal of the display in inches. This will enable additional 
checks
diff --git a/utils/edid-decode/edid-decode.cpp 
b/utils/edid-decode/edid-decode.cpp
index 595eb28777da..25d872a3958f 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"
@@ -225,6 +228,8 @@ static void usage(void)
               "  --list-rid-timings <rid> List all timings for RID <rid> or 
all known RIDs if <rid> is 0.\n"
               "  -I, --infoframe <file> Parse the InfoFrame from <file> that 
was sent to this display.\n"
               "                        This option can be specified multiple 
times for different InfoFrame files.\n"
+              "  -E, --eld <file>      Parse the EDID-Like Data, ELD from 
<file> (or stdin if '-' was specified).\n"
+              "                        This option can be specified multiple 
times for different ELD files.\n"
               "  -h, --help            Display this help message.\n");
 }
 #endif
@@ -1600,6 +1605,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 +1732,212 @@ 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) || strchr(ignore_chars, *s))
+                       continue;
+
+               if (*s == '0' && tolower(s[1]) == 'x') {
+                       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);
+
+       unsigned i;
+
+       /* Is the EDID provided in hex? */
+       for (i = 0; i < 32 && (isspace(data[i]) || strchr(ignore_chars, 
data[i]) ||
+                              tolower(data[i]) == 'x' || isxdigit(data[i])); 
i++);
+
+       if (i == 32)
+               return extract_eld_hex(data);
+
+       // 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 (!strcmp(from_file, "-")) {
+               from_file = "stdin";
+               fd = 0;
+       } else 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 +2584,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 +2729,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 +2791,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 +2854,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 0d71d544145e..e64143deb79a 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 807d4e873c05..d1ed5179413a 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 a2f0e7408ef8..0d7f7c212f65 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 06bc07d30b0f..ce47be713527 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 000000000000..a569a278ef4f
--- /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.barysh...@linaro.org>
+ */
+
+#include <stdio.h>
+
+#include "edid-decode.h"
+
+void edid_state::parse_eld_baseline(const unsigned char *x, unsigned size)
+{
+       unsigned mnl, sad_count, data;
+       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);
+       }
+}

Reply via email to