// SPDX-License-Identifier: MIT
/*
 * Copyright 2006-2012 Red Hat, Inc.
 * Copyright 2018-2019 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
 *
 * Author: Adam Jackson <ajax@nwnk.net>
 * Maintainer: Hans Verkuil <hverkuil-cisco@xs4all.nl>
 */

#include <ctype.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#include "edid-decode.h"

static edid_state state;

static unsigned char edid[EDID_PAGE_SIZE * EDID_MAX_BLOCKS];

enum output_format {
	OUT_FMT_DEFAULT,
	OUT_FMT_HEX,
	OUT_FMT_RAW,
	OUT_FMT_CARRAY
};

/*
 * Options
 * Please keep in alphabetical order of the short option.
 * That makes it easier to see which options are still free.
 */
enum Option {
	OptCheck = 'c',
	OptCheckInline = 'C',
	OptExtract = 'e',
	OptHelp = 'h',
	OptOutputFormat = 'o',
	OptSkipHexDump = 's',
	OptSkipSHA = 128,
	OptLast = 256
};

static char options[OptLast];

static struct option long_options[] = {
	{ "help", no_argument, 0, OptHelp },
	{ "output-format", required_argument, 0, OptOutputFormat },
	{ "extract", no_argument, 0, OptExtract },
	{ "skip-hex-dump", no_argument, 0, OptSkipHexDump },
	{ "skip-sha", no_argument, 0, OptSkipSHA },
	{ "check-inline", no_argument, 0, OptCheckInline },
	{ "check", no_argument, 0, OptCheck },
	{ 0, 0, 0, 0 }
};

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"
	       "                        or if the 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"
	       "  -o, --output-format <fmt>\n"
	       "                        if [out] is specified, then write the EDID in this format\n"
	       "                        <fmt> is one of:\n"
	       "                        hex:    hex numbers in ascii text (default for stdout)\n"
	       "                        raw:    binary data (default unless writing to stdout)\n"
	       "                        carray: c-program struct\n"
	       "  -c, --check           check if the EDID conforms to the standards, failures and\n"
	       "                        warnings are reported at the end.\n"
	       "  -C, --check-inline    check if the EDID conforms to the standards, failures and\n"
	       "                        warnings are reported inline.\n"
	       "  -s, --skip-hex-dump   skip the initial hex dump of the EDID\n"
	       "  --skip-sha            skip the SHA report\n"
	       "  -e, --extract         extract the contents of the first block in hex values\n"
	       "  -h, --help            display this help message\n");
}

static std::string s_msgs[EDID_MAX_BLOCKS + 1][2];

void msg(bool is_warn, const char *fmt, ...)
{
	char buf[256] = "";
	va_list ap;

	va_start(ap, fmt);
	vsprintf(buf, fmt, ap);
	va_end(ap);

	if (is_warn)
		state.warnings++;
	else
		state.failures++;
	if (state.data_block.empty())
		s_msgs[state.block_nr][is_warn] += std::string("  ") + buf;
	else
		s_msgs[state.block_nr][is_warn] += "  " + state.data_block + ": " + buf;

	if (options[OptCheckInline])
		printf("%s: %s", is_warn ? "WARN" : "FAIL", buf);
}

static void show_msgs(bool is_warn)
{
	printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures");
	for (unsigned i = 0; i < state.num_blocks; i++) {
		if (s_msgs[i][is_warn].empty())
			continue;
		printf("Block %u (%s):\n%s",
		       i, block_name(edid[i * EDID_PAGE_SIZE]).c_str(),
		       s_msgs[i][is_warn].c_str());
	}
	if (s_msgs[EDID_MAX_BLOCKS][is_warn].empty())
		return;
	printf("All Blocks:\n%s",
	       s_msgs[EDID_MAX_BLOCKS][is_warn].c_str());
}


void do_checksum(const char *prefix, const unsigned char *x, size_t len)
{
	unsigned char check = x[len - 1];
	unsigned char sum = 0;
	unsigned i;

	printf("%sChecksum: 0x%hhx", prefix, check);

	for (i = 0; i < len-1; i++)
		sum += x[i];

	if ((unsigned char)(check + sum) != 0) {
		printf(" (should be 0x%02x)\n", -sum & 0xff);
		fail("Invalid checksum 0x%02x (should be 0x%02x)\n",
		     check, -sum & 0xff);
		return;
	}
	printf("\n");
}

static unsigned gcd(unsigned a, unsigned b)
{
	while (b) {
		unsigned t = b;

		b = a % b;
		a = t;
	}
	return a;
}

void calc_ratio(struct timings *t)
{
	unsigned d = gcd(t->hact, t->vact);

	t->hratio = t->hact / d;
	t->vratio = t->vact / d;
}

void edid_state::print_timings(const char *prefix, const struct timings *t,
			       const char *suffix)
{
	if (!t) {
		// Should not happen
		fail("Unknown short timings\n");
		return;
	}

	unsigned htotal = t->hact + t->hfp + t->hsync + t->hbp;
	double hor_freq_khz = (double)t->pixclk_khz / htotal;

	double vtotal = t->vact + t->vfp + t->vsync + t->vbp;
	if (t->even_vtotal)
		vtotal = t->vact / 2.0 + t->vfp + t->vsync + t->vbp;
	else if (t->interlaced)
		vtotal = t->vact / 2.0 + t->vfp + t->vsync + t->vbp + 0.5;
	double refresh = (double)t->pixclk_khz * 1000.0 / (htotal * vtotal);

	min_vert_freq_hz = min(min_vert_freq_hz, refresh);
	max_vert_freq_hz = max(max_vert_freq_hz, refresh);
	min_hor_freq_hz = min(min_hor_freq_hz, hor_freq_khz * 1000);
	max_hor_freq_hz = max(max_hor_freq_hz, hor_freq_khz * 1000);
	max_pixclk_khz = max(max_pixclk_khz, t->pixclk_khz);

	std::string s(suffix);
	if (t->rb) {
		if (s.empty())
			s = "RB";
		else
			s += ", RB";
		if (t->rb == 2)
			s += "v2";
	}
	if (!s.empty())
		s = " (" + s + ")";

	char buf[10];
	sprintf(buf, "%u%s", t->vact, t->interlaced ? "i" : "");
	printf("%s%5ux%-5s %7.3f Hz %3u:%-3u %7.3f kHz %7.3f MHz%s\n",
	       prefix,
	       t->hact, buf,
	       refresh,
	       t->hratio, t->vratio,
	       hor_freq_khz,
	       t->pixclk_khz / 1000.0,
	       s.c_str());
}

