edid-decode: fix emscripten build
[edid-decode.git] / edid-decode.cpp
blob11c85c77db1ddff7edaeebf7ffc6470a9754fe10
1 // SPDX-License-Identifier: MIT
2 /*
3 * Copyright 2006-2012 Red Hat, Inc.
4 * Copyright 2018-2020 Cisco Systems, Inc. and/or its affiliates. All rights reserved.
6 * Author: Adam Jackson <ajax@nwnk.net>
7 * Maintainer: Hans Verkuil <hverkuil-cisco@xs4all.nl>
8 */
10 #include <ctype.h>
11 #include <fcntl.h>
12 #include <getopt.h>
13 #include <math.h>
14 #include <stdarg.h>
15 #include <stdio.h>
16 #include <stdlib.h>
17 #include <unistd.h>
19 #include "edid-decode.h"
21 #define STR(x) #x
22 #define STRING(x) STR(x)
24 static edid_state state;
26 static unsigned char edid[EDID_PAGE_SIZE * EDID_MAX_BLOCKS];
27 static bool odd_hex_digits;
29 enum output_format {
30 OUT_FMT_DEFAULT,
31 OUT_FMT_HEX,
32 OUT_FMT_RAW,
33 OUT_FMT_CARRAY,
34 OUT_FMT_XML,
38 * Options
39 * Please keep in alphabetical order of the short option.
40 * That makes it easier to see which options are still free.
42 enum Option {
43 OptI2CAdapter = 'a',
44 OptCheck = 'c',
45 OptCheckInline = 'C',
46 OptFBModeTimings = 'F',
47 OptHelp = 'h',
48 OptOnlyHexDump = 'H',
49 OptInfoFrame = 'I',
50 OptLongTimings = 'L',
51 OptNativeResolution = 'n',
52 OptNTSC = 'N',
53 OptOutputFormat = 'o',
54 OptPreferredTimings = 'p',
55 OptPhysicalAddress = 'P',
56 OptSkipHexDump = 's',
57 OptShortTimings = 'S',
58 OptUTF8 = 'u',
59 OptV4L2Timings = 'V',
60 OptXModeLineTimings = 'X',
61 OptSkipSHA = 128,
62 OptHideSerialNumbers,
63 OptReplaceUniqueIDs,
64 OptVersion,
65 OptDiag,
66 OptI2CEDID,
67 OptI2CHDCP,
68 OptI2CHDCPRi,
69 OptSTD,
70 OptDMT,
71 OptVIC,
72 OptHDMIVIC,
73 OptCVT,
74 OptGTF,
75 OptOVT,
76 OptListEstTimings,
77 OptListDMTs,
78 OptListVICs,
79 OptListHDMIVICs,
80 OptListRIDTimings,
81 OptListRIDs,
82 OptLast = 256
85 static char options[OptLast];
87 #ifndef __EMSCRIPTEN__
88 static struct option long_options[] = {
89 { "help", no_argument, 0, OptHelp },
90 { "output-format", required_argument, 0, OptOutputFormat },
91 { "native-resolution", no_argument, 0, OptNativeResolution },
92 { "preferred-timings", no_argument, 0, OptPreferredTimings },
93 { "physical-address", no_argument, 0, OptPhysicalAddress },
94 { "skip-hex-dump", no_argument, 0, OptSkipHexDump },
95 { "only-hex-dump", no_argument, 0, OptOnlyHexDump },
96 { "skip-sha", no_argument, 0, OptSkipSHA },
97 { "hide-serial-numbers", no_argument, 0, OptHideSerialNumbers },
98 { "replace-unique-ids", no_argument, 0, OptReplaceUniqueIDs },
99 { "utf8", no_argument, 0, OptUTF8 },
100 { "version", no_argument, 0, OptVersion },
101 { "check-inline", no_argument, 0, OptCheckInline },
102 { "check", no_argument, 0, OptCheck },
103 { "short-timings", no_argument, 0, OptShortTimings },
104 { "long-timings", no_argument, 0, OptLongTimings },
105 { "ntsc", no_argument, 0, OptNTSC },
106 { "xmodeline", no_argument, 0, OptXModeLineTimings },
107 { "fbmode", no_argument, 0, OptFBModeTimings },
108 { "v4l2-timings", no_argument, 0, OptV4L2Timings },
109 { "diagonal", required_argument, 0, OptDiag },
110 { "i2c-adapter", required_argument, 0, OptI2CAdapter },
111 { "i2c-edid", no_argument, 0, OptI2CEDID },
112 { "i2c-hdcp", no_argument, 0, OptI2CHDCP },
113 { "i2c-hdcp-ri", required_argument, 0, OptI2CHDCPRi },
114 { "std", required_argument, 0, OptSTD },
115 { "dmt", required_argument, 0, OptDMT },
116 { "vic", required_argument, 0, OptVIC },
117 { "hdmi-vic", required_argument, 0, OptHDMIVIC },
118 { "cvt", required_argument, 0, OptCVT },
119 { "gtf", required_argument, 0, OptGTF },
120 { "ovt", required_argument, 0, OptOVT },
121 { "list-established-timings", no_argument, 0, OptListEstTimings },
122 { "list-dmts", no_argument, 0, OptListDMTs },
123 { "list-vics", no_argument, 0, OptListVICs },
124 { "list-hdmi-vics", no_argument, 0, OptListHDMIVICs },
125 { "list-rid-timings", required_argument, 0, OptListRIDTimings },
126 { "list-rids", no_argument, 0, OptListRIDs },
127 { "infoframe", required_argument, 0, OptInfoFrame },
128 { 0, 0, 0, 0 }
131 static void usage(void)
133 printf("Usage: edid-decode <options> [in [out]]\n"
134 " [in] EDID file to parse. Read from standard input if none given\n"
135 " and --infoframe was not used, or if the input filename is '-'.\n"
136 " [out] Output the read EDID to this file. Write to standard output\n"
137 " if the output filename is '-'.\n"
138 "\nOptions:\n"
139 " -o, --output-format <fmt>\n"
140 " If [out] is specified, then write the EDID in this format.\n"
141 " <fmt> is one of:\n"
142 " hex: hex numbers in ascii text (default for stdout)\n"
143 " raw: binary data (default unless writing to stdout)\n"
144 " carray: c-program struct\n"
145 " xml: XML data\n"
146 " -c, --check Check if the EDID conforms to the standards, failures and\n"
147 " warnings are reported at the end.\n"
148 " -C, --check-inline Check if the EDID conforms to the standards, failures and\n"
149 " warnings are reported inline.\n"
150 " -n, --native-resolution Report the native resolution.\n"
151 " -p, --preferred-timings Report the preferred timings.\n"
152 " -P, --physical-address Only report the CEC physical address.\n"
153 " -S, --short-timings Report all video timings in a short format.\n"
154 " -L, --long-timings Report all video timings in a long format.\n"
155 " -N, --ntsc Report the video timings suitable for NTSC-based video.\n"
156 " -X, --xmodeline Report all long video timings in Xorg.conf format.\n"
157 " -F, --fbmode Report all long video timings in fb.modes format.\n"
158 " -V, --v4l2-timings Report all long video timings in v4l2-dv-timings.h format.\n"
159 " -s, --skip-hex-dump Skip the initial hex dump of the EDID.\n"
160 " -H, --only-hex-dump Only output the hex dump of the EDID.\n"
161 " --skip-sha Skip the SHA report.\n"
162 " --hide-serial-numbers Hide serial numbers with '...'.\n"
163 " --replace-unique-ids Replace unique IDs (serial numbers, dates, Container IDs) with fixed values.\n"
164 " -u, --utf8 Convert strings in EDIDs to UTF-8.\n"
165 " --version Show the edid-decode version (SHA).\n"
166 " --diagonal <inches> Set the display's diagonal in inches.\n"
167 " -a, --i2c-adapter <num> Use /dev/i2c-<num> to access the DDC lines.\n"
168 " --i2c-edid Read the EDID from the DDC lines.\n"
169 " --i2c-hdcp Read the HDCP from the DDC lines.\n"
170 " --i2c-hdcp-ri=<t> Read and print the HDCP Ri information every <t> seconds.\n"
171 " --std <byte1>,<byte2> Show the standard timing represented by these two bytes.\n"
172 " --dmt <dmt> Show the timings for the DMT with the given DMT ID.\n"
173 " --vic <vic> Show the timings for this VIC.\n"
174 " --hdmi-vic <hdmivic> Show the timings for this HDMI VIC.\n"
175 " --cvt w=<width>,h=<height>,fps=<fps>[,rb=<rb>][,interlaced][,overscan][,alt][,hblank=<hblank>][,vblank=<vblank>][,early-vsync]\n"
176 " Calculate the CVT timings for the given format.\n"
177 " <fps> is frames per second for progressive timings,\n"
178 " or fields per second for interlaced timings.\n"
179 " <rb> can be 0 (no reduced blanking, default), or\n"
180 " 1-3 for the reduced blanking version.\n"
181 " If 'interlaced' is given, then this is an interlaced format.\n"
182 " If 'overscan' is given, then this is an overscanned format.\n"
183 " If 'alt' is given and <rb>=2, then report the timings\n"
184 " optimized for video: 1000 / 1001 * <fps>.\n"
185 " If 'alt' is given and <rb>=3, then the horizontal blanking\n"
186 " is 160 instead of 80 pixels.\n"
187 " If 'hblank' is given and <rb>=3, then the horizontal blanking\n"
188 " is <hblank> pixels (range of 80-200), overriding 'alt'.\n"
189 " If 'vblank' is given and <rb>=3, then the vertical blanking\n"
190 " time is <vblank> microseconds (range of 460-705 or 300-440).\n"
191 " If 'early-vsync' is given and <rb=3>, then select early vsync.\n"
192 " --gtf w=<width>,h=<height>[,fps=<fps>][,horfreq=<horfreq>][,pixclk=<pixclk>][,interlaced]\n"
193 " [,overscan][,secondary][,C=<c>][,M=<m>][,K=<k>][,J=<j>]\n"
194 " Calculate the GTF timings for the given format.\n"
195 " <fps> is frames per second for progressive timings,\n"
196 " or fields per second for interlaced timings.\n"
197 " <horfreq> is the horizontal frequency in kHz.\n"
198 " <pixclk> is the pixel clock frequency in MHz.\n"
199 " Only one of fps, horfreq or pixclk must be given.\n"
200 " If 'interlaced' is given, then this is an interlaced format.\n"
201 " If 'overscan' is given, then this is an overscanned format.\n"
202 " If 'secondary' is given, then the secondary GTF is used for\n"
203 " reduced blanking, where <c>, <m>, <k> and <j> are parameters\n"
204 " for the secondary curve.\n"
205 " --ovt (rid=<rid>|w=<width>,h=<height>),fps=<fps>\n"
206 " Calculate the OVT timings for the given format.\n"
207 " Either specify a RID or explicitly specify width and height.\n"
208 " --list-established-timings List all known Established Timings.\n"
209 " --list-dmts List all known DMTs.\n"
210 " --list-vics List all known VICs.\n"
211 " --list-hdmi-vics List all known HDMI VICs.\n"
212 " --list-rids List all known RIDs.\n"
213 " --list-rid-timings <rid> List all timings for RID <rid> or all known RIDs if <rid> is 0.\n"
214 " -I, --infoframe <file> Parse the InfoFrame from <file> that was sent to this display.\n"
215 " This option can be specified multiple times for different InfoFrame files.\n"
216 " -h, --help Display this help message.\n");
218 #endif
220 static std::string s_msgs[EDID_MAX_BLOCKS + 1][2];
222 void msg(bool is_warn, const char *fmt, ...)
224 char buf[1024] = "";
225 va_list ap;
227 va_start(ap, fmt);
228 vsprintf(buf, fmt, ap);
229 va_end(ap);
231 if (is_warn)
232 state.warnings++;
233 else
234 state.failures++;
235 if (state.data_block.empty())
236 s_msgs[state.block_nr][is_warn] += std::string(" ") + buf;
237 else
238 s_msgs[state.block_nr][is_warn] += " " + state.data_block + ": " + buf;
240 if (options[OptCheckInline])
241 printf("%s: %s", is_warn ? "WARN" : "FAIL", buf);
244 static void show_msgs(bool is_warn)
246 printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures");
247 for (unsigned i = 0; i < state.num_blocks; i++) {
248 if (s_msgs[i][is_warn].empty())
249 continue;
250 printf("Block %u, %s:\n%s",
251 i, block_name(edid[i * EDID_PAGE_SIZE]).c_str(),
252 s_msgs[i][is_warn].c_str());
254 if (s_msgs[EDID_MAX_BLOCKS][is_warn].empty())
255 return;
256 printf("EDID:\n%s",
257 s_msgs[EDID_MAX_BLOCKS][is_warn].c_str());
261 void replace_checksum(unsigned char *x, size_t len)
263 unsigned char sum = 0;
264 unsigned i;
266 for (i = 0; i < len - 1; i++)
267 sum += x[i];
268 x[len - 1] = -sum & 0xff;
271 void do_checksum(const char *prefix, const unsigned char *x, size_t len, size_t checksum_pos,
272 unsigned unused_bytes)
274 unsigned char check = x[checksum_pos];
275 unsigned char sum = 0;
276 unsigned i;
278 for (i = 0; i < len; i++) {
279 if (i != checksum_pos)
280 sum += x[i];
283 printf("%sChecksum: 0x%02hhx", prefix, check);
284 if ((unsigned char)(check + sum) != 0) {
285 printf(" (should be 0x%02x)", -sum & 0xff);
286 fail("Invalid checksum 0x%02x (should be 0x%02x).\n",
287 check, -sum & 0xff);
289 if (unused_bytes)
290 printf(" Unused space in Extension Block: %u byte%s",
291 unused_bytes, unused_bytes > 1 ? "s" : "");
292 printf("\n");
295 unsigned gcd(unsigned a, unsigned b)
297 while (b) {
298 unsigned t = b;
300 b = a % b;
301 a = t;
303 return a;
306 void calc_ratio(struct timings *t)
308 unsigned d = gcd(t->hact, t->vact);
310 if (d == 0) {
311 t->hratio = t->vratio = 0;
312 return;
314 t->hratio = t->hact / d;
315 t->vratio = t->vact / d;
317 if (t->hratio == 8 && t->vratio == 5) {
318 t->hratio = 16;
319 t->vratio = 10;
323 unsigned calc_fps(const struct timings *t)
325 unsigned vact = t->vact;
326 unsigned vbl = t->vfp + t->vsync + t->vbp + 2 * t->vborder;
327 unsigned hbl = t->hfp + t->hsync + t->hbp + 2 * t->hborder;
328 unsigned htotal = t->hact + hbl;
330 if (t->interlaced)
331 vact /= 2;
333 double vtotal = vact + vbl;
335 if (t->even_vtotal)
336 vtotal = vact + t->vfp + t->vsync + t->vbp;
337 else if (t->interlaced)
338 vtotal = vact + t->vfp + t->vsync + t->vbp + 0.5;
340 return t->pixclk_khz * 1000.0 / (htotal * vtotal);
343 std::string edid_state::dtd_type(unsigned cnt)
345 unsigned len = std::to_string(cta.preparsed_total_dtds).length();
346 char buf[16];
347 sprintf(buf, "DTD %*u", len, cnt);
348 return buf;
351 bool match_timings(const timings &t1, const timings &t2)
353 if (t1.hact != t2.hact ||
354 t1.vact != t2.vact ||
355 t1.rb != t2.rb ||
356 t1.interlaced != t2.interlaced ||
357 t1.hfp != t2.hfp ||
358 t1.hbp != t2.hbp ||
359 t1.hsync != t2.hsync ||
360 t1.pos_pol_hsync != t2.pos_pol_hsync ||
361 t1.hratio != t2.hratio ||
362 t1.vfp != t2.vfp ||
363 t1.vbp != t2.vbp ||
364 t1.vsync != t2.vsync ||
365 t1.pos_pol_vsync != t2.pos_pol_vsync ||
366 t1.vratio != t2.vratio ||
367 t1.pixclk_khz != t2.pixclk_khz)
368 return false;
369 return true;
372 static void or_str(std::string &s, const std::string &flag, unsigned &num_flags)
374 if (!num_flags)
375 s = flag;
376 else if (num_flags % 2 == 0)
377 s = s + " | \\\n\t\t" + flag;
378 else
379 s = s + " | " + flag;
380 num_flags++;
384 * Return true if the timings are a close, but not identical,
385 * match. The only differences allowed are polarities and
386 * porches and syncs, provided the total blanking remains the
387 * same.
389 bool timings_close_match(const timings &t1, const timings &t2)
391 // We don't want to deal with borders, you're on your own
392 // if you are using those.
393 if (t1.hborder || t1.vborder ||
394 t2.hborder || t2.vborder)
395 return false;
396 if (t1.hact != t2.hact || t1.vact != t2.vact ||
397 t1.interlaced != t2.interlaced ||
398 t1.pixclk_khz != t2.pixclk_khz ||
399 t1.hfp + t1.hsync + t1.hbp != t2.hfp + t2.hsync + t2.hbp ||
400 t1.vfp + t1.vsync + t1.vbp != t2.vfp + t2.vsync + t2.vbp)
401 return false;
402 if (t1.hfp == t2.hfp &&
403 t1.hsync == t2.hsync &&
404 t1.hbp == t2.hbp &&
405 t1.pos_pol_hsync == t2.pos_pol_hsync &&
406 t1.vfp == t2.vfp &&
407 t1.vsync == t2.vsync &&
408 t1.vbp == t2.vbp &&
409 t1.pos_pol_vsync == t2.pos_pol_vsync)
410 return false;
411 return true;
414 static void print_modeline(unsigned indent, const struct timings *t, double refresh)
416 unsigned offset = (!t->even_vtotal && t->interlaced) ? 1 : 0;
417 unsigned hfp = t->hborder + t->hfp;
418 unsigned hbp = t->hborder + t->hbp;
419 unsigned vfp = t->vborder + t->vfp;
420 unsigned vbp = t->vborder + t->vbp;
422 printf("%*sModeline \"%ux%u_%.2f%s\" %.3f %u %u %u %u %u %u %u %u %cHSync",
423 indent, "",
424 t->hact, t->vact, refresh,
425 t->interlaced ? "i" : "", t->pixclk_khz / 1000.0,
426 t->hact, t->hact + hfp, t->hact + hfp + t->hsync,
427 t->hact + hfp + t->hsync + hbp,
428 t->vact, t->vact + vfp, t->vact + vfp + t->vsync,
429 t->vact + vfp + t->vsync + vbp + offset,
430 t->pos_pol_hsync ? '+' : '-');
431 if (!t->no_pol_vsync)
432 printf(" %cVSync", t->pos_pol_vsync ? '+' : '-');
433 if (t->interlaced)
434 printf(" Interlace");
435 printf("\n");
438 static void print_fbmode(unsigned indent, const struct timings *t,
439 double refresh, double hor_freq_khz)
441 printf("%*smode \"%ux%u-%u%s\"\n",
442 indent, "",
443 t->hact, t->vact,
444 (unsigned)(0.5 + (t->interlaced ? refresh / 2.0 : refresh)),
445 t->interlaced ? "-lace" : "");
446 printf("%*s# D: %.2f MHz, H: %.3f kHz, V: %.2f Hz\n",
447 indent + 8, "",
448 t->pixclk_khz / 1000.0, hor_freq_khz, refresh);
449 printf("%*sgeometry %u %u %u %u 32\n",
450 indent + 8, "",
451 t->hact, t->vact, t->hact, t->vact);
452 unsigned mult = t->interlaced ? 2 : 1;
453 unsigned offset = !t->even_vtotal && t->interlaced;
454 unsigned hfp = t->hborder + t->hfp;
455 unsigned hbp = t->hborder + t->hbp;
456 unsigned vfp = t->vborder + t->vfp;
457 unsigned vbp = t->vborder + t->vbp;
458 printf("%*stimings %llu %d %d %d %u %u %u\n",
459 indent + 8, "",
460 (unsigned long long)(1000000000.0 / (double)(t->pixclk_khz) + 0.5),
461 hbp, hfp, mult * vbp, mult * vfp + offset, t->hsync, mult * t->vsync);
462 if (t->interlaced)
463 printf("%*slaced true\n", indent + 8, "");
464 if (t->pos_pol_hsync)
465 printf("%*shsync high\n", indent + 8, "");
466 if (t->pos_pol_vsync)
467 printf("%*svsync high\n", indent + 8, "");
468 printf("%*sendmode\n", indent, "");
471 static void print_v4l2_timing(const struct timings *t,
472 double refresh, const char *type)
474 printf("\t#define V4L2_DV_BT_%uX%u%c%u_%02u { \\\n",
475 t->hact, t->vact, t->interlaced ? 'I' : 'P',
476 (unsigned)refresh, (unsigned)(0.5 + 100.0 * (refresh - (unsigned)refresh)));
477 printf("\t\t.type = V4L2_DV_BT_656_1120, \\\n");
478 printf("\t\tV4L2_INIT_BT_TIMINGS(%u, %u, %u, ",
479 t->hact, t->vact, t->interlaced);
480 if (!t->pos_pol_hsync && !t->pos_pol_vsync)
481 printf("0, \\\n");
482 else if (t->pos_pol_hsync && t->pos_pol_vsync)
483 printf("\\\n\t\t\tV4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, \\\n");
484 else if (t->pos_pol_hsync)
485 printf("V4L2_DV_HSYNC_POS_POL, \\\n");
486 else
487 printf("V4L2_DV_VSYNC_POS_POL, \\\n");
488 unsigned hfp = t->hborder + t->hfp;
489 unsigned hbp = t->hborder + t->hbp;
490 unsigned vfp = t->vborder + t->vfp;
491 unsigned vbp = t->vborder + t->vbp;
492 printf("\t\t\t%lluULL, %d, %u, %d, %u, %u, %d, %u, %u, %d, \\\n",
493 t->pixclk_khz * 1000ULL, hfp, t->hsync, hbp,
494 vfp, t->vsync, vbp,
495 t->interlaced ? vfp : 0,
496 t->interlaced ? t->vsync : 0,
497 t->interlaced ? vbp + !t->even_vtotal : 0);
499 std::string flags;
500 unsigned num_flags = 0;
501 unsigned vic = 0;
502 unsigned hdmi_vic = 0;
503 const char *std = "0";
505 if (t->interlaced && !t->even_vtotal)
506 or_str(flags, "V4L2_DV_FL_HALF_LINE", num_flags);
507 if (!memcmp(type, "VIC", 3)) {
508 or_str(flags, "V4L2_DV_FL_HAS_CEA861_VIC", num_flags);
509 or_str(flags, "V4L2_DV_FL_IS_CE_VIDEO", num_flags);
510 vic = strtoul(type + 4, 0, 0);
512 if (!memcmp(type, "HDMI VIC", 8)) {
513 or_str(flags, "V4L2_DV_FL_HAS_HDMI_VIC", num_flags);
514 or_str(flags, "V4L2_DV_FL_IS_CE_VIDEO", num_flags);
515 hdmi_vic = strtoul(type + 9, 0, 0);
516 vic = hdmi_vic_to_vic(hdmi_vic);
517 if (vic)
518 or_str(flags, "V4L2_DV_FL_HAS_CEA861_VIC", num_flags);
520 if (vic && (fmod(refresh, 6)) == 0.0)
521 or_str(flags, "V4L2_DV_FL_CAN_REDUCE_FPS", num_flags);
522 if (t->rb)
523 or_str(flags, "V4L2_DV_FL_REDUCED_BLANKING", num_flags);
524 if (t->hratio && t->vratio)
525 or_str(flags, "V4L2_DV_FL_HAS_PICTURE_ASPECT", num_flags);
527 if (!memcmp(type, "VIC", 3) || !memcmp(type, "HDMI VIC", 8))
528 std = "V4L2_DV_BT_STD_CEA861";
529 else if (!memcmp(type, "DMT", 3))
530 std = "V4L2_DV_BT_STD_DMT";
531 else if (!memcmp(type, "CVT", 3))
532 std = "V4L2_DV_BT_STD_CVT";
533 else if (!memcmp(type, "GTF", 3))
534 std = "V4L2_DV_BT_STD_GTF";
535 printf("\t\t\t%s, \\\n", std);
536 printf("\t\t\t%s, \\\n", flags.empty() ? "0" : flags.c_str());
537 printf("\t\t\t{ %u, %u }, %u, %u) \\\n",
538 t->hratio, t->vratio, vic, hdmi_vic);
539 printf("\t}\n");
542 static void print_detailed_timing(unsigned indent, const struct timings *t)
544 printf("%*sHfront %4d Hsync %3u Hback %4d Hpol %s",
545 indent, "",
546 t->hfp, t->hsync, t->hbp, t->pos_pol_hsync ? "P" : "N");
547 if (t->hborder)
548 printf(" Hborder %u", t->hborder);
549 printf("\n");
551 printf("%*sVfront %4u Vsync %3u Vback %4d",
552 indent, "", t->vfp, t->vsync, t->vbp);
553 if (!t->no_pol_vsync)
554 printf(" Vpol %s", t->pos_pol_vsync ? "P" : "N");
555 if (t->vborder)
556 printf(" Vborder %u", t->vborder);
557 if (t->even_vtotal) {
558 printf(" Both Fields");
559 } else if (t->interlaced) {
560 printf(" Vfront +0.5 Odd Field\n");
561 printf("%*sVfront %4d Vsync %3u Vback %4d",
562 indent, "", t->vfp, t->vsync, t->vbp);
563 if (!t->no_pol_vsync)
564 printf(" Vpol %s", t->pos_pol_vsync ? "P" : "N");
565 if (t->vborder)
566 printf(" Vborder %u", t->vborder);
567 printf(" Vback +0.5 Even Field");
569 printf("\n");
572 bool edid_state::print_timings(const char *prefix, const struct timings *t,
573 const char *type, const char *flags,
574 bool detailed, bool do_checks, unsigned ntsc)
576 if (!t) {
577 // Should not happen
578 if (do_checks)
579 fail("Unknown video timings.\n");
580 return false;
583 if (detailed && options[OptShortTimings])
584 detailed = false;
585 if (options[OptLongTimings])
586 detailed = true;
588 unsigned vact = t->vact;
589 unsigned hbl = t->hfp + t->hsync + t->hbp + 2 * t->hborder;
590 unsigned vbl = t->vfp + t->vsync + t->vbp + 2 * t->vborder;
591 unsigned htotal = t->hact + hbl;
592 double hor_freq_khz = htotal ? (double)t->pixclk_khz / htotal : 0;
594 if (t->interlaced)
595 vact /= 2;
597 double out_hor_freq_khz = hor_freq_khz;
598 if (t->ycbcr420)
599 hor_freq_khz /= 2;
601 double vtotal = vact + vbl;
603 bool ok = true;
605 if (!t->hact || !hbl || !t->hfp || !t->hsync ||
606 !vact || !vbl || (!t->vfp && !t->interlaced && !t->even_vtotal) || !t->vsync) {
607 if (do_checks)
608 fail("0 values in the video timing:\n"
609 " Horizontal Active/Blanking %u/%u\n"
610 " Horizontal Frontporch/Sync Width %u/%u\n"
611 " Vertical Active/Blanking %u/%u\n"
612 " Vertical Frontporch/Sync Width %u/%u\n",
613 t->hact, hbl, t->hfp, t->hsync, vact, vbl, t->vfp, t->vsync);
614 ok = false;
617 if (t->even_vtotal)
618 vtotal = vact + t->vfp + t->vsync + t->vbp;
619 else if (t->interlaced)
620 vtotal = vact + t->vfp + t->vsync + t->vbp + 0.5;
622 double refresh = t->pixclk_khz * 1000.0 / (htotal * vtotal);
623 double pixclk = t->pixclk_khz * 1000.0;
624 if (((ntsc > 1 && options[OptNTSC]) || ntsc == 1) && fmod(refresh, 6.0) == 0) {
625 const double ntsc_fact = 1000.0 / 1001.0;
626 pixclk *= ntsc_fact;
627 refresh *= ntsc_fact;
628 out_hor_freq_khz *= ntsc_fact;
631 std::string s;
632 unsigned rb = t->rb & ~RB_ALT;
633 if (rb) {
634 bool alt = t->rb & RB_ALT;
635 s = "RB";
636 if (rb == RB_CVT_V2)
637 s += std::string("v2") + (alt ? ",video-optimized" : "");
638 else if (rb == RB_CVT_V3)
639 s += std::string("v3") + (alt ? ",h-blank-160" : "");
641 add_str(s, flags);
642 if (t->hsize_mm || t->vsize_mm)
643 add_str(s, std::to_string(t->hsize_mm) + " mm x " + std::to_string(t->vsize_mm) + " mm");
644 if (t->hsize_mm > dtd_max_hsize_mm)
645 dtd_max_hsize_mm = t->hsize_mm;
646 if (t->vsize_mm > dtd_max_vsize_mm)
647 dtd_max_vsize_mm = t->vsize_mm;
648 if (!s.empty())
649 s = " (" + s + ")";
650 unsigned pixclk_khz = t->pixclk_khz / (t->ycbcr420 ? 2 : 1);
652 char buf[10];
654 sprintf(buf, "%u%s", t->vact, t->interlaced ? "i" : "");
655 printf("%s%s: %5ux%-5s %10.6f Hz %3u:%-3u %8.3f kHz %13.6f MHz%s\n",
656 prefix, type,
657 t->hact, buf,
658 refresh,
659 t->hratio, t->vratio,
660 out_hor_freq_khz,
661 pixclk / 1000000.0,
662 s.c_str());
664 unsigned len = strlen(prefix) + 2;
666 if (!t->ycbcr420 && detailed && options[OptXModeLineTimings])
667 print_modeline(len, t, refresh);
668 else if (!t->ycbcr420 && detailed && options[OptFBModeTimings])
669 print_fbmode(len, t, refresh, hor_freq_khz);
670 else if (!t->ycbcr420 && detailed && options[OptV4L2Timings])
671 print_v4l2_timing(t, refresh, type);
672 else if (detailed)
673 print_detailed_timing(len + strlen(type) + 6, t);
675 if (!do_checks)
676 return ok;
678 if (!memcmp(type, "DTD", 3)) {
679 unsigned vic, dmt;
680 const timings *vic_t = cta_close_match_to_vic(*t, vic);
682 // We report this even if there is no CTA block since it
683 // is still likely that the actual VIC timings were intended.
684 if (vic_t)
685 warn("DTD is similar but not identical to VIC %u.\n", vic);
687 if (cta_matches_vic(*t, vic) && has_cta &&
688 !cta.preparsed_has_vic[0][vic]) {
689 warn("DTD is identical to VIC %u, which is not present in the CTA Ext Block.\n", vic);
691 if (cta.preparsed_max_vic_pixclk_khz && t->pixclk_khz > 340000 &&
692 t->pixclk_khz > cta.preparsed_max_vic_pixclk_khz)
693 cta.warn_about_hdmi_2x_dtd = true;
696 const timings *dmt_t = close_match_to_dmt(*t, dmt);
697 if (!vic_t && dmt_t)
698 warn("DTD is similar but not identical to DMT 0x%02x.\n", dmt);
701 if (refresh) {
702 min_vert_freq_hz = min(min_vert_freq_hz, refresh);
703 max_vert_freq_hz = max(max_vert_freq_hz, refresh);
705 if (hor_freq_khz) {
706 min_hor_freq_hz = min(min_hor_freq_hz, hor_freq_khz * 1000.0);
707 max_hor_freq_hz = max(max_hor_freq_hz, hor_freq_khz * 1000.0);
708 max_pixclk_khz = max(max_pixclk_khz, pixclk_khz);
709 if (t->pos_pol_hsync && !t->pos_pol_vsync && t->vsync == 3)
710 base.max_pos_neg_hor_freq_khz = hor_freq_khz;
713 if (t->ycbcr420 && t->pixclk_khz < 590000)
714 warn_once("Some YCbCr 4:2:0 timings are invalid for HDMI 2.1 (which requires an RGB timings pixel rate >= 590 MHz).\n");
715 if (t->hfp <= 0)
716 fail("0 or negative horizontal front porch.\n");
717 if (t->hbp <= 0)
718 fail("0 or negative horizontal back porch.\n");
719 if (t->vbp <= 0)
720 fail("0 or negative vertical back porch.\n");
721 if (!base.max_display_width_mm && !base.max_display_height_mm) {
722 /* this is valid */
723 } else if (!t->hsize_mm && !t->vsize_mm) {
724 /* this is valid */
725 } else if (t->hsize_mm > base.max_display_width_mm + 9 ||
726 t->vsize_mm > base.max_display_height_mm + 9) {
727 fail("Mismatch of image size %ux%u mm vs display size %ux%u mm.\n",
728 t->hsize_mm, t->vsize_mm, base.max_display_width_mm, base.max_display_height_mm);
729 } else if (t->hsize_mm < base.max_display_width_mm - 9 &&
730 t->vsize_mm < base.max_display_height_mm - 9) {
731 fail("Mismatch of image size %ux%u mm vs display size %ux%u mm.\n",
732 t->hsize_mm, t->vsize_mm, base.max_display_width_mm, base.max_display_height_mm);
734 return ok;
737 std::string containerid2s(const unsigned char *x)
739 char buf[40];
741 sprintf(buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
742 x[0], x[1], x[2], x[3],
743 x[4], x[5],
744 x[6], x[7],
745 x[8], x[9],
746 x[10], x[11], x[12], x[13], x[14], x[15]);
747 return buf;
750 std::string utohex(unsigned char x)
752 char buf[10];
754 sprintf(buf, "0x%02hhx", x);
755 return buf;
758 const char *oui_name(unsigned oui, unsigned *ouinum)
760 unsigned ouinumscratch;
761 if (!ouinum) ouinum = &ouinumscratch;
762 const char *name;
763 switch (oui) {
764 #define oneoui(c,k,n) case c: *ouinum = kOUI_##k; name = n; break;
765 #include "oui.h"
766 default: *ouinum = 0; name = NULL; break;
768 return name;
771 void edid_state::data_block_oui(std::string block_name, const unsigned char *x,
772 unsigned length, unsigned *ouinum, bool ignorezeros, bool do_ascii, bool big_endian,
773 bool silent)
775 std::string buf;
776 char ascii[4];
777 unsigned oui;
778 const char *ouiname = NULL;
779 bool matched_reverse = false;
780 bool matched_ascii = false;
781 bool valid_ascii = false;
783 if (big_endian)
784 oui = ((length > 0 ? x[0] : 0) << 16) + ((length > 1 ? x[1] : 0) << 8) + (length > 2 ? x[2] : 0);
785 else
786 oui = ((length > 2 ? x[2] : 0) << 16) + ((length > 1 ? x[1] : 0) << 8) + (length > 0 ? x[0] : 0);
788 buf = ouitohex(oui);
789 if (length < 3) {
790 sprintf(ascii, "?"); // some characters are null
791 if (ouinum) *ouinum = 0; // doesn't match a known OUI
792 } else {
793 valid_ascii = (x[0] >= 'A' && x[1] >= 'A' && x[2] >= 'A' && x[0] <= 'Z' && x[1] <= 'Z' && x[2] <= 'Z');
794 sprintf(ascii, "%c%c%c", x[0], x[1], x[2]);
796 ouiname = oui_name(oui, ouinum);
797 if (!ouiname) {
798 big_endian = !big_endian;
799 unsigned reversedoui = ((oui & 0xff) << 16) + (oui & 0x00ff00) + (oui >> 16);
800 ouiname = oui_name(reversedoui, ouinum);
801 if (ouiname) {
802 oui = reversedoui;
803 buf = ouitohex(oui);
804 matched_reverse = true;
805 } else if (do_ascii && valid_ascii) {
806 unsigned asciioui = (x[0] << 24) + (x[1] << 16) + (x[2] << 8);
807 ouiname = oui_name(asciioui, ouinum);
808 if (ouiname) {
809 matched_ascii = true;
815 std::string name;
816 if (ouiname) {
817 if (matched_ascii)
818 name = block_name + " (" + ouiname + ")" + ", PNP ID '" + ascii + "'";
819 else
820 name = block_name + " (" + ouiname + ")" + ", OUI " + buf;
821 } else if (do_ascii && valid_ascii) {
822 name = block_name + ", PNP ID '" + ascii + "'";
823 } else {
824 name = block_name + ", OUI " + buf;
826 // assign string to data_block before outputting errors
827 data_block = name;
829 if (oui || !ignorezeros) {
830 if (!silent)
831 printf(" %s:\n", data_block.c_str());
832 if (length < 3)
833 fail("Data block length (%d) is not enough to contain an OUI.\n", length);
834 else if (ouiname) {
835 if (do_ascii && !valid_ascii)
836 warn("Expected PNP ID but found OUI.\n");
837 if (matched_reverse)
838 fail("Endian-ness (%s) of OUI is different than expected (%s).\n", big_endian ? "be" : "le", big_endian ? "le" : "be");
840 else {
841 if (valid_ascii)
842 warn("Unknown OUI %s (possible PNP %s).\n", buf.c_str(), ascii);
843 else
844 warn("Unknown OUI %s.\n", buf.c_str());
849 std::string ouitohex(unsigned oui)
851 char buf[32];
853 sprintf(buf, "%02X-%02X-%02X", (oui >> 16) & 0xff, (oui >> 8) & 0xff, oui & 0xff);
854 return buf;
857 bool memchk(const unsigned char *x, unsigned len, unsigned char v)
859 for (unsigned i = 0; i < len; i++)
860 if (x[i] != v)
861 return false;
862 return true;
865 void hex_block(const char *prefix, const unsigned char *x,
866 unsigned length, bool show_ascii, unsigned step)
868 unsigned i, j;
870 for (i = 0; i < length; i += step) {
871 unsigned len = min(step, length - i);
873 printf("%s", prefix);
874 for (j = 0; j < len; j++)
875 printf("%s%02x", j ? " " : "", x[i + j]);
877 if (show_ascii) {
878 for (j = len; j < step; j++)
879 printf(" ");
880 printf(" '");
881 for (j = 0; j < len; j++)
882 printf("%c", x[i + j] >= ' ' && x[i + j] <= '~' ? x[i + j] : '.');
883 printf("'");
885 printf("\n");
889 static bool edid_add_byte(const char *s, bool two_digits = true)
891 char buf[3];
893 if (state.edid_size == sizeof(edid))
894 return false;
895 buf[0] = s[0];
896 buf[1] = two_digits ? s[1] : 0;
897 buf[2] = 0;
898 edid[state.edid_size++] = strtoul(buf, NULL, 16);
899 return true;
902 static bool extract_edid_quantumdata(const char *start)
904 /* Parse QuantumData 980 EDID files */
905 do {
906 start = strstr(start, ">");
907 if (!start)
908 return false;
909 start++;
910 for (unsigned i = 0; start[i] && start[i + 1] && i < 256; i += 2)
911 if (!edid_add_byte(start + i))
912 return false;
913 start = strstr(start, "<BLOCK");
914 } while (start);
915 return state.edid_size;
918 static const char *ignore_chars = ",:;";
920 static bool extract_edid_hex(const char *s, bool require_two_digits = true)
922 for (; *s; s++) {
923 if (isspace(*s) || strchr(ignore_chars, *s))
924 continue;
926 if (*s == '0' && tolower(s[1]) == 'x') {
927 s++;
928 continue;
931 /* Read one or two hex digits from the log */
932 if (!isxdigit(s[0])) {
933 if (state.edid_size && state.edid_size % 128 == 0)
934 break;
935 return false;
937 if (require_two_digits && !isxdigit(s[1])) {
938 odd_hex_digits = true;
939 return false;
941 if (!edid_add_byte(s, isxdigit(s[1])))
942 return false;
943 if (isxdigit(s[1]))
944 s++;
946 return state.edid_size;
949 static bool extract_edid_xrandr(const char *start)
951 static const char indentation1[] = " ";
952 static const char indentation2[] = "\t\t";
953 /* Used to detect that we've gone past the EDID property */
954 static const char half_indentation1[] = " ";
955 static const char half_indentation2[] = "\t";
956 const char *indentation;
957 const char *s;
959 for (;;) {
960 unsigned j;
962 /* Get the next start of the line of EDID hex, assuming spaces for indentation */
963 s = strstr(start, indentation = indentation1);
964 /* Did we skip the start of another property? */
965 if (s && s > strstr(start, half_indentation1))
966 break;
968 /* If we failed, retry assuming tabs for indentation */
969 if (!s) {
970 s = strstr(start, indentation = indentation2);
971 /* Did we skip the start of another property? */
972 if (s && s > strstr(start, half_indentation2))
973 break;
976 if (!s)
977 break;
979 start = s + strlen(indentation);
981 for (j = 0; j < 16; j++, start += 2) {
982 /* Read a %02x from the log */
983 if (!isxdigit(start[0]) || !isxdigit(start[1])) {
984 if (j)
985 break;
986 return false;
988 if (!edid_add_byte(start))
989 return false;
992 return state.edid_size;
995 static bool extract_edid_xorg(const char *start)
997 bool find_first_num = true;
999 for (; *start; start++) {
1000 if (find_first_num) {
1001 const char *s;
1003 /* skip ahead to the : */
1004 s = strstr(start, ": \t");
1005 if (!s)
1006 s = strstr(start, ": ");
1007 if (!s)
1008 break;
1009 start = s;
1010 /* and find the first number */
1011 while (!isxdigit(start[1]))
1012 start++;
1013 find_first_num = false;
1014 continue;
1015 } else {
1016 /* Read a %02x from the log */
1017 if (!isxdigit(*start)) {
1018 find_first_num = true;
1019 continue;
1021 if (!edid_add_byte(start))
1022 return false;
1023 start++;
1026 return state.edid_size;
1029 static bool extract_edid(int fd, FILE *error)
1031 std::vector<char> edid_data;
1032 char buf[EDID_PAGE_SIZE];
1034 for (;;) {
1035 ssize_t i = read(fd, buf, sizeof(buf));
1037 if (i < 0)
1038 return false;
1039 if (i == 0)
1040 break;
1041 edid_data.insert(edid_data.end(), buf, buf + i);
1044 if (edid_data.empty()) {
1045 state.edid_size = 0;
1046 return false;
1048 // Ensure it is safely terminated by a 0 char
1049 edid_data.push_back('\0');
1051 const char *data = &edid_data[0];
1052 const char *start;
1054 /* Look for edid-decode output */
1055 start = strstr(data, "EDID (hex):");
1056 if (!start)
1057 start = strstr(data, "edid-decode (hex):");
1058 if (start)
1059 return extract_edid_hex(strchr(start, ':'));
1061 /* Look for C-array */
1062 start = strstr(data, "unsigned char edid[] = {");
1063 if (start)
1064 return extract_edid_hex(strchr(start, '{') + 1, false);
1066 /* Look for QuantumData EDID output */
1067 start = strstr(data, "<BLOCK");
1068 if (start)
1069 return extract_edid_quantumdata(start);
1071 /* Look for xrandr --verbose output (lines of 16 hex bytes) */
1072 start = strstr(data, "EDID_DATA:");
1073 if (!start)
1074 start = strstr(data, "EDID:");
1075 if (start)
1076 return extract_edid_xrandr(start);
1078 /* Look for an EDID in an Xorg.0.log file */
1079 start = strstr(data, "EDID (in hex):");
1080 if (start)
1081 start = strstr(start, "(II)");
1082 if (start)
1083 return extract_edid_xorg(start);
1085 unsigned i;
1087 /* Is the EDID provided in hex? */
1088 for (i = 0; i < 32 && (isspace(data[i]) || strchr(ignore_chars, data[i]) ||
1089 tolower(data[i]) == 'x' || isxdigit(data[i])); i++);
1091 if (i == 32)
1092 return extract_edid_hex(data);
1094 // Drop the extra '\0' byte since we now assume binary data
1095 edid_data.pop_back();
1097 /* Assume binary */
1098 if (edid_data.size() > sizeof(edid)) {
1099 fprintf(error, "Binary EDID length %zu is greater than %zu.\n",
1100 edid_data.size(), sizeof(edid));
1101 return false;
1103 memcpy(edid, data, edid_data.size());
1104 state.edid_size = edid_data.size();
1105 return true;
1108 static int edid_from_file(const char *from_file, FILE *error)
1110 #ifdef O_BINARY
1111 // Windows compatibility
1112 int flags = O_RDONLY | O_BINARY;
1113 #else
1114 int flags = O_RDONLY;
1115 #endif
1116 int fd;
1118 if (!strcmp(from_file, "-")) {
1119 from_file = "stdin";
1120 fd = 0;
1121 } else if ((fd = open(from_file, flags)) == -1) {
1122 perror(from_file);
1123 return -1;
1126 odd_hex_digits = false;
1127 if (!extract_edid(fd, error)) {
1128 if (!state.edid_size) {
1129 fprintf(error, "EDID of '%s' was empty.\n", from_file);
1130 return -1;
1132 fprintf(error, "EDID extract of '%s' failed: ", from_file);
1133 if (odd_hex_digits)
1134 fprintf(error, "odd number of hexadecimal digits.\n");
1135 else
1136 fprintf(error, "unknown format.\n");
1137 return -1;
1139 if (state.edid_size % EDID_PAGE_SIZE) {
1140 fprintf(error, "EDID length %u is not a multiple of %u.\n",
1141 state.edid_size, EDID_PAGE_SIZE);
1142 return -1;
1144 state.num_blocks = state.edid_size / EDID_PAGE_SIZE;
1145 if (fd != 0)
1146 close(fd);
1148 if (memcmp(edid, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", 8)) {
1149 fprintf(error, "No EDID header found in '%s'.\n", from_file);
1150 return -1;
1152 return 0;
1155 /* generic extension code */
1157 std::string block_name(unsigned char block)
1159 char buf[10];
1161 switch (block) {
1162 case 0x00: return "Base EDID";
1163 case 0x02: return "CTA-861 Extension Block";
1164 case 0x10: return "Video Timing Extension Block";
1165 case 0x20: return "EDID 2.0 Extension Block";
1166 case 0x40: return "Display Information Extension Block";
1167 case 0x50: return "Localized String Extension Block";
1168 case 0x60: return "Microdisplay Interface Extension Block";
1169 case 0x70: return "DisplayID Extension Block";
1170 case 0xf0: return "Block Map Extension Block";
1171 case 0xff: return "Manufacturer-Specific Extension Block";
1172 default:
1173 sprintf(buf, " 0x%02x", block);
1174 return std::string("Unknown EDID Extension Block") + buf;
1178 void edid_state::parse_block_map(const unsigned char *x)
1180 unsigned last_valid_block_tag = 0;
1181 bool fail_once = false;
1182 unsigned offset = 1;
1183 unsigned i;
1185 if (block_nr == 1)
1186 block_map.saw_block_1 = true;
1187 else if (!block_map.saw_block_1)
1188 fail("No EDID Block Map Extension found in block 1.\n");
1189 else if (block_nr == 128)
1190 block_map.saw_block_128 = true;
1192 if (block_nr > 1)
1193 offset = 128;
1195 for (i = 1; i < 127; i++) {
1196 unsigned block = offset + i;
1198 if (x[i]) {
1199 last_valid_block_tag++;
1200 if (i != last_valid_block_tag && !fail_once) {
1201 fail("Valid block tags are not consecutive.\n");
1202 fail_once = true;
1204 printf(" Block %3u: %s\n", block, block_name(x[i]).c_str());
1205 if (block >= num_blocks) {
1206 if (!fail_once)
1207 fail("Invalid block number %u.\n", block);
1208 fail_once = true;
1209 } else if (x[i] != edid[block * EDID_PAGE_SIZE]) {
1210 fail("Block %u tag mismatch: expected 0x%02x, but got 0x%02x.\n",
1211 block, edid[block * EDID_PAGE_SIZE], x[i]);
1213 } else if (block < num_blocks) {
1214 fail("Block %u tag mismatch: expected 0x%02x, but got 0x00.\n",
1215 block, edid[block * EDID_PAGE_SIZE]);
1220 void edid_state::preparse_extension(unsigned char *x)
1222 switch (x[0]) {
1223 case 0x02:
1224 has_cta = true;
1225 preparse_cta_block(x);
1226 break;
1227 case 0x50:
1228 preparse_ls_ext_block(x);
1229 break;
1230 case 0x70:
1231 has_dispid = true;
1232 preparse_displayid_block(x);
1233 break;
1237 void edid_state::parse_extension(const unsigned char *x)
1239 block = block_name(x[0]);
1240 data_block.clear();
1241 unused_bytes = 0;
1243 printf("\n");
1244 if (block_nr && x[0] == 0)
1245 block = "Unknown EDID Extension Block 0x00";
1246 printf("Block %u, %s:\n", block_nr, block.c_str());
1248 switch (x[0]) {
1249 case 0x02:
1250 parse_cta_block(x);
1251 break;
1252 case 0x10:
1253 parse_vtb_ext_block(x);
1254 break;
1255 case 0x20:
1256 fail("Deprecated extension block for EDID 2.0, do not use.\n");
1257 break;
1258 case 0x40:
1259 parse_di_ext_block(x);
1260 break;
1261 case 0x50:
1262 parse_ls_ext_block(x);
1263 break;
1264 case 0x70:
1265 parse_displayid_block(x);
1266 break;
1267 case 0xf0:
1268 parse_block_map(x);
1269 if (block_nr != 1 && block_nr != 128)
1270 fail("Must be used in block 1 and 128.\n");
1271 break;
1272 default:
1273 hex_block(" ", x, EDID_PAGE_SIZE);
1274 fail("Unknown Extension Block.\n");
1275 break;
1278 data_block.clear();
1279 do_checksum("", x, EDID_PAGE_SIZE, EDID_PAGE_SIZE - 1, unused_bytes);
1282 void edid_state::print_preferred_timings()
1284 if (base.preferred_timing.is_valid()) {
1285 printf("\n----------------\n");
1286 printf("\nPreferred Video Timing if only Block 0 is parsed:\n");
1287 print_timings(" ", base.preferred_timing, true, false);
1290 if (!cta.preferred_timings.empty()) {
1291 printf("\n----------------\n");
1292 printf("\nPreferred Video Timing%s if Block 0 and CTA-861 Blocks are parsed:\n",
1293 cta.preferred_timings.size() > 1 ? "s" : "");
1294 for (vec_timings_ext::iterator iter = cta.preferred_timings.begin();
1295 iter != cta.preferred_timings.end(); ++iter)
1296 print_timings(" ", *iter, true, false);
1299 if (!cta.preferred_timings_vfpdb.empty()) {
1300 printf("\n----------------\n");
1301 printf("\nPreferred Video Timing%s if Block 0 and CTA-861 Blocks are parsed with VFPDB support:\n",
1302 cta.preferred_timings_vfpdb.size() > 1 ? "s" : "");
1303 for (vec_timings_ext::iterator iter = cta.preferred_timings_vfpdb.begin();
1304 iter != cta.preferred_timings_vfpdb.end(); ++iter)
1305 print_timings(" ", *iter, true, false);
1308 if (!dispid.preferred_timings.empty()) {
1309 printf("\n----------------\n");
1310 printf("\nPreferred Video Timing%s if Block 0 and DisplayID Blocks are parsed:\n",
1311 dispid.preferred_timings.size() > 1 ? "s" : "");
1312 for (vec_timings_ext::iterator iter = dispid.preferred_timings.begin();
1313 iter != dispid.preferred_timings.end(); ++iter)
1314 print_timings(" ", *iter, true, false);
1318 void edid_state::print_native_res()
1320 typedef std::pair<unsigned, unsigned> resolution;
1321 typedef std::set<resolution> resolution_set;
1322 resolution_set native_prog, native_int, native_nvrdb;
1323 unsigned native_width = 0, native_height = 0;
1324 unsigned native_width_int = 0, native_height_int = 0;
1326 // Note: it is also a mismatch if Block 0 does not define a
1327 // native resolution, but other blocks do.
1328 bool native_mismatch = false;
1329 bool native_int_mismatch = false;
1331 if (base.preferred_timing.is_valid() && base.preferred_is_also_native) {
1332 if (base.preferred_timing.t.interlaced) {
1333 native_width_int = base.preferred_timing.t.hact;
1334 native_height_int = base.preferred_timing.t.vact;
1335 } else {
1336 native_width = base.preferred_timing.t.hact;
1337 native_height = base.preferred_timing.t.vact;
1341 if (!native_width && dispid.native_width) {
1342 native_width = dispid.native_width;
1343 native_height = dispid.native_height;
1344 native_mismatch = true;
1345 } else if (dispid.native_width && native_width &&
1346 (dispid.native_width != native_width ||
1347 dispid.native_height != native_height)) {
1348 native_mismatch = true;
1351 for (vec_timings_ext::iterator iter = cta.native_timings.begin();
1352 iter != cta.native_timings.end(); ++iter) {
1353 if (iter->t.interlaced) {
1354 native_int.insert(std::pair<unsigned, unsigned>(iter->t.hact, iter->t.vact));
1355 if (!native_width_int) {
1356 native_width_int = iter->t.hact;
1357 native_height_int = iter->t.vact;
1358 native_int_mismatch = true;
1359 } else if (native_width_int &&
1360 (iter->t.hact != native_width_int ||
1361 iter->t.vact != native_height_int)) {
1362 native_int_mismatch = true;
1364 } else {
1365 native_prog.insert(std::pair<unsigned, unsigned>(iter->t.hact, iter->t.vact));
1366 if (!native_width) {
1367 native_width = iter->t.hact;
1368 native_height = iter->t.vact;
1369 native_mismatch = true;
1370 } else if (native_width &&
1371 (iter->t.hact != native_width ||
1372 iter->t.vact != native_height)) {
1373 native_mismatch = true;
1378 for (vec_timings_ext::iterator iter = cta.native_timing_nvrdb.begin();
1379 iter != cta.native_timing_nvrdb.end(); ++iter) {
1380 if (iter->t.interlaced) {
1381 fail("Interlaced native timing in NVRDB.\n");
1382 } else {
1383 native_nvrdb.insert(std::pair<unsigned, unsigned>(iter->t.hact, iter->t.vact));
1384 if (!native_width) {
1385 native_width = iter->t.hact;
1386 native_height = iter->t.vact;
1387 native_mismatch = true;
1388 } else if (native_width &&
1389 (iter->t.hact != native_width ||
1390 iter->t.vact != native_height)) {
1391 native_mismatch = true;
1396 if (diagonal) {
1397 if (image_width) {
1398 double w = image_width;
1399 double h = image_height;
1400 double d = sqrt(w * w + h * h) / 254.0;
1402 if (fabs(diagonal - d) >= 0.1)
1403 warn("Specified diagonal is %.1f\", calculated diagonal is %.1f\".\n",
1404 diagonal, d);
1406 if (native_width) {
1407 double w = native_width;
1408 double h = native_height;
1409 double d = diagonal * 254.0;
1410 double c = sqrt((d * d) / (w * w + h * h));
1412 w *= c;
1413 h *= c;
1415 if (image_width) {
1416 printf("\n----------------\n");
1417 printf("\nCalculated image size for a diagonal of %.1f\" is %.1fx%.1fmm.\n",
1418 diagonal, w / 10.0, h / 10.0);
1420 if (fabs((double)image_width - w) >= 100.0 ||
1421 fabs((double)image_height - h) >= 100.0)
1422 warn("Calculated image size is %.1fx%.1fmm, EDID image size is %.1fx%.1fmm.\n",
1423 w / 10.0, h / 10.0,
1424 image_width / 10.0, image_height / 10.0);
1425 } else {
1426 warn("No image size was specified, but it is calculated as %.1fx%.1fmm.\n",
1427 w / 10.0, h / 10.0);
1432 if (!options[OptNativeResolution])
1433 return;
1435 if (native_width == 0 && native_width_int == 0) {
1436 printf("\n----------------\n");
1437 printf("\nNo Native Video Resolution was defined.\n");
1438 return;
1441 if ((native_width || native_width_int) &&
1442 !native_mismatch && !native_int_mismatch) {
1443 printf("\n----------------\n");
1444 printf("\nNative Video Resolution%s:\n",
1445 native_width && native_width_int ? "s" : "");
1446 if (native_width)
1447 printf(" %ux%u\n", native_width, native_height);
1448 if (native_width_int)
1449 printf(" %ux%ui\n", native_width_int, native_height_int);
1450 return;
1453 if (base.preferred_timing.is_valid() && base.preferred_is_also_native) {
1454 printf("\n----------------\n");
1455 printf("\nNative Video Resolution if only Block 0 is parsed:\n");
1456 printf(" %ux%u%s\n",
1457 base.preferred_timing.t.hact, base.preferred_timing.t.vact,
1458 base.preferred_timing.t.interlaced ? "i" : "");
1461 if (!cta.native_timings.empty()) {
1462 printf("\n----------------\n");
1463 printf("\nNative Video Resolution%s if Block 0 and CTA-861 Blocks are parsed:\n",
1464 native_prog.size() + native_int.size() > 1 ? "s" : "");
1465 for (resolution_set::iterator iter = native_prog.begin();
1466 iter != native_prog.end(); ++iter)
1467 printf(" %ux%u\n", iter->first, iter->second);
1468 for (resolution_set::iterator iter = native_int.begin();
1469 iter != native_int.end(); ++iter)
1470 printf(" %ux%ui\n", iter->first, iter->second);
1473 if (!cta.native_timing_nvrdb.empty()) {
1474 printf("\n----------------\n");
1475 printf("\nNative Video Resolution if Block 0 and CTA-861 Blocks are parsed with NVRDB support:\n");
1476 for (resolution_set::iterator iter = native_nvrdb.begin();
1477 iter != native_nvrdb.end(); ++iter)
1478 printf(" %ux%u\n", iter->first, iter->second);
1481 if (dispid.native_width) {
1482 printf("\n----------------\n");
1483 printf("\nNative Video Resolution if the DisplayID Blocks are parsed:\n");
1484 printf(" %ux%u\n", dispid.native_width, dispid.native_height);
1488 int edid_state::parse_edid()
1490 hide_serial_numbers = options[OptHideSerialNumbers];
1491 replace_unique_ids = options[OptReplaceUniqueIDs];
1493 to_utf8 = options[OptUTF8];
1495 preparse_base_block(edid);
1496 if (replace_unique_ids)
1497 replace_checksum(edid, EDID_PAGE_SIZE);
1499 for (unsigned i = 1; i < num_blocks; i++)
1500 preparse_extension(edid + i * EDID_PAGE_SIZE);
1502 if (options[OptPhysicalAddress]) {
1503 printf("%x.%x.%x.%x\n",
1504 (cta.preparsed_phys_addr >> 12) & 0xf,
1505 (cta.preparsed_phys_addr >> 8) & 0xf,
1506 (cta.preparsed_phys_addr >> 4) & 0xf,
1507 cta.preparsed_phys_addr & 0xf);
1508 return 0;
1511 if (!options[OptSkipHexDump]) {
1512 printf("edid-decode (hex):\n\n");
1513 for (unsigned i = 0; i < num_blocks; i++) {
1514 hex_block("", edid + i * EDID_PAGE_SIZE, EDID_PAGE_SIZE, false);
1515 if (i == num_blocks - 1 && options[OptOnlyHexDump])
1516 return 0;
1517 printf("\n");
1519 printf("----------------\n\n");
1522 block = block_name(0x00);
1523 printf("Block %u, %s:\n", block_nr, block.c_str());
1524 parse_base_block(edid);
1526 for (unsigned i = 1; i < num_blocks; i++) {
1527 block_nr++;
1528 printf("\n----------------\n");
1529 parse_extension(edid + i * EDID_PAGE_SIZE);
1532 block = "";
1533 block_nr = EDID_MAX_BLOCKS;
1535 if (cta.has_svrs)
1536 cta_resolve_svrs();
1538 if (options[OptPreferredTimings])
1539 print_preferred_timings();
1541 print_native_res();
1543 if (!options[OptCheck] && !options[OptCheckInline])
1544 return 0;
1546 check_base_block(edid);
1547 if (has_cta)
1548 check_cta_blocks();
1549 if (has_dispid)
1550 check_displayid_blocks();
1552 printf("\n----------------\n");
1554 if (!options[OptSkipSHA] && strlen(STRING(SHA))) {
1555 options[OptSkipSHA] = 1;
1556 printf("\nedid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
1559 if (options[OptCheck]) {
1560 if (warnings)
1561 show_msgs(true);
1562 if (failures)
1563 show_msgs(false);
1565 printf("\nEDID conformity: %s\n", failures ? "FAIL" : "PASS");
1566 return failures ? -2 : 0;
1569 /* InfoFrame parsing */
1571 static unsigned char infoframe[32];
1572 static unsigned if_size;
1574 static bool if_add_byte(const char *s)
1576 char buf[3];
1578 if (if_size == sizeof(infoframe))
1579 return false;
1580 buf[0] = s[0];
1581 buf[1] = s[1];
1582 buf[2] = 0;
1583 infoframe[if_size++] = strtoul(buf, NULL, 16);
1584 return true;
1587 static bool extract_if_hex(const char *s)
1589 for (; *s; s++) {
1590 if (isspace(*s))
1591 continue;
1593 /* Read one or two hex digits from the log */
1594 if (!isxdigit(s[0]))
1595 break;
1597 if (!isxdigit(s[1])) {
1598 odd_hex_digits = true;
1599 return false;
1601 if (!if_add_byte(s))
1602 return false;
1603 s++;
1605 return if_size;
1608 static bool extract_if(int fd)
1610 std::vector<char> if_data;
1611 char buf[128];
1613 for (;;) {
1614 ssize_t i = read(fd, buf, sizeof(buf));
1616 if (i < 0)
1617 return false;
1618 if (i == 0)
1619 break;
1620 if_data.insert(if_data.end(), buf, buf + i);
1623 if (if_data.empty()) {
1624 if_size = 0;
1625 return false;
1627 // Ensure it is safely terminated by a 0 char
1628 if_data.push_back('\0');
1630 const char *data = &if_data[0];
1631 const char *start;
1633 /* Look for edid-decode output */
1634 start = strstr(data, "edid-decode InfoFrame (hex):");
1635 if (start)
1636 return extract_if_hex(strchr(start, ':') + 1);
1638 // Drop the extra '\0' byte since we now assume binary data
1639 if_data.pop_back();
1641 if_size = if_data.size();
1643 /* Assume binary */
1644 if (if_size > sizeof(infoframe)) {
1645 fprintf(stderr, "Binary InfoFrame length %u is greater than %zu.\n",
1646 if_size, sizeof(infoframe));
1647 return false;
1649 memcpy(infoframe, data, if_size);
1650 return true;
1653 static int if_from_file(const char *from_file)
1655 #ifdef O_BINARY
1656 // Windows compatibility
1657 int flags = O_RDONLY | O_BINARY;
1658 #else
1659 int flags = O_RDONLY;
1660 #endif
1661 int fd;
1663 memset(infoframe, 0, sizeof(infoframe));
1664 if_size = 0;
1666 if ((fd = open(from_file, flags)) == -1) {
1667 perror(from_file);
1668 return -1;
1671 odd_hex_digits = false;
1672 if (!extract_if(fd)) {
1673 if (!if_size) {
1674 fprintf(stderr, "InfoFrame of '%s' was empty.\n", from_file);
1675 return -1;
1677 fprintf(stderr, "InfoFrame extraction of '%s' failed: ", from_file);
1678 if (odd_hex_digits)
1679 fprintf(stderr, "odd number of hexadecimal digits.\n");
1680 else
1681 fprintf(stderr, "unknown format.\n");
1682 return -1;
1684 close(fd);
1686 return 0;
1689 static void show_if_msgs(bool is_warn)
1691 printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures");
1692 if (s_msgs[0][is_warn].empty())
1693 return;
1694 printf("InfoFrame:\n%s",
1695 s_msgs[0][is_warn].c_str());
1698 int edid_state::parse_if(const std::string &fname)
1700 int ret = if_from_file(fname.c_str());
1701 unsigned min_size = 4;
1702 bool is_hdmi = false;
1704 if (ret)
1705 return ret;
1707 state.block_nr = 0;
1708 state.data_block.clear();
1710 if (!options[OptSkipHexDump]) {
1711 printf("edid-decode InfoFrame (hex):\n\n");
1712 hex_block("", infoframe, if_size, false);
1713 if (options[OptOnlyHexDump])
1714 return 0;
1715 printf("\n----------------\n\n");
1718 if (infoframe[0] >= 0x80) {
1719 is_hdmi = true;
1720 min_size++;
1723 if (if_size < min_size) {
1724 fail("InfoFrame is too small to parse.\n");
1725 return -1;
1728 if (is_hdmi) {
1729 do_checksum("HDMI InfoFrame ", infoframe, if_size, 3);
1730 printf("\n");
1731 memcpy(infoframe + 3, infoframe + 4, if_size - 4);
1732 infoframe[0] &= 0x7f;
1733 if_size--;
1736 switch (infoframe[0]) {
1737 case 0x01:
1738 parse_if_vendor(infoframe, if_size);
1739 break;
1740 case 0x02:
1741 parse_if_avi(infoframe, if_size);
1742 break;
1743 case 0x03:
1744 parse_if_spd(infoframe, if_size);
1745 break;
1746 case 0x04:
1747 parse_if_audio(infoframe, if_size);
1748 break;
1749 case 0x05:
1750 parse_if_mpeg_source(infoframe, if_size);
1751 break;
1752 case 0x06:
1753 parse_if_ntsc_vbi(infoframe, if_size);
1754 break;
1755 case 0x07:
1756 parse_if_drm(infoframe, if_size);
1757 break;
1758 default:
1759 if (infoframe[0] <= 0x1f)
1760 fail("Reserved InfoFrame type %hhx.\n", infoframe[0]);
1761 else
1762 fail("Forbidden InfoFrame type %hhx.\n", infoframe[0]);
1763 break;
1766 if (!options[OptCheck] && !options[OptCheckInline])
1767 return 0;
1769 printf("\n----------------\n");
1771 if (!options[OptSkipSHA] && strlen(STRING(SHA))) {
1772 options[OptSkipSHA] = 1;
1773 printf("\nedid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
1776 if (options[OptCheck]) {
1777 if (warnings)
1778 show_if_msgs(true);
1779 if (failures)
1780 show_if_msgs(false);
1783 printf("\n%s conformity: %s\n",
1784 state.data_block.empty() ? "InfoFrame" : state.data_block.c_str(),
1785 failures ? "FAIL" : "PASS");
1786 return failures ? -2 : 0;
1789 #ifndef __EMSCRIPTEN__
1791 static unsigned char crc_calc(const unsigned char *b)
1793 unsigned char sum = 0;
1794 unsigned i;
1796 for (i = 0; i < 127; i++)
1797 sum += b[i];
1798 return 256 - sum;
1801 static int crc_ok(const unsigned char *b)
1803 return crc_calc(b) == b[127];
1806 static void hexdumpedid(FILE *f, const unsigned char *edid, unsigned size)
1808 unsigned b, i, j;
1810 for (b = 0; b < size / 128; b++) {
1811 const unsigned char *buf = edid + 128 * b;
1813 if (b)
1814 fprintf(f, "\n");
1815 for (i = 0; i < 128; i += 0x10) {
1816 fprintf(f, "%02x", buf[i]);
1817 for (j = 1; j < 0x10; j++) {
1818 fprintf(f, " %02x", buf[i + j]);
1820 fprintf(f, "\n");
1822 if (!crc_ok(buf))
1823 fprintf(f, "Block %u has a checksum error (should be 0x%02x).\n",
1824 b, crc_calc(buf));
1828 static void carraydumpedid(FILE *f, const unsigned char *edid, unsigned size)
1830 unsigned b, i, j;
1832 fprintf(f, "const unsigned char edid[] = {\n");
1833 for (b = 0; b < size / 128; b++) {
1834 const unsigned char *buf = edid + 128 * b;
1836 if (b)
1837 fprintf(f, "\n");
1838 for (i = 0; i < 128; i += 8) {
1839 fprintf(f, "\t0x%02x,", buf[i]);
1840 for (j = 1; j < 8; j++) {
1841 fprintf(f, " 0x%02x,", buf[i + j]);
1843 fprintf(f, "\n");
1845 if (!crc_ok(buf))
1846 fprintf(f, "\t/* Block %u has a checksum error (should be 0x%02x). */\n",
1847 b, crc_calc(buf));
1849 fprintf(f, "};\n");
1852 // This format can be read by the QuantumData EDID editor
1853 static void xmldumpedid(FILE *f, const unsigned char *edid, unsigned size)
1855 fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
1856 fprintf(f, "<DATAOBJ>\n");
1857 fprintf(f, " <HEADER TYPE=\"DID\" VERSION=\"1.0\"/>\n");
1858 fprintf(f, " <DATA>\n");
1859 for (unsigned b = 0; b < size / 128; b++) {
1860 const unsigned char *buf = edid + 128 * b;
1862 fprintf(f, " <BLOCK%u>", b);
1863 for (unsigned i = 0; i < 128; i++)
1864 fprintf(f, "%02X", buf[i]);
1865 fprintf(f, "</BLOCK%u>\n", b);
1867 fprintf(f, " </DATA>\n");
1868 fprintf(f, "</DATAOBJ>\n");
1871 static int edid_to_file(const char *to_file, enum output_format out_fmt)
1873 FILE *out;
1875 if (!strcmp(to_file, "-")) {
1876 to_file = "stdout";
1877 out = stdout;
1878 } else if ((out = fopen(to_file, "w")) == NULL) {
1879 perror(to_file);
1880 return -1;
1882 if (out_fmt == OUT_FMT_DEFAULT)
1883 out_fmt = out == stdout ? OUT_FMT_HEX : OUT_FMT_RAW;
1885 switch (out_fmt) {
1886 default:
1887 case OUT_FMT_HEX:
1888 hexdumpedid(out, edid, state.edid_size);
1889 break;
1890 case OUT_FMT_RAW:
1891 fwrite(edid, state.edid_size, 1, out);
1892 break;
1893 case OUT_FMT_CARRAY:
1894 carraydumpedid(out, edid, state.edid_size);
1895 break;
1896 case OUT_FMT_XML:
1897 xmldumpedid(out, edid, state.edid_size);
1898 break;
1901 if (out != stdout)
1902 fclose(out);
1903 return 0;
1906 enum cvt_opts {
1907 CVT_WIDTH = 0,
1908 CVT_HEIGHT,
1909 CVT_FPS,
1910 CVT_INTERLACED,
1911 CVT_OVERSCAN,
1912 CVT_RB,
1913 CVT_ALT,
1914 CVT_RB_H_BLANK,
1915 CVT_RB_V_BLANK,
1916 CVT_EARLY_VSYNC,
1919 static int parse_cvt_subopt(char **subopt_str, double *value)
1921 int opt;
1922 char *opt_str;
1924 static const char * const subopt_list[] = {
1925 "w",
1926 "h",
1927 "fps",
1928 "interlaced",
1929 "overscan",
1930 "rb",
1931 "alt",
1932 "hblank",
1933 "vblank",
1934 "early-vsync",
1935 nullptr
1938 opt = getsubopt(subopt_str, (char* const*) subopt_list, &opt_str);
1940 if (opt == -1) {
1941 fprintf(stderr, "Invalid suboptions specified.\n");
1942 usage();
1943 std::exit(EXIT_FAILURE);
1945 if (opt_str == nullptr && opt != CVT_INTERLACED && opt != CVT_ALT &&
1946 opt != CVT_OVERSCAN && opt != CVT_EARLY_VSYNC) {
1947 fprintf(stderr, "No value given to suboption <%s>.\n",
1948 subopt_list[opt]);
1949 usage();
1950 std::exit(EXIT_FAILURE);
1953 if (opt_str)
1954 *value = strtod(opt_str, nullptr);
1955 return opt;
1958 static void parse_cvt(char *optarg)
1960 unsigned w = 0, h = 0;
1961 double fps = 0;
1962 unsigned rb = RB_NONE;
1963 unsigned rb_h_blank = 0;
1964 unsigned rb_v_blank = 460;
1965 bool interlaced = false;
1966 bool alt = false;
1967 bool overscan = false;
1968 bool early_vsync = false;
1970 while (*optarg != '\0') {
1971 int opt;
1972 double opt_val;
1974 opt = parse_cvt_subopt(&optarg, &opt_val);
1976 switch (opt) {
1977 case CVT_WIDTH:
1978 w = round(opt_val);
1979 break;
1980 case CVT_HEIGHT:
1981 h = round(opt_val);
1982 break;
1983 case CVT_FPS:
1984 fps = opt_val;
1985 break;
1986 case CVT_RB:
1987 rb = opt_val;
1988 break;
1989 case CVT_OVERSCAN:
1990 overscan = true;
1991 break;
1992 case CVT_INTERLACED:
1993 interlaced = opt_val;
1994 break;
1995 case CVT_ALT:
1996 alt = opt_val;
1997 break;
1998 case CVT_RB_H_BLANK:
1999 rb_h_blank = opt_val;
2000 break;
2001 case CVT_RB_V_BLANK:
2002 rb_v_blank = opt_val;
2003 if (rb_v_blank < 460) {
2004 fprintf(stderr, "vblank must be >= 460, set to 460.\n");
2005 rb_v_blank = 460;
2006 } else if (rb_v_blank > 705) {
2007 fprintf(stderr, "warning: vblank values > 705 might not be supported by RBv3 compliant sources.\n");
2009 break;
2010 case CVT_EARLY_VSYNC:
2011 early_vsync = true;
2012 break;
2013 default:
2014 break;
2018 if (!w || !h || !fps) {
2019 fprintf(stderr, "Missing width, height and/or fps.\n");
2020 usage();
2021 std::exit(EXIT_FAILURE);
2023 if (interlaced)
2024 fps /= 2;
2025 timings t = state.calc_cvt_mode(w, h, fps, rb, interlaced, overscan, alt,
2026 rb_h_blank, rb_v_blank, early_vsync);
2027 state.print_timings("", &t, "CVT", "", true, false);
2030 struct gtf_parsed_data {
2031 unsigned w, h;
2032 double freq;
2033 double C, M, K, J;
2034 bool overscan;
2035 bool interlaced;
2036 bool secondary;
2037 bool params_from_edid;
2038 enum gtf_ip_parm ip_parm;
2041 enum gtf_opts {
2042 GTF_WIDTH = 0,
2043 GTF_HEIGHT,
2044 GTF_FPS,
2045 GTF_HORFREQ,
2046 GTF_PIXCLK,
2047 GTF_INTERLACED,
2048 GTF_OVERSCAN,
2049 GTF_SECONDARY,
2050 GTF_C2,
2051 GTF_M,
2052 GTF_K,
2053 GTF_J2,
2056 static int parse_gtf_subopt(char **subopt_str, double *value)
2058 int opt;
2059 char *opt_str;
2061 static const char * const subopt_list[] = {
2062 "w",
2063 "h",
2064 "fps",
2065 "horfreq",
2066 "pixclk",
2067 "interlaced",
2068 "overscan",
2069 "secondary",
2070 "C",
2071 "M",
2072 "K",
2073 "J",
2074 nullptr
2077 opt = getsubopt(subopt_str, (char * const *)subopt_list, &opt_str);
2079 if (opt == -1) {
2080 fprintf(stderr, "Invalid suboptions specified.\n");
2081 usage();
2082 std::exit(EXIT_FAILURE);
2084 if (opt_str == nullptr && opt != GTF_INTERLACED && opt != GTF_OVERSCAN &&
2085 opt != GTF_SECONDARY) {
2086 fprintf(stderr, "No value given to suboption <%s>.\n",
2087 subopt_list[opt]);
2088 usage();
2089 std::exit(EXIT_FAILURE);
2092 if (opt == GTF_C2 || opt == GTF_J2)
2093 *value = round(2.0 * strtod(opt_str, nullptr));
2094 else if (opt_str)
2095 *value = strtod(opt_str, nullptr);
2096 return opt;
2099 static void parse_gtf(char *optarg, gtf_parsed_data &data)
2101 memset(&data, 0, sizeof(data));
2102 data.params_from_edid = true;
2103 data.C = 40;
2104 data.M = 600;
2105 data.K = 128;
2106 data.J = 20;
2108 while (*optarg != '\0') {
2109 int opt;
2110 double opt_val;
2112 opt = parse_gtf_subopt(&optarg, &opt_val);
2114 switch (opt) {
2115 case GTF_WIDTH:
2116 data.w = round(opt_val);
2117 break;
2118 case GTF_HEIGHT:
2119 data.h = round(opt_val);
2120 break;
2121 case GTF_FPS:
2122 data.freq = opt_val;
2123 data.ip_parm = gtf_ip_vert_freq;
2124 break;
2125 case GTF_HORFREQ:
2126 data.freq = opt_val;
2127 data.ip_parm = gtf_ip_hor_freq;
2128 break;
2129 case GTF_PIXCLK:
2130 data.freq = opt_val;
2131 data.ip_parm = gtf_ip_clk_freq;
2132 break;
2133 case GTF_INTERLACED:
2134 data.interlaced = true;
2135 break;
2136 case GTF_OVERSCAN:
2137 data.overscan = true;
2138 break;
2139 case GTF_SECONDARY:
2140 data.secondary = true;
2141 break;
2142 case GTF_C2:
2143 data.C = opt_val / 2.0;
2144 data.params_from_edid = false;
2145 break;
2146 case GTF_M:
2147 data.M = round(opt_val);
2148 data.params_from_edid = false;
2149 break;
2150 case GTF_K:
2151 data.K = round(opt_val);
2152 data.params_from_edid = false;
2153 break;
2154 case GTF_J2:
2155 data.J = opt_val / 2.0;
2156 data.params_from_edid = false;
2157 break;
2158 default:
2159 break;
2163 if (!data.w || !data.h) {
2164 fprintf(stderr, "Missing width and/or height.\n");
2165 usage();
2166 std::exit(EXIT_FAILURE);
2168 if (!data.freq) {
2169 fprintf(stderr, "One of fps, horfreq or pixclk must be given.\n");
2170 usage();
2171 std::exit(EXIT_FAILURE);
2173 if (!data.secondary)
2174 data.params_from_edid = false;
2175 if (data.interlaced && data.ip_parm == gtf_ip_vert_freq)
2176 data.freq /= 2;
2179 static void show_gtf(gtf_parsed_data &data)
2181 timings t;
2183 t = state.calc_gtf_mode(data.w, data.h, data.freq, data.interlaced,
2184 data.ip_parm, data.overscan, data.secondary,
2185 data.C, data.M, data.K, data.J);
2186 calc_ratio(&t);
2187 state.print_timings("", &t, "GTF", "", true, false);
2190 enum ovt_opts {
2191 OVT_RID,
2192 OVT_WIDTH,
2193 OVT_HEIGHT,
2194 OVT_FPS,
2197 static int parse_ovt_subopt(char **subopt_str, unsigned *value)
2199 int opt;
2200 char *opt_str;
2202 static const char * const subopt_list[] = {
2203 "rid",
2204 "w",
2205 "h",
2206 "fps",
2207 nullptr
2210 opt = getsubopt(subopt_str, (char* const*) subopt_list, &opt_str);
2212 if (opt == -1) {
2213 fprintf(stderr, "Invalid suboptions specified.\n");
2214 usage();
2215 std::exit(EXIT_FAILURE);
2217 if (opt_str == nullptr) {
2218 fprintf(stderr, "No value given to suboption <%s>.\n",
2219 subopt_list[opt]);
2220 usage();
2221 std::exit(EXIT_FAILURE);
2224 if (opt_str)
2225 *value = strtoul(opt_str, NULL, 0);
2226 return opt;
2229 static void parse_ovt(char *optarg)
2231 unsigned rid = 0;
2232 unsigned w = 0, h = 0;
2233 unsigned fps = 0;
2235 while (*optarg != '\0') {
2236 int opt;
2237 unsigned opt_val;
2239 opt = parse_ovt_subopt(&optarg, &opt_val);
2241 switch (opt) {
2242 case OVT_RID:
2243 rid = opt_val;
2244 break;
2245 case OVT_WIDTH:
2246 w = opt_val;
2247 break;
2248 case OVT_HEIGHT:
2249 h = opt_val;
2250 break;
2251 case OVT_FPS:
2252 fps = opt_val;
2253 break;
2254 default:
2255 break;
2259 if ((!rid && (!w || !h)) || !fps) {
2260 fprintf(stderr, "Missing rid, width, height and/or fps.\n");
2261 usage();
2262 std::exit(EXIT_FAILURE);
2264 unsigned hratio = 0, vratio = 0;
2265 if (rid) {
2266 const cta_rid *r = find_rid(rid);
2268 if (r) {
2269 w = r->hact;
2270 h = r->vact;
2271 hratio = r->hratio;
2272 vratio = r->vratio;
2275 timings t = state.calc_ovt_mode(w, h, hratio, vratio, fps);
2276 state.print_timings("", &t, "OVT", "", true, false);
2279 int main(int argc, char **argv)
2281 char short_options[26 * 2 * 2 + 1];
2282 enum output_format out_fmt = OUT_FMT_DEFAULT;
2283 gtf_parsed_data gtf_data;
2284 unsigned list_rid = 0;
2285 int adapter_fd = -1;
2286 double hdcp_ri_sleep = 0;
2287 std::vector<std::string> if_names;
2288 int ret;
2290 while (1) {
2291 int option_index = 0;
2292 unsigned idx = 0;
2293 unsigned i, val;
2294 const timings *t;
2295 char buf[16];
2297 for (i = 0; long_options[i].name; i++) {
2298 if (!isalpha(long_options[i].val))
2299 continue;
2300 short_options[idx++] = long_options[i].val;
2301 if (long_options[i].has_arg == required_argument)
2302 short_options[idx++] = ':';
2304 short_options[idx] = 0;
2305 int ch = getopt_long(argc, argv, short_options,
2306 long_options, &option_index);
2307 if (ch == -1)
2308 break;
2310 options[ch] = 1;
2311 switch (ch) {
2312 case OptHelp:
2313 usage();
2314 return -1;
2315 case OptOutputFormat:
2316 if (!strcmp(optarg, "hex")) {
2317 out_fmt = OUT_FMT_HEX;
2318 } else if (!strcmp(optarg, "raw")) {
2319 out_fmt = OUT_FMT_RAW;
2320 } else if (!strcmp(optarg, "carray")) {
2321 out_fmt = OUT_FMT_CARRAY;
2322 } else if (!strcmp(optarg, "xml")) {
2323 out_fmt = OUT_FMT_XML;
2324 } else {
2325 usage();
2326 exit(1);
2328 break;
2329 case OptDiag:
2330 state.diagonal = strtod(optarg, NULL);
2331 break;
2332 #ifndef __EMSCRIPTEN__
2333 case OptI2CAdapter: {
2334 unsigned num = strtoul(optarg, NULL, 0);
2336 adapter_fd = request_i2c_adapter(num);
2337 if (adapter_fd < 0)
2338 exit(1);
2339 break;
2341 #endif
2342 case OptI2CHDCPRi:
2343 hdcp_ri_sleep = strtod(optarg, NULL);
2344 break;
2345 case OptSTD: {
2346 unsigned char byte1, byte2 = 0;
2347 char *endptr;
2349 byte1 = strtoul(optarg, &endptr, 0);
2350 if (*endptr == ',')
2351 byte2 = strtoul(endptr + 1, NULL, 0);
2352 state.print_standard_timing("", byte1, byte2, false, true);
2353 break;
2355 case OptDMT:
2356 val = strtoul(optarg, NULL, 0);
2357 t = find_dmt_id(val);
2358 if (t) {
2359 sprintf(buf, "DMT 0x%02x", val);
2360 state.print_timings("", t, buf, "", true, false);
2361 } else {
2362 fprintf(stderr, "Unknown DMT code 0x%02x.\n", val);
2364 break;
2365 case OptVIC:
2366 val = strtoul(optarg, NULL, 0);
2367 t = find_vic_id(val);
2368 if (t) {
2369 sprintf(buf, "VIC %3u", val);
2370 state.print_timings("", t, buf, "", true, false);
2371 } else {
2372 fprintf(stderr, "Unknown VIC code %u.\n", val);
2374 break;
2375 case OptHDMIVIC:
2376 val = strtoul(optarg, NULL, 0);
2377 t = find_hdmi_vic_id(val);
2378 if (t) {
2379 sprintf(buf, "HDMI VIC %u", val);
2380 state.print_timings("", t, buf, "", true, false);
2381 } else {
2382 fprintf(stderr, "Unknown HDMI VIC code %u.\n", val);
2384 break;
2385 case OptCVT:
2386 parse_cvt(optarg);
2387 break;
2388 case OptGTF:
2389 parse_gtf(optarg, gtf_data);
2390 break;
2391 case OptOVT:
2392 parse_ovt(optarg);
2393 break;
2394 case OptListRIDTimings:
2395 list_rid = strtoul(optarg, NULL, 0);
2396 break;
2397 case OptInfoFrame:
2398 if_names.push_back(optarg);
2399 break;
2400 case ':':
2401 fprintf(stderr, "Option '%s' requires a value.\n",
2402 argv[optind]);
2403 usage();
2404 return -1;
2405 case '?':
2406 fprintf(stderr, "Unknown argument '%s'.\n",
2407 argv[optind]);
2408 usage();
2409 return -1;
2412 if (optind == argc && options[OptVersion]) {
2413 if (strlen(STRING(SHA)))
2414 printf("edid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
2415 else
2416 printf("edid-decode SHA: not available\n");
2417 return 0;
2420 if (options[OptListEstTimings])
2421 state.list_established_timings();
2422 if (options[OptListDMTs])
2423 state.list_dmts();
2424 if (options[OptListVICs])
2425 state.cta_list_vics();
2426 if (options[OptListHDMIVICs])
2427 state.cta_list_hdmi_vics();
2428 if (options[OptListRIDs])
2429 state.cta_list_rids();
2430 if (options[OptListRIDTimings])
2431 state.cta_list_rid_timings(list_rid);
2433 if (options[OptListEstTimings] || options[OptListDMTs] ||
2434 options[OptListVICs] || options[OptListHDMIVICs] ||
2435 options[OptListRIDs] || options[OptListRIDTimings])
2436 return 0;
2438 if (options[OptCVT] || options[OptDMT] || options[OptVIC] ||
2439 options[OptHDMIVIC] || options[OptSTD] || options[OptOVT])
2440 return 0;
2442 if (options[OptGTF] && (!gtf_data.params_from_edid || optind == argc)) {
2443 show_gtf(gtf_data);
2444 return 0;
2447 if (optind == argc) {
2448 if (adapter_fd >= 0 && options[OptI2CEDID]) {
2449 #ifndef __EMSCRIPTEN__
2450 ret = read_edid(adapter_fd, edid);
2451 #else
2452 ret = -ENODEV;
2453 #endif
2454 if (ret > 0) {
2455 state.edid_size = ret * EDID_PAGE_SIZE;
2456 state.num_blocks = ret;
2457 ret = 0;
2459 } else if (adapter_fd >= 0) {
2460 #ifndef __EMSCRIPTEN__
2461 if (options[OptI2CHDCP])
2462 read_hdcp(adapter_fd);
2463 if (options[OptI2CHDCPRi])
2464 read_hdcp_ri(adapter_fd, hdcp_ri_sleep);
2465 #else
2466 ret = -ENODEV;
2467 #endif
2468 ret = 0;
2469 } else if (options[OptInfoFrame] && !options[OptGTF]) {
2470 ret = 0;
2471 } else {
2472 ret = edid_from_file("-", stdout);
2474 } else {
2475 ret = edid_from_file(argv[optind], argv[optind + 1] ? stderr : stdout);
2478 if (ret && options[OptPhysicalAddress]) {
2479 printf("f.f.f.f\n");
2480 return 0;
2482 if (optind < argc - 1)
2483 return ret ? ret : edid_to_file(argv[optind + 1], out_fmt);
2485 if (options[OptGTF]) {
2486 timings t;
2488 state.preparse_base_block(edid);
2490 t = state.calc_gtf_mode(gtf_data.w, gtf_data.h, gtf_data.freq,
2491 gtf_data.interlaced, gtf_data.ip_parm,
2492 gtf_data.overscan);
2493 unsigned hbl = t.hfp + t.hsync + t.hbp;
2494 unsigned htotal = t.hact + hbl;
2495 double hor_freq_khz = htotal ? (double)t.pixclk_khz / htotal : 0;
2497 if (state.base.supports_sec_gtf &&
2498 hor_freq_khz >= state.base.sec_gtf_start_freq) {
2499 t = state.calc_gtf_mode(gtf_data.w, gtf_data.h, gtf_data.freq,
2500 gtf_data.interlaced, gtf_data.ip_parm,
2501 gtf_data.overscan, true,
2502 state.base.C, state.base.M,
2503 state.base.K, state.base.J);
2505 calc_ratio(&t);
2506 if (t.hfp <= 0)
2507 state.print_timings("", &t, "GTF", "INVALID: Hfront <= 0", true, false);
2508 else
2509 state.print_timings("", &t, "GTF", "", true, false);
2510 return 0;
2513 if (!ret && state.edid_size)
2514 ret = state.parse_edid();
2516 bool show_line = state.edid_size;
2518 for (const auto &n : if_names) {
2519 if (show_line)
2520 printf("\n================\n\n");
2521 show_line = true;
2523 state.warnings = state.failures = 0;
2524 for (unsigned i = 0; i < EDID_MAX_BLOCKS + 1; i++) {
2525 s_msgs[i][0].clear();
2526 s_msgs[i][1].clear();
2528 int r = state.parse_if(n);
2529 if (r && !ret)
2530 ret = r;
2532 return ret;
2535 #else
2538 * The surrounding JavaScript implementation will call this function
2539 * each time it wants to decode an EDID. So this should reset all the
2540 * state and start over.
2542 extern "C" int parse_edid(const char *input)
2544 for (unsigned i = 0; i < EDID_MAX_BLOCKS + 1; i++) {
2545 s_msgs[i][0].clear();
2546 s_msgs[i][1].clear();
2548 options[OptCheck] = 1;
2549 options[OptPreferredTimings] = 1;
2550 options[OptNativeResolution] = 1;
2551 options[OptSkipSHA] = 0;
2552 options[OptUTF8] = 1;
2553 state = edid_state();
2554 int ret = edid_from_file(input, stderr);
2555 return ret ? ret : state.parse_edid();
2558 #endif