@@ -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;
}
@@ -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)
@@ -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 = []
@@ -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];
@@ -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;
new file mode 100644
@@ -0,0 +1,97 @@
+// SPDX-License-Identifier: MIT
+/*
+ * Copyright 2024 Linaro Ltd. All rights reserved.
+ *
+ * Author: Dmitry Baryshkov <dmitry.baryshkov@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, 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);
+ }
+}
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@linaro.org> --- 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(-) --- base-commit: f6270b7ce87eed140234db1119890cb90b781ed7 change-id: 20250106-eld-support-4a243e37981c Best regards,