bool edid_state::print_detailed_timings(const char *prefix, const struct timings &t, const char *flags)
{
	unsigned vact = t.vact;
	unsigned hbl = t.hfp + t.hsync + t.hbp;
	unsigned vbl = t.vfp + t.vsync + t.vbp;

	if (t.interlaced)
		vact /= 2;

	double vtotal = vact + vbl;

	bool ok = true;

	if (!t.hact || !hbl || !t.hfp || !t.hsync || !vact || !vbl || !t.vfp || !t.vsync) {
		fail("0 values in the detailed timings:\n"
		     "    Horizontal Active/Blanking %u/%u\n"
		     "    Horizontal Frontporch/Sync Width %u/%u\n"
		     "    Vertical Active/Blanking %u/%u\n"
		     "    Vertical Frontporch/Sync Width %u/%u\n",
		     t.hact, hbl, t.hfp, t.hsync, vact, vbl, t.vfp, t.vsync);
		ok = false;
	}

	if (t.even_vtotal)
		vtotal = vact + t.vfp + t.vsync + t.vbp;
	else if (t.interlaced)
		vtotal = vact + t.vfp + t.vsync + t.vbp + 0.5;

	double refresh = (double)t.pixclk_khz * 1000.0 / ((t.hact + hbl) * vtotal);

	printf("%sDetailed mode: Clock %.3f MHz", prefix, t.pixclk_khz / 1000.0);
	if (flags && *flags)
		printf(", %s", flags);
	if (t.hsize_mm || t.vsize_mm)
		printf(", %u mm x %u mm", t.hsize_mm, t.vsize_mm);
	printf("\n");
	printf("%s               %4u %4u %4u %4u (%3u %3u %3d)%s\n"
	       "%s               %4u %4u %4u %4u (%3u %3u %3d)%s\n"
	       "%s               %chsync%s\n"
	       "%s               VertFreq: %.3f%s Hz, HorFreq: %.3f kHz\n",
	       prefix,
	       t.hact, t.hact + t.hfp, t.hact + t.hfp + t.hsync, t.hact + hbl, t.hfp, t.hsync, t.hbp,
	       t.hborder ? (std::string(" hborder ") + std::to_string(t.hborder)).c_str() : "",
	       prefix,
	       vact, vact + t.vfp, vact + t.vfp + t.vsync, vact + vbl, t.vfp, t.vsync, t.vbp,
	       t.vborder ? (std::string(" vborder ") + std::to_string(t.vborder)).c_str() : "",
	       prefix,
	       t.pos_pol_hsync ? '+' : '-', t.no_pol_vsync ? "" : (t.pos_pol_vsync ? " +vsync" : " -vsync"),
	       prefix,
	       refresh, t.interlaced ? "i" : "",
	       t.hact + hbl ? (double)t.pixclk_khz / (t.hact + hbl) : 0.0);

	if (t.hbp <= 0)
		fail("0 or negative horizontal back porch\n");
	if (t.vbp <= 0)
		fail("0 or negative vertical back porch\n");
	if ((!max_display_width_mm && t.hsize_mm) ||
	    (!max_display_height_mm && t.vsize_mm)) {
		fail("Mismatch of image size vs display size: image size is set, but not display size\n");
	} else if (!t.hsize_mm && !t.vsize_mm) {
		/* this is valid */
	} else if (t.hsize_mm > max_display_width_mm + 9 ||
		   t.vsize_mm > max_display_height_mm + 9) {
		fail("Mismatch of image size %ux%u mm vs display size %ux%u mm\n",
		     t.hsize_mm, t.vsize_mm, max_display_width_mm, max_display_height_mm);
	} else if (t.hsize_mm < max_display_width_mm - 9 &&
		   t.vsize_mm < max_display_height_mm - 9) {
		fail("Mismatch of image size %ux%u mm vs display size %ux%u mm\n",
		     t.hsize_mm, t.vsize_mm, max_display_width_mm, max_display_height_mm);
	}
	if (refresh) {
		min_vert_freq_hz = min(min_vert_freq_hz, refresh);
		max_vert_freq_hz = max(max_vert_freq_hz, refresh);
	}
	if (t.pixclk_khz && (t.hact + hbl)) {
		min_hor_freq_hz = min(min_hor_freq_hz, (t.pixclk_khz * 1000) / (t.hact + hbl));
		max_hor_freq_hz = max(max_hor_freq_hz, (t.pixclk_khz * 1000) / (t.hact + hbl));
		max_pixclk_khz = max(max_pixclk_khz, t.pixclk_khz);
	}
	return ok;
}

std::string utohex(unsigned char x)
{
	char buf[10];

	sprintf(buf, "0x%02hhx", x);
	return buf;
}

bool memchk(const unsigned char *x, unsigned len, unsigned char v)
{
	for (unsigned i = 0; i < len; i++)
		if (x[i] != v)
			return false;
	return true;
}

void hex_block(const char *prefix, const unsigned char *x,
	       unsigned length, bool show_ascii, unsigned step)
{
	unsigned i, j;

	if (!length)
		return;

	for (i = 0; i < length; i += step) {
		printf("%s", prefix);
		for (j = 0; j < step; j++)
			if (i + j < length)
				printf("%02x ", x[i + j]);
			else if (length > step)
				printf("   ");
		if (show_ascii) {
			printf(" ");
			for (j = 0; j < step && i + j < length; j++)
				printf("%c", x[i + j] >= ' ' && x[i + j] <= '~' ? x[i + j] : '.');
		}
		printf("\n");
	}
}

static bool edid_add_byte(const char *s)
{
	char buf[3];

	if (state.edid_size == sizeof(edid))
		return false;
	buf[0] = s[0];
	buf[1] = s[1];
	buf[2] = 0;
	edid[state.edid_size++] = strtoul(buf, NULL, 16);
	return true;
}

