diff mbox series

edid-decode: support parsing EDID-Like Data

Message ID 20250106-eld-support-v1-1-5a6ae0d25cd2@linaro.org (mailing list archive)
State New
Headers show
Series edid-decode: support parsing EDID-Like Data | expand

Commit Message

Dmitry Baryshkov Jan. 6, 2025, 10:08 a.m. UTC
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,

Comments

Hans Verkuil Jan. 6, 2025, 10:19 a.m. UTC | #1
Hi Dmitry,

The patch looks good, but you also need to update edid-decode.1.in with the new option.

Also add an entry for the High Definition Audio spec to the STANDARD section
in that manpage.

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

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@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(-)
> 
> 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@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);
> +	}
> +}
> 
> ---
> base-commit: f6270b7ce87eed140234db1119890cb90b781ed7
> change-id: 20250106-eld-support-4a243e37981c
> 
> Best regards,
Dmitry Baryshkov Jan. 6, 2025, 2:32 p.m. UTC | #2
Hi Hans,

On Mon, 6 Jan 2025 at 12:19, Hans Verkuil <hverkuil@xs4all.nl> 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@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(-)
> >
> > 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@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);
> > +     }
> > +}
> >
> > ---
> > base-commit: f6270b7ce87eed140234db1119890cb90b781ed7
> > change-id: 20250106-eld-support-4a243e37981c
> >
> > Best regards,
>


--
With best wishes
Dmitry
diff mbox series

Patch

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@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);
+	}
+}