static bool extract_edid_quantumdata(const char *start)
{
	/* Parse QuantumData 980 EDID files */
	do {
		start = strstr(start, ">");
		if (!start)
			return false;
		start++;
		for (unsigned i = 0; start[i] && start[i + 1] && i < 256; i += 2)
			if (!edid_add_byte(start + i))
				return false;
		start = strstr(start, "<BLOCK");
	} while (start);
	return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}

static const char *ignore_chars = ",:;";

static bool extract_edid_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 a %02x from the log */
		if (!isxdigit(s[0]) || !isxdigit(s[1])) {
			if (state.edid_size && state.edid_size % 128 == 0)
				break;
			return false;
		}
		if (!edid_add_byte(s))
			return false;
		s++;
	}
	return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}

static bool extract_edid_xrandr(const char *start)
{
	static const char indentation1[] = "                ";
	static const char indentation2[] = "\t\t";
	/* Used to detect that we've gone past the EDID property */
	static const char half_indentation1[] = "        ";
	static const char half_indentation2[] = "\t";
	const char *indentation;
	const char *s;

	for (;;) {
		unsigned j;

		/* Get the next start of the line of EDID hex, assuming spaces for indentation */
		s = strstr(start, indentation = indentation1);
		/* Did we skip the start of another property? */
		if (s && s > strstr(start, half_indentation1))
			break;

		/* If we failed, retry assuming tabs for indentation */
		if (!s) {
			s = strstr(start, indentation = indentation2);
			/* Did we skip the start of another property? */
			if (s && s > strstr(start, half_indentation2))
				break;
		}

		if (!s)
			break;

		start = s + strlen(indentation);

		for (j = 0; j < 16; j++, start += 2) {
			/* Read a %02x from the log */
			if (!isxdigit(start[0]) || !isxdigit(start[1])) {
				if (j)
					break;
				return false;
			}
			if (!edid_add_byte(start))
				return false;
		}
	}
	return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}

static bool extract_edid_xorg(const char *start)
{
	bool find_first_num = true;

	for (; *start; start++) {
		if (find_first_num) {
			const char *s;

			/* skip ahead to the : */
			s = strstr(start, ": \t");
			if (!s)
				s = strstr(start, ":     ");
			if (!s)
				break;
			start = s;
			/* and find the first number */
			while (!isxdigit(start[1]))
				start++;
			find_first_num = false;
			continue;
		} else {
			/* Read a %02x from the log */
			if (!isxdigit(*start)) {
				find_first_num = true;
				continue;
			}
			if (!edid_add_byte(start))
				return false;
			start++;
		}
	}
	return state.edid_size && !(state.edid_size % EDID_PAGE_SIZE);
}

static bool extract_edid(int fd)
{
	std::vector<char> edid_data;
	char buf[EDID_PAGE_SIZE];

	for (;;) {
		ssize_t i = read(fd, buf, sizeof(buf));

		if (i < 0)
			return false;
		if (i == 0)
			break;
		edid_data.insert(edid_data.end(), buf, buf + i);
	}

	if (edid_data.empty())
		return false;

	const char *data = &edid_data[0];
	const char *start;

	/* Look for edid-decode output */
	start = strstr(data, "EDID (hex):");
	if (!start)
		start = strstr(data, "edid-decode (hex):");
	if (start)
		return extract_edid_hex(strchr(start, ':'));

	/* Look for C-array */
	start = strstr(data, "unsigned char edid[] = {");
	if (start)
		return extract_edid_hex(strchr(start, '{') + 1);

	/* Look for QuantumData EDID output */
	start = strstr(data, "<BLOCK");
	if (start)
		return extract_edid_quantumdata(start);

	/* Look for xrandr --verbose output (lines of 16 hex bytes) */
	start = strstr(data, "EDID_DATA:");
	if (!start)
		start = strstr(data, "EDID:");
	if (start)
		return extract_edid_xrandr(start);

	/* Look for an EDID in an Xorg.0.log file */
	start = strstr(data, "EDID (in hex):");
	if (start)
		start = strstr(start, "(II)");
	if (start)
		return extract_edid_xorg(start);

	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_edid_hex(data);

	/* Assume binary */
	if (edid_data.size() % EDID_PAGE_SIZE || edid_data.size() > sizeof(edid))
		return false;
	memcpy(edid, data, edid_data.size());
	state.edid_size = edid_data.size();
	return true;
}

static void print_subsection(const char *name, const unsigned char *edid,
			     unsigned start, unsigned end)
{
	unsigned i;

	printf("%s:", name);
	for (i = strlen(name); i < 15; i++)
		printf(" ");
	for (i = start; i <= end; i++)
		printf(" %02x", edid[i]);
	printf("\n");
}

static void dump_breakdown(const unsigned char *edid)
{
	printf("Extracted contents:\n");
	print_subsection("header", edid, 0, 7);
	print_subsection("serial number", edid, 8, 17);
	print_subsection("version", edid,18, 19);
	print_subsection("basic params", edid, 20, 24);
	print_subsection("chroma info", edid, 25, 34);
	print_subsection("established", edid, 35, 37);
	print_subsection("standard", edid, 38, 53);
	print_subsection("descriptor 1", edid, 54, 71);
	print_subsection("descriptor 2", edid, 72, 89);
	print_subsection("descriptor 3", edid, 90, 107);
	print_subsection("descriptor 4", edid, 108, 125);
	print_subsection("extensions", edid, 126, 126);
	print_subsection("checksum", edid, 127, 127);
	printf("\n");
}

static unsigned char crc_calc(const unsigned char *b)
{
	unsigned char sum = 0;
	unsigned i;

	for (i = 0; i < 127; i++)
		sum += b[i];
	return 256 - sum;
}

static int crc_ok(const unsigned char *b)
{
	return crc_calc(b) == b[127];
}

static void hexdumpedid(FILE *f, const unsigned char *edid, unsigned size)
{
	unsigned b, i, j;

	for (b = 0; b < size / 128; b++) {
		const unsigned char *buf = edid + 128 * b;

		if (b)
			fprintf(f, "\n");
		for (i = 0; i < 128; i += 0x10) {
			fprintf(f, "%02x", buf[i]);
			for (j = 1; j < 0x10; j++) {
				fprintf(f, " %02x", buf[i + j]);
			}
			fprintf(f, "\n");
		}
		if (!crc_ok(buf))
			fprintf(f, "Block %u has a checksum error (should be 0x%02x)\n",
				b, crc_calc(buf));
	}
}

static void carraydumpedid(FILE *f, const unsigned char *edid, unsigned size)
{
	unsigned b, i, j;

	fprintf(f, "const unsigned char edid[] = {\n");
	for (b = 0; b < size / 128; b++) {
		const unsigned char *buf = edid + 128 * b;

		if (b)
			fprintf(f, "\n");
		for (i = 0; i < 128; i += 8) {
			fprintf(f, "\t0x%02x,", buf[i]);
			for (j = 1; j < 8; j++) {
				fprintf(f, " 0x%02x,", buf[i + j]);
			}
			fprintf(f, "\n");
		}
		if (!crc_ok(buf))
			fprintf(f, "\t/* Block %u has a checksum error (should be 0x%02x) */\n",
				b, crc_calc(buf));
	}
	fprintf(f, "};\n");
}

static void write_edid(FILE *f, const unsigned char *edid, unsigned size,
		       enum output_format out_fmt)
{
	switch (out_fmt) {
	default:
	case OUT_FMT_HEX:
		hexdumpedid(f, edid, size);
		break;
	case OUT_FMT_RAW:
		fwrite(edid, size, 1, f);
		break;
	case OUT_FMT_CARRAY:
		carraydumpedid(f, edid, size);
		break;
	}
}

static int edid_from_file(const char *from_file, const char *to_file,
			  enum output_format out_fmt)
{
	FILE *out = NULL;
	int fd;

	if (!from_file || !strcmp(from_file, "-")) {
		from_file = "stdin";
		fd = 0;
	} else if ((fd = open(from_file, O_RDONLY)) == -1) {
		perror(from_file);
		return -1;
	}
	if (to_file) {
		if (!strcmp(to_file, "-")) {
			to_file = "stdout";
			out = stdout;
		} else if ((out = fopen(to_file, "w")) == NULL) {
			perror(to_file);
			return -1;
		}
		if (out_fmt == OUT_FMT_DEFAULT)
			out_fmt = out == stdout ? OUT_FMT_HEX : OUT_FMT_RAW;
	}

	if (!extract_edid(fd)) {
		fprintf(stderr, "EDID extract of '%s' failed\n", from_file);
		return -1;
	}
	state.num_blocks = state.edid_size / EDID_PAGE_SIZE;
	if (fd != 0)
		close(fd);

	if (memcmp(edid, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", 8)) {
		fprintf(stderr, "No EDID header found in '%s'\n", from_file);
		return -1;
	}

	if (out) {
		write_edid(out, edid, state.edid_size, out_fmt);
		if (out != stdout)
			fclose(out);
		return 0;
	}

	return 0;
}

/* generic extension code */

std::string block_name(unsigned char block)
{
	char buf[10];

	switch (block) {
	case 0x00: return "Base Block";
	case 0x02: return "CTA-861 Extension Block";
	case 0x10: return "VTB Extension Block";
	case 0x40: return "Display Information Extension Block";
	case 0x50: return "Localized String Extension Block";
	case 0x70: return "DisplayID Extension Block";
	case 0xf0: return "Block Map Extension Block";
	case 0xff: return "Manufacturer-Specific Extension Block";
	default:
		sprintf(buf, " 0x%02x", block);
		return std::string("Unknown EDID Extension Block") + buf;
	}
}

void edid_state::parse_block_map(const unsigned char *x)
{
	static bool saw_block_1;
	unsigned last_valid_block_tag = 0;
	bool fail_once = false;
	unsigned offset = 1;
	unsigned i;

	printf("%s\n", block.c_str());
	if (block_nr == 1)
		saw_block_1 = true;
	else if (!saw_block_1)
		fail("No EDID Block Map Extension found in block 1\n");

	if (block_nr > 1)
		offset = 128;

	for (i = 1; i < 127; i++) {
		unsigned block = offset + i;

		if (x[i]) {
			last_valid_block_tag++;
			if (i != last_valid_block_tag && !fail_once) {
				fail("Valid block tags are not consecutive\n");
				fail_once = true;
			}
			printf("  Block %3u: %s\n", block, block_name(block).c_str());
			if (block >= num_blocks && !fail_once) {
				fail("Invalid block number %u\n", block);
				fail_once = true;
			}
		}
	}
}

void edid_state::preparse_extension(const unsigned char *x)
{
	switch (x[0]) {
	case 0x02:
		preparse_cta_block(x);
		break;
	case 0x70:
		preparse_displayid_block(x);
		break;
	}
}

void edid_state::parse_extension(const unsigned char *x)
{
	block = block_name(x[0]);
	data_block.clear();

	printf("\n");

	switch (x[0]) {
	case 0x02:
		parse_cta_block(x);
		break;
	case 0x10:
		parse_vtb_ext_block(x);
		break;
	case 0x20:
		printf("%s\n", block.c_str());
		fail("Deprecated extension block, do not use\n");
		break;
	case 0x40:
		parse_di_ext_block(x);
		break;
	case 0x50:
		parse_ls_ext_block(x);
		break;
	case 0x70:
		parse_displayid_block(x);
		break;
	case 0xf0:
		parse_block_map(x);
		if (block_nr != 1 && block_nr != 128)
			fail("Must be used in block 1 and 128\n");
		break;
	default:
		printf("%s\n", block.c_str());
		hex_block("  ", x, EDID_PAGE_SIZE);
		fail("Unknown Extension Block\n");
		break;
	}

	data_block.clear();
	do_checksum("", x, EDID_PAGE_SIZE);
}

int edid_state::parse_edid()
{
	if (!options[OptSkipHexDump]) {
		printf("edid-decode (hex):\n\n");
		for (unsigned i = 0; i < num_blocks; i++) {
			hex_block("", edid + i * EDID_PAGE_SIZE, EDID_PAGE_SIZE, false);
			printf("\n");
		}
		printf("----------------\n\n");
	}

	if (options[OptExtract])
		dump_breakdown(edid);

	block = block_name(0x00);
	parse_base_block(edid);

	for (unsigned i = 1; i < num_blocks; i++)
		preparse_extension(edid + i * EDID_PAGE_SIZE);

	for (unsigned i = 1; i < num_blocks; i++) {
		block_nr++;
		printf("\n----------------\n");
		parse_extension(edid + i * EDID_PAGE_SIZE);
	}

	block = "";
	block_nr = EDID_MAX_BLOCKS;
	if (uses_gtf && !supports_gtf)
		fail("GTF timings are used, but the EDID does not signal GTF support\n");
	if (uses_cvt && !supports_cvt)
		fail("CVT timings are used, but the EDID does not signal CVT support\n");
	if (has_display_range_descriptor &&
	    (min_vert_freq_hz < min_display_vert_freq_hz ||
	     max_vert_freq_hz > max_display_vert_freq_hz ||
	     min_hor_freq_hz / 1000 < min_display_hor_freq_hz / 1000 ||
	     max_hor_freq_hz / 1000  > max_display_hor_freq_hz / 1000 ||
	     max_pixclk_khz > max_display_pixclk_khz)) {
		/*
		 * EDID 1.4 states (in an Errata) that explicitly defined
		 * timings supersede the monitor range definition.
		 */
		char buf[512];
		snprintf(buf, sizeof(buf),
			"One or more of the timings is out of range of the Monitor Ranges:\n"
			"    Vertical Freq: %u - %u Hz (Monitor: %u - %u Hz)\n"
			"    Horizontal Freq: %.3f - %.3f kHz (Monitor: %.3f - %.3f kHz)\n"
			"    Maximum Clock: %.3f MHz (Monitor: %.3f MHz)\n",
			min_vert_freq_hz, max_vert_freq_hz,
			min_display_vert_freq_hz, max_display_vert_freq_hz,
			min_hor_freq_hz / 1000.0, max_hor_freq_hz / 1000.0,
			min_display_hor_freq_hz / 1000.0, max_display_hor_freq_hz / 1000.0,
			max_pixclk_khz / 1000.0, max_display_pixclk_khz / 1000.0);

		if (edid_minor < 4) {
			fail("%s", buf);
		} else {
			warn("%s", buf);
		}
	}

	if (!options[OptCheck] && !options[OptCheckInline])
		return 0;

	printf("\n----------------\n");

	if (!options[OptSkipSHA]) {
#ifdef SHA
#define STR(x) #x
#define STRING(x) STR(x)
		printf("\nedid-decode SHA: %s\n", STRING(SHA));
#else
		printf("\nedid-decode SHA: not available\n");
#endif
	}

	if (options[OptCheck]) {
		if (warnings)
			show_msgs(true);
		if (failures)
			show_msgs(false);
	}
	printf("\nEDID conformity: %s\n", failures ? "FAIL" : "PASS");
	return failures ? -2 : 0;
}

int main(int argc, char **argv)
{
	char short_options[26 * 2 * 2 + 1];
	enum output_format out_fmt = OUT_FMT_DEFAULT;
	int ret;

	while (1) {
		int option_index = 0;
		unsigned idx = 0;
		unsigned i;

		for (i = 0; long_options[i].name; i++) {
			if (!isalpha(long_options[i].val))
				continue;
			short_options[idx++] = long_options[i].val;
			if (long_options[i].has_arg == required_argument)
				short_options[idx++] = ':';
		}
		short_options[idx] = 0;
		int ch = getopt_long(argc, argv, short_options,
				     long_options, &option_index);
		if (ch == -1)
			break;

		options[ch] = 1;
		switch (ch) {
		case OptHelp:
			usage();
			return -1;
		case OptOutputFormat:
			if (!strcmp(optarg, "hex")) {
				out_fmt = OUT_FMT_HEX;
			} else if (!strcmp(optarg, "raw")) {
				out_fmt = OUT_FMT_RAW;
			} else if (!strcmp(optarg, "carray")) {
				out_fmt = OUT_FMT_CARRAY;
			} else {
				usage();
				exit(1);
			}
			break;
		case ':':
			fprintf(stderr, "Option '%s' requires a value\n",
				argv[optind]);
			usage();
			return -1;
		case '?':
			fprintf(stderr, "Unknown argument '%s'\n",
				argv[optind]);
			usage();
			return -1;
		}
	}
	if (optind == argc)
		ret = edid_from_file(NULL, NULL, out_fmt);
	else if (optind == argc - 1)
		ret = edid_from_file(argv[optind], NULL, out_fmt);
	else
		return edid_from_file(argv[optind], argv[optind + 1], out_fmt);
	return ret ? ret : state.parse_edid();
}
