edid-decode: fix reporting PNP as a proper PNP
[edid-decode.git] / edid-decode.cpp
blob1fc7fce361757810302f5e642fc9274f7433184f
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 OptCheck = 'c',
44 OptCheckInline = 'C',
45 OptFBModeTimings = 'F',
46 OptHelp = 'h',
47 OptOnlyHexDump = 'H',
48 OptLongTimings = 'L',
49 OptNativeTimings = 'n',
50 OptOutputFormat = 'o',
51 OptPreferredTimings = 'p',
52 OptPhysicalAddress = 'P',
53 OptSkipHexDump = 's',
54 OptShortTimings = 'S',
55 OptV4L2Timings = 'V',
56 OptXModeLineTimings = 'X',
57 OptSkipSHA = 128,
58 OptHideSerialNumbers,
59 OptVersion,
60 OptSTD,
61 OptDMT,
62 OptVIC,
63 OptHDMIVIC,
64 OptCVT,
65 OptGTF,
66 OptListEstTimings,
67 OptListDMTs,
68 OptListVICs,
69 OptListHDMIVICs,
70 OptLast = 256
73 static char options[OptLast];
75 static struct option long_options[] = {
76 { "help", no_argument, 0, OptHelp },
77 { "output-format", required_argument, 0, OptOutputFormat },
78 { "native-timings", no_argument, 0, OptNativeTimings },
79 { "preferred-timings", no_argument, 0, OptPreferredTimings },
80 { "physical-address", no_argument, 0, OptPhysicalAddress },
81 { "skip-hex-dump", no_argument, 0, OptSkipHexDump },
82 { "only-hex-dump", no_argument, 0, OptOnlyHexDump },
83 { "skip-sha", no_argument, 0, OptSkipSHA },
84 { "hide-serial-numbers", no_argument, 0, OptHideSerialNumbers },
85 { "version", no_argument, 0, OptVersion },
86 { "check-inline", no_argument, 0, OptCheckInline },
87 { "check", no_argument, 0, OptCheck },
88 { "short-timings", no_argument, 0, OptShortTimings },
89 { "long-timings", no_argument, 0, OptLongTimings },
90 { "xmodeline", no_argument, 0, OptXModeLineTimings },
91 { "fbmode", no_argument, 0, OptFBModeTimings },
92 { "v4l2-timings", no_argument, 0, OptV4L2Timings },
93 { "std", required_argument, 0, OptSTD },
94 { "dmt", required_argument, 0, OptDMT },
95 { "vic", required_argument, 0, OptVIC },
96 { "hdmi-vic", required_argument, 0, OptHDMIVIC },
97 { "cvt", required_argument, 0, OptCVT },
98 { "gtf", required_argument, 0, OptGTF },
99 { "list-established-timings", no_argument, 0, OptListEstTimings },
100 { "list-dmts", no_argument, 0, OptListDMTs },
101 { "list-vics", no_argument, 0, OptListVICs },
102 { "list-hdmi-vics", no_argument, 0, OptListHDMIVICs },
103 { 0, 0, 0, 0 }
106 static void usage(void)
108 printf("Usage: edid-decode <options> [in [out]]\n"
109 " [in] EDID file to parse. Read from standard input if none given\n"
110 " or if the input filename is '-'.\n"
111 " [out] Output the read EDID to this file. Write to standard output\n"
112 " if the output filename is '-'.\n"
113 "\nOptions:\n"
114 " -o, --output-format <fmt>\n"
115 " If [out] is specified, then write the EDID in this format\n"
116 " <fmt> is one of:\n"
117 " hex: hex numbers in ascii text (default for stdout)\n"
118 " raw: binary data (default unless writing to stdout)\n"
119 " carray: c-program struct\n"
120 " xml: XML data\n"
121 " -c, --check Check if the EDID conforms to the standards, failures and\n"
122 " warnings are reported at the end.\n"
123 " -C, --check-inline Check if the EDID conforms to the standards, failures and\n"
124 " warnings are reported inline.\n"
125 " -n, --native-timings Report the native timings.\n"
126 " -p, --preferred-timings Report the preferred timings.\n"
127 " -P, --physical-address Only report the CEC physical address.\n"
128 " -S, --short-timings Report all video timings in a short format.\n"
129 " -L, --long-timings Report all video timings in a long format.\n"
130 " -X, --xmodeline Report all long video timings in Xorg.conf format.\n"
131 " -F, --fbmode Report all long video timings in fb.modes format.\n"
132 " -V, --v4l2-timings Report all long video timings in v4l2-dv-timings.h format.\n"
133 " -s, --skip-hex-dump Skip the initial hex dump of the EDID.\n"
134 " -H, --only-hex-dump Only output the hex dump of the EDID.\n"
135 " --skip-sha Skip the SHA report.\n"
136 " --hide-serial-numbers Replace serial numbers with '...'\n"
137 " --version show the edid-decode version (SHA)\n"
138 " --std <byte1>,<byte2> Show the standard timing represented by these two bytes.\n"
139 " --dmt <dmt> Show the timings for the DMT with the given DMT ID.\n"
140 " --vic <vic> Show the timings for this VIC.\n"
141 " --hdmi-vic <hdmivic> Show the timings for this HDMI VIC.\n"
142 " --cvt w=<width>,h=<height>,fps=<fps>[,rb=<rb>][,interlaced][,overscan][,alt][,hblank=<hblank][,add-vblank=<add-vblank>\n"
143 " Calculate the CVT timings for the given format.\n"
144 " <fps> is frames per second for progressive timings,\n"
145 " or fields per second for interlaced timings.\n"
146 " <rb> can be 0 (no reduced blanking, default), or\n"
147 " 1-3 for the reduced blanking version.\n"
148 " If 'interlaced' is given, then this is an interlaced format.\n"
149 " If 'overscan' is given, then this is an overscanned format.\n"
150 " If 'alt' is given and <rb>=2, then report the timings\n"
151 " optimized for video: 1000 / 1001 * <fps>.\n"
152 " If 'alt' is given and <rb>=3, then the horizontal blanking\n"
153 " is 160 instead of 80 pixels.\n"
154 " If 'hblank' is given and <rb>=3, then the horizontal blanking\n"
155 " is <hblank> pixels (range of 80-200), overriding 'alt'.\n"
156 " If 'add-vblank' is given and <rb>=3, then <add-vblank> usecs are\n"
157 " added to the minimum vertical blank time of 460 usecs.\n"
158 " --gtf w=<width>,h=<height>[,fps=<fps>][,horfreq=<horfreq>][,pixclk=<pixclk>][,interlaced]\n"
159 " [,overscan][,secondary][,C=<c>][,M=<m>][,K=<k>][,J=<j>]\n"
160 " Calculate the GTF timings for the given format.\n"
161 " <fps> is frames per second for progressive timings,\n"
162 " or fields per second for interlaced timings.\n"
163 " <horfreq> is the horizontal frequency in kHz.\n"
164 " <pixclk> is the pixel clock frequency in MHz.\n"
165 " Only one of fps, horfreq or pixclk must be given.\n"
166 " If 'interlaced' is given, then this is an interlaced format.\n"
167 " If 'overscan' is given, then this is an overscanned format.\n"
168 " If 'secondary' is given, then the secondary GTF is used for\n"
169 " reduced blanking, where <c>, <m>, <k> and <j> are parameters\n"
170 " for the secondary curve.\n"
171 " --list-established-timings List all known Established Timings.\n"
172 " --list-dmts List all known DMTs.\n"
173 " --list-vics List all known VICs.\n"
174 " --list-hdmi-vics List all known HDMI VICs.\n"
175 " -h, --help Display this help message.\n");
178 static std::string s_msgs[EDID_MAX_BLOCKS + 1][2];
180 void msg(bool is_warn, const char *fmt, ...)
182 char buf[1024] = "";
183 va_list ap;
185 va_start(ap, fmt);
186 vsprintf(buf, fmt, ap);
187 va_end(ap);
189 if (is_warn)
190 state.warnings++;
191 else
192 state.failures++;
193 if (state.data_block.empty())
194 s_msgs[state.block_nr][is_warn] += std::string(" ") + buf;
195 else
196 s_msgs[state.block_nr][is_warn] += " " + state.data_block + ": " + buf;
198 if (options[OptCheckInline])
199 printf("%s: %s", is_warn ? "WARN" : "FAIL", buf);
202 static void show_msgs(bool is_warn)
204 printf("\n%s:\n\n", is_warn ? "Warnings" : "Failures");
205 for (unsigned i = 0; i < state.num_blocks; i++) {
206 if (s_msgs[i][is_warn].empty())
207 continue;
208 printf("Block %u, %s:\n%s",
209 i, block_name(edid[i * EDID_PAGE_SIZE]).c_str(),
210 s_msgs[i][is_warn].c_str());
212 if (s_msgs[EDID_MAX_BLOCKS][is_warn].empty())
213 return;
214 printf("EDID:\n%s",
215 s_msgs[EDID_MAX_BLOCKS][is_warn].c_str());
219 void do_checksum(const char *prefix, const unsigned char *x, size_t len)
221 unsigned char check = x[len - 1];
222 unsigned char sum = 0;
223 unsigned i;
225 printf("%sChecksum: 0x%02hhx", prefix, check);
227 for (i = 0; i < len-1; i++)
228 sum += x[i];
230 if ((unsigned char)(check + sum) != 0) {
231 printf(" (should be 0x%02x)\n", -sum & 0xff);
232 fail("Invalid checksum 0x%02x (should be 0x%02x).\n",
233 check, -sum & 0xff);
234 return;
236 printf("\n");
239 static unsigned gcd(unsigned a, unsigned b)
241 while (b) {
242 unsigned t = b;
244 b = a % b;
245 a = t;
247 return a;
250 void calc_ratio(struct timings *t)
252 unsigned d = gcd(t->hact, t->vact);
254 if (d == 0) {
255 t->hratio = t->vratio = 0;
256 return;
258 t->hratio = t->hact / d;
259 t->vratio = t->vact / d;
262 std::string edid_state::dtd_type(unsigned cnt)
264 unsigned len = std::to_string(cta.preparsed_total_dtds).length();
265 char buf[16];
266 sprintf(buf, "DTD %*u", len, cnt);
267 return buf;
270 bool edid_state::match_timings(const timings &t1, const timings &t2)
272 if (t1.hact != t2.hact ||
273 t1.vact != t2.vact ||
274 t1.rb != t2.rb ||
275 t1.interlaced != t2.interlaced ||
276 t1.hfp != t2.hfp ||
277 t1.hbp != t2.hbp ||
278 t1.hsync != t2.hsync ||
279 t1.pos_pol_hsync != t2.pos_pol_hsync ||
280 t1.hratio != t2.hratio ||
281 t1.vfp != t2.vfp ||
282 t1.vbp != t2.vbp ||
283 t1.vsync != t2.vsync ||
284 t1.pos_pol_vsync != t2.pos_pol_vsync ||
285 t1.vratio != t2.vratio ||
286 t1.pixclk_khz != t2.pixclk_khz)
287 return false;
288 return true;
291 static void or_str(std::string &s, const std::string &flag, unsigned &num_flags)
293 if (!num_flags)
294 s = flag;
295 else if (num_flags % 2 == 0)
296 s = s + " | \\\n\t\t" + flag;
297 else
298 s = s + " | " + flag;
299 num_flags++;
303 * Return true if the timings are a close, but not identical,
304 * match. The only differences allowed are polarities and
305 * porches and syncs, provided the total blanking remains the
306 * same.
308 bool timings_close_match(const timings &t1, const timings &t2)
310 // We don't want to deal with borders, you're on your own
311 // if you are using those.
312 if (t1.hborder || t1.vborder ||
313 t2.hborder || t2.vborder)
314 return false;
315 if (t1.hact != t2.hact || t1.vact != t2.vact ||
316 t1.interlaced != t2.interlaced ||
317 t1.pixclk_khz != t2.pixclk_khz ||
318 t1.hfp + t1.hsync + t1.hbp != t2.hfp + t2.hsync + t2.hbp ||
319 t1.vfp + t1.vsync + t1.vbp != t2.vfp + t2.vsync + t2.vbp)
320 return false;
321 if (t1.hfp == t2.hfp &&
322 t1.hsync == t2.hsync &&
323 t1.hbp == t2.hbp &&
324 t1.pos_pol_hsync == t2.pos_pol_hsync &&
325 t1.vfp == t2.vfp &&
326 t1.vsync == t2.vsync &&
327 t1.vbp == t2.vbp &&
328 t1.pos_pol_vsync == t2.pos_pol_vsync)
329 return false;
330 return true;
333 static void print_modeline(unsigned indent, const struct timings *t, double refresh)
335 unsigned offset = (!t->even_vtotal && t->interlaced) ? 1 : 0;
336 unsigned hfp = t->hborder + t->hfp;
337 unsigned hbp = t->hborder + t->hbp;
338 unsigned vfp = t->vborder + t->vfp;
339 unsigned vbp = t->vborder + t->vbp;
341 printf("%*sModeline \"%ux%u_%.2f%s\" %.3f %u %u %u %u %u %u %u %u %cHSync",
342 indent, "",
343 t->hact, t->vact, refresh,
344 t->interlaced ? "i" : "", t->pixclk_khz / 1000.0,
345 t->hact, t->hact + hfp, t->hact + hfp + t->hsync,
346 t->hact + hfp + t->hsync + hbp,
347 t->vact, t->vact + vfp, t->vact + vfp + t->vsync,
348 t->vact + vfp + t->vsync + vbp + offset,
349 t->pos_pol_hsync ? '+' : '-');
350 if (!t->no_pol_vsync)
351 printf(" %cVSync", t->pos_pol_vsync ? '+' : '-');
352 if (t->interlaced)
353 printf(" Interlace");
354 printf("\n");
357 static void print_fbmode(unsigned indent, const struct timings *t,
358 double refresh, double hor_freq_khz)
360 printf("%*smode \"%ux%u-%u%s\"\n",
361 indent, "",
362 t->hact, t->vact,
363 (unsigned)(0.5 + (t->interlaced ? refresh / 2.0 : refresh)),
364 t->interlaced ? "-lace" : "");
365 printf("%*s# D: %.2f MHz, H: %.3f kHz, V: %.2f Hz\n",
366 indent + 8, "",
367 t->pixclk_khz / 1000.0, hor_freq_khz, refresh);
368 printf("%*sgeometry %u %u %u %u 32\n",
369 indent + 8, "",
370 t->hact, t->vact, t->hact, t->vact);
371 unsigned mult = t->interlaced ? 2 : 1;
372 unsigned offset = !t->even_vtotal && t->interlaced;
373 unsigned hfp = t->hborder + t->hfp;
374 unsigned hbp = t->hborder + t->hbp;
375 unsigned vfp = t->vborder + t->vfp;
376 unsigned vbp = t->vborder + t->vbp;
377 printf("%*stimings %llu %d %d %d %u %u %u\n",
378 indent + 8, "",
379 (unsigned long long)(1000000000.0 / (double)(t->pixclk_khz) + 0.5),
380 hbp, hfp, mult * vbp, mult * vfp + offset, t->hsync, mult * t->vsync);
381 if (t->interlaced)
382 printf("%*slaced true\n", indent + 8, "");
383 if (t->pos_pol_hsync)
384 printf("%*shsync high\n", indent + 8, "");
385 if (t->pos_pol_vsync)
386 printf("%*svsync high\n", indent + 8, "");
387 printf("%*sendmode\n", indent, "");
390 static void print_v4l2_timing(const struct timings *t,
391 double refresh, const char *type)
393 printf("\t#define V4L2_DV_BT_%uX%u%c%u_%02u { \\\n",
394 t->hact, t->vact, t->interlaced ? 'I' : 'P',
395 (unsigned)refresh, (unsigned)(0.5 + 100.0 * (refresh - (unsigned)refresh)));
396 printf("\t\t.type = V4L2_DV_BT_656_1120, \\\n");
397 printf("\t\tV4L2_INIT_BT_TIMINGS(%u, %u, %u, ",
398 t->hact, t->vact, t->interlaced);
399 if (!t->pos_pol_hsync && !t->pos_pol_vsync)
400 printf("0, \\\n");
401 else if (t->pos_pol_hsync && t->pos_pol_vsync)
402 printf("\\\n\t\t\tV4L2_DV_HSYNC_POS_POL | V4L2_DV_VSYNC_POS_POL, \\\n");
403 else if (t->pos_pol_hsync)
404 printf("V4L2_DV_HSYNC_POS_POL, \\\n");
405 else
406 printf("V4L2_DV_VSYNC_POS_POL, \\\n");
407 unsigned hfp = t->hborder + t->hfp;
408 unsigned hbp = t->hborder + t->hbp;
409 unsigned vfp = t->vborder + t->vfp;
410 unsigned vbp = t->vborder + t->vbp;
411 printf("\t\t\t%lluULL, %d, %u, %d, %u, %u, %d, %u, %u, %d, \\\n",
412 t->pixclk_khz * 1000ULL, hfp, t->hsync, hbp,
413 vfp, t->vsync, vbp,
414 t->interlaced ? vfp : 0,
415 t->interlaced ? t->vsync : 0,
416 t->interlaced ? vbp + !t->even_vtotal : 0);
418 std::string flags;
419 unsigned num_flags = 0;
420 unsigned vic = 0;
421 unsigned hdmi_vic = 0;
422 const char *std = "0";
424 if (t->interlaced && !t->even_vtotal)
425 or_str(flags, "V4L2_DV_FL_HALF_LINE", num_flags);
426 if (!memcmp(type, "VIC", 3)) {
427 or_str(flags, "V4L2_DV_FL_HAS_CEA861_VIC", num_flags);
428 or_str(flags, "V4L2_DV_FL_IS_CE_VIDEO", num_flags);
429 vic = strtoul(type + 4, 0, 0);
431 if (!memcmp(type, "HDMI VIC", 8)) {
432 or_str(flags, "V4L2_DV_FL_HAS_HDMI_VIC", num_flags);
433 or_str(flags, "V4L2_DV_FL_IS_CE_VIDEO", num_flags);
434 hdmi_vic = strtoul(type + 9, 0, 0);
435 vic = hdmi_vic_to_vic(hdmi_vic);
436 if (vic)
437 or_str(flags, "V4L2_DV_FL_HAS_CEA861_VIC", num_flags);
439 if (vic && (fmod(refresh, 6)) == 0.0)
440 or_str(flags, "V4L2_DV_FL_CAN_REDUCE_FPS", num_flags);
441 if (t->rb)
442 or_str(flags, "V4L2_DV_FL_REDUCED_BLANKING", num_flags);
443 if (t->hratio && t->vratio)
444 or_str(flags, "V4L2_DV_FL_HAS_PICTURE_ASPECT", num_flags);
446 if (!memcmp(type, "VIC", 3) || !memcmp(type, "HDMI VIC", 8))
447 std = "V4L2_DV_BT_STD_CEA861";
448 else if (!memcmp(type, "DMT", 3))
449 std = "V4L2_DV_BT_STD_DMT";
450 else if (!memcmp(type, "CVT", 3))
451 std = "V4L2_DV_BT_STD_CVT";
452 else if (!memcmp(type, "GTF", 3))
453 std = "V4L2_DV_BT_STD_GTF";
454 printf("\t\t\t%s, \\\n", std);
455 printf("\t\t\t%s, \\\n", flags.empty() ? "0" : flags.c_str());
456 printf("\t\t\t{ %u, %u }, %u, %u) \\\n",
457 t->hratio, t->vratio, vic, hdmi_vic);
458 printf("\t}\n");
461 static void print_detailed_timing(unsigned indent, const struct timings *t)
463 printf("%*sHfront %4d Hsync %3u Hback %3d Hpol %s",
464 indent, "",
465 t->hfp, t->hsync, t->hbp, t->pos_pol_hsync ? "P" : "N");
466 if (t->hborder)
467 printf(" Hborder %u", t->hborder);
468 printf("\n");
470 printf("%*sVfront %4u Vsync %3u Vback %3d",
471 indent, "", t->vfp, t->vsync, t->vbp);
472 if (!t->no_pol_vsync)
473 printf(" Vpol %s", t->pos_pol_vsync ? "P" : "N");
474 if (t->vborder)
475 printf(" Vborder %u", t->vborder);
476 if (t->even_vtotal) {
477 printf(" Both Fields");
478 } else if (t->interlaced) {
479 printf(" Vfront +0.5 Odd Field\n");
480 printf("%*sVfront %4d Vsync %3u Vback %3d",
481 indent, "", t->vfp, t->vsync, t->vbp);
482 if (!t->no_pol_vsync)
483 printf(" Vpol %s", t->pos_pol_vsync ? "P" : "N");
484 if (t->vborder)
485 printf(" Vborder %u", t->vborder);
486 printf(" Vback +0.5 Even Field");
488 printf("\n");
491 bool edid_state::print_timings(const char *prefix, const struct timings *t,
492 const char *type, const char *flags,
493 bool detailed, bool do_checks)
495 if (!t) {
496 // Should not happen
497 if (do_checks)
498 fail("Unknown video timings.\n");
499 return false;
502 if (detailed && options[OptShortTimings])
503 detailed = false;
504 if (options[OptLongTimings])
505 detailed = true;
507 unsigned vact = t->vact;
508 unsigned hbl = t->hfp + t->hsync + t->hbp + 2 * t->hborder;
509 unsigned vbl = t->vfp + t->vsync + t->vbp + 2 * t->vborder;
510 unsigned htotal = t->hact + hbl;
511 double hor_freq_khz = htotal ? (double)t->pixclk_khz / htotal : 0;
513 if (t->interlaced)
514 vact /= 2;
516 double out_hor_freq_khz = hor_freq_khz;
517 if (t->ycbcr420)
518 hor_freq_khz /= 2;
520 double vtotal = vact + vbl;
522 bool ok = true;
524 if (!t->hact || !hbl || !t->hfp || !t->hsync ||
525 !vact || !vbl || (!t->vfp && !t->interlaced && !t->even_vtotal) || !t->vsync) {
526 if (do_checks)
527 fail("0 values in the video timing:\n"
528 " Horizontal Active/Blanking %u/%u\n"
529 " Horizontal Frontporch/Sync Width %u/%u\n"
530 " Vertical Active/Blanking %u/%u\n"
531 " Vertical Frontporch/Sync Width %u/%u\n",
532 t->hact, hbl, t->hfp, t->hsync, vact, vbl, t->vfp, t->vsync);
533 ok = false;
536 if (t->even_vtotal)
537 vtotal = vact + t->vfp + t->vsync + t->vbp;
538 else if (t->interlaced)
539 vtotal = vact + t->vfp + t->vsync + t->vbp + 0.5;
541 double refresh = (double)t->pixclk_khz * 1000.0 / (htotal * vtotal);
543 std::string s;
544 unsigned rb = t->rb & ~RB_ALT;
545 if (rb) {
546 bool alt = t->rb & RB_ALT;
547 s = "RB";
548 if (rb == RB_CVT_V2)
549 s += std::string("v2") + (alt ? ",video-optimized" : "");
550 else if (rb == RB_CVT_V3)
551 s += std::string("v3") + (alt ? ",h-blank-160" : "");
553 add_str(s, flags);
554 if (t->hsize_mm || t->vsize_mm)
555 add_str(s, std::to_string(t->hsize_mm) + " mm x " + std::to_string(t->vsize_mm) + " mm");
556 if (t->hsize_mm > dtd_max_hsize_mm)
557 dtd_max_hsize_mm = t->hsize_mm;
558 if (t->vsize_mm > dtd_max_vsize_mm)
559 dtd_max_vsize_mm = t->vsize_mm;
560 if (!s.empty())
561 s = " (" + s + ")";
562 unsigned out_pixclk_khz = t->pixclk_khz;
563 unsigned pixclk_khz = t->pixclk_khz / (t->ycbcr420 ? 2 : 1);
565 char buf[10];
567 sprintf(buf, "%u%s", t->vact, t->interlaced ? "i" : "");
568 printf("%s%s: %5ux%-5s %7.3f Hz %3u:%-3u %7.3f kHz %8.3f MHz%s\n",
569 prefix, type,
570 t->hact, buf,
571 refresh,
572 t->hratio, t->vratio,
573 out_hor_freq_khz,
574 out_pixclk_khz / 1000.0,
575 s.c_str());
577 unsigned len = strlen(prefix) + 2;
579 if (!t->ycbcr420 && detailed && options[OptXModeLineTimings])
580 print_modeline(len, t, refresh);
581 else if (!t->ycbcr420 && detailed && options[OptFBModeTimings])
582 print_fbmode(len, t, refresh, hor_freq_khz);
583 else if (!t->ycbcr420 && detailed && options[OptV4L2Timings])
584 print_v4l2_timing(t, refresh, type);
585 else if (detailed)
586 print_detailed_timing(len + strlen(type) + 6, t);
588 if (!do_checks)
589 return ok;
591 if (!memcmp(type, "DTD", 3)) {
592 unsigned vic, dmt;
593 const timings *vic_t = cta_close_match_to_vic(*t, vic);
595 if (vic_t)
596 warn("DTD is similar but not identical to VIC %u.\n", vic);
598 const timings *dmt_t = close_match_to_dmt(*t, dmt);
599 if (!vic_t && dmt_t)
600 warn("DTD is similar but not identical to DMT 0x%02x.\n", dmt);
603 if (refresh) {
604 min_vert_freq_hz = min(min_vert_freq_hz, refresh);
605 max_vert_freq_hz = max(max_vert_freq_hz, refresh);
607 if (hor_freq_khz) {
608 min_hor_freq_hz = min(min_hor_freq_hz, hor_freq_khz * 1000.0);
609 max_hor_freq_hz = max(max_hor_freq_hz, hor_freq_khz * 1000.0);
610 max_pixclk_khz = max(max_pixclk_khz, pixclk_khz);
611 if (t->pos_pol_hsync && !t->pos_pol_vsync && t->vsync == 3)
612 base.max_pos_neg_hor_freq_khz = hor_freq_khz;
615 if (t->ycbcr420 && t->pixclk_khz < 590000)
616 warn_once("Some YCbCr 4:2:0 timings are invalid for HDMI (which requires an RGB timings pixel rate >= 590 MHz).\n");
617 if (t->hfp <= 0)
618 fail("0 or negative horizontal front porch.\n");
619 if (t->hbp <= 0)
620 fail("0 or negative horizontal back porch.\n");
621 if (t->vbp <= 0)
622 fail("0 or negative vertical back porch.\n");
623 if (!base.max_display_width_mm && !base.max_display_height_mm) {
624 /* this is valid */
625 } else if (!t->hsize_mm && !t->vsize_mm) {
626 /* this is valid */
627 } else if (t->hsize_mm > base.max_display_width_mm + 9 ||
628 t->vsize_mm > base.max_display_height_mm + 9) {
629 fail("Mismatch of image size %ux%u mm vs display size %ux%u mm.\n",
630 t->hsize_mm, t->vsize_mm, base.max_display_width_mm, base.max_display_height_mm);
631 } else if (t->hsize_mm < base.max_display_width_mm - 9 &&
632 t->vsize_mm < base.max_display_height_mm - 9) {
633 fail("Mismatch of image size %ux%u mm vs display size %ux%u mm.\n",
634 t->hsize_mm, t->vsize_mm, base.max_display_width_mm, base.max_display_height_mm);
636 return ok;
639 std::string containerid2s(const unsigned char *x)
641 char buf[40];
643 sprintf(buf, "%02x%02x%02x%02x-%02x%02x-%02x%02x-%02x%02x-%02x%02x%02x%02x%02x%02x",
644 x[0], x[1], x[2], x[3],
645 x[4], x[5],
646 x[6], x[7],
647 x[8], x[9],
648 x[10], x[11], x[12], x[13], x[14], x[15]);
649 return buf;
652 std::string utohex(unsigned char x)
654 char buf[10];
656 sprintf(buf, "0x%02hhx", x);
657 return buf;
660 const char *oui_name(unsigned oui, unsigned *ouinum)
662 unsigned ouinumscratch;
663 if (!ouinum) ouinum = &ouinumscratch;
664 const char *name;
665 switch (oui) {
666 #define oneoui(c,k,n) case c: *ouinum = kOUI_##k; name = n; break;
667 #include "oui.h"
668 default: *ouinum = 0; name = NULL; break;
670 return name;
673 void edid_state::data_block_oui(std::string block_name, const unsigned char *x,
674 unsigned length, unsigned *ouinum, bool ignorezeros, bool do_ascii, bool big_endian)
676 std::string buf;
677 char ascii[4];
678 unsigned oui;
679 const char *ouiname = NULL;
680 bool matched_reverse = false;
681 bool matched_ascii = false;
682 bool valid_ascii = false;
684 if (big_endian)
685 oui = ((length > 0 ? x[0] : 0) << 16) + ((length > 1 ? x[1] : 0) << 8) + (length > 2 ? x[2] : 0);
686 else
687 oui = ((length > 2 ? x[2] : 0) << 16) + ((length > 1 ? x[1] : 0) << 8) + (length > 0 ? x[0] : 0);
689 buf = ouitohex(oui);
690 if (length < 3) {
691 sprintf(ascii, "?"); // some characters are null
692 if (ouinum) *ouinum = 0; // doesn't match a known OUI
693 } else {
694 valid_ascii = (x[0] >= 'A' && x[1] >= 'A' && x[2] >= 'A' && x[0] <= 'Z' && x[1] <= 'Z' && x[2] <= 'Z');
695 sprintf(ascii, "%c%c%c", x[0], x[1], x[2]);
697 ouiname = oui_name(oui, ouinum);
698 if (!ouiname) {
699 big_endian = !big_endian;
700 unsigned reversedoui = ((oui & 0xff) << 16) + (oui & 0x00ff00) + (oui >> 16);
701 ouiname = oui_name(reversedoui, ouinum);
702 if (ouiname) {
703 oui = reversedoui;
704 buf = ouitohex(oui);
705 matched_reverse = true;
706 } else if (do_ascii && valid_ascii) {
707 unsigned asciioui = (x[0] << 24) + (x[1] << 16) + (x[2] << 8);
708 ouiname = oui_name(asciioui, ouinum);
711 matched_ascii = do_ascii && valid_ascii && ouiname != NULL;
714 std::string name;
715 if (ouiname) {
716 if (matched_ascii)
717 name = block_name + " (" + ouiname + ")" + ", PNP ID '" + ascii + "'";
718 else
719 name = block_name + " (" + ouiname + ")" + ", OUI " + buf;
720 } else if (do_ascii && valid_ascii) {
721 name = block_name + ", PNP ID '" + ascii + "'";
722 } else {
723 name = block_name + ", OUI " + buf;
725 // assign string to data_block before outputting errors
726 data_block = name;
728 if (oui || !ignorezeros) {
729 printf(" %s:\n", data_block.c_str());
730 if (length < 3)
731 fail("Data block length (%d) is not enough to contain an OUI.\n", length);
732 else if (ouiname) {
733 if (do_ascii && !valid_ascii)
734 warn("Expected PNP ID but found OUI.\n");
735 if (matched_reverse)
736 fail("Endian-ness (%s) of OUI is different than expected (%s).\n", big_endian ? "be" : "le", big_endian ? "le" : "be");
738 else {
739 if (valid_ascii)
740 warn("Unknown OUI %s (possible PNP %s).\n", buf.c_str(), ascii);
741 else
742 warn("Unknown OUI %s.\n", buf.c_str());
747 std::string ouitohex(unsigned oui)
749 char buf[32];
751 sprintf(buf, "%02X-%02X-%02X", (oui >> 16) & 0xff, (oui >> 8) & 0xff, oui & 0xff);
752 return buf;
755 bool memchk(const unsigned char *x, unsigned len, unsigned char v)
757 for (unsigned i = 0; i < len; i++)
758 if (x[i] != v)
759 return false;
760 return true;
763 void hex_block(const char *prefix, const unsigned char *x,
764 unsigned length, bool show_ascii, unsigned step)
766 unsigned i, j;
768 for (i = 0; i < length; i += step) {
769 unsigned len = min(step, length - i);
771 printf("%s", prefix);
772 for (j = 0; j < len; j++)
773 printf("%s%02x", j ? " " : "", x[i + j]);
775 if (show_ascii) {
776 for (j = len; j < step; j++)
777 printf(" ");
778 printf(" '");
779 for (j = 0; j < len; j++)
780 printf("%c", x[i + j] >= ' ' && x[i + j] <= '~' ? x[i + j] : '.');
781 printf("'");
783 printf("\n");
787 static bool edid_add_byte(const char *s, bool two_digits = true)
789 char buf[3];
791 if (state.edid_size == sizeof(edid))
792 return false;
793 buf[0] = s[0];
794 buf[1] = two_digits ? s[1] : 0;
795 buf[2] = 0;
796 edid[state.edid_size++] = strtoul(buf, NULL, 16);
797 return true;
800 static bool extract_edid_quantumdata(const char *start)
802 /* Parse QuantumData 980 EDID files */
803 do {
804 start = strstr(start, ">");
805 if (!start)
806 return false;
807 start++;
808 for (unsigned i = 0; start[i] && start[i + 1] && i < 256; i += 2)
809 if (!edid_add_byte(start + i))
810 return false;
811 start = strstr(start, "<BLOCK");
812 } while (start);
813 return state.edid_size;
816 static const char *ignore_chars = ",:;";
818 static bool extract_edid_hex(const char *s, bool require_two_digits = true)
820 for (; *s; s++) {
821 if (isspace(*s) || strchr(ignore_chars, *s))
822 continue;
824 if (*s == '0' && tolower(s[1]) == 'x') {
825 s++;
826 continue;
829 /* Read one or two hex digits from the log */
830 if (!isxdigit(s[0])) {
831 if (state.edid_size && state.edid_size % 128 == 0)
832 break;
833 return false;
835 if (require_two_digits && !isxdigit(s[1])) {
836 odd_hex_digits = true;
837 return false;
839 if (!edid_add_byte(s, isxdigit(s[1])))
840 return false;
841 if (isxdigit(s[1]))
842 s++;
844 return state.edid_size;
847 static bool extract_edid_xrandr(const char *start)
849 static const char indentation1[] = " ";
850 static const char indentation2[] = "\t\t";
851 /* Used to detect that we've gone past the EDID property */
852 static const char half_indentation1[] = " ";
853 static const char half_indentation2[] = "\t";
854 const char *indentation;
855 const char *s;
857 for (;;) {
858 unsigned j;
860 /* Get the next start of the line of EDID hex, assuming spaces for indentation */
861 s = strstr(start, indentation = indentation1);
862 /* Did we skip the start of another property? */
863 if (s && s > strstr(start, half_indentation1))
864 break;
866 /* If we failed, retry assuming tabs for indentation */
867 if (!s) {
868 s = strstr(start, indentation = indentation2);
869 /* Did we skip the start of another property? */
870 if (s && s > strstr(start, half_indentation2))
871 break;
874 if (!s)
875 break;
877 start = s + strlen(indentation);
879 for (j = 0; j < 16; j++, start += 2) {
880 /* Read a %02x from the log */
881 if (!isxdigit(start[0]) || !isxdigit(start[1])) {
882 if (j)
883 break;
884 return false;
886 if (!edid_add_byte(start))
887 return false;
890 return state.edid_size;
893 static bool extract_edid_xorg(const char *start)
895 bool find_first_num = true;
897 for (; *start; start++) {
898 if (find_first_num) {
899 const char *s;
901 /* skip ahead to the : */
902 s = strstr(start, ": \t");
903 if (!s)
904 s = strstr(start, ": ");
905 if (!s)
906 break;
907 start = s;
908 /* and find the first number */
909 while (!isxdigit(start[1]))
910 start++;
911 find_first_num = false;
912 continue;
913 } else {
914 /* Read a %02x from the log */
915 if (!isxdigit(*start)) {
916 find_first_num = true;
917 continue;
919 if (!edid_add_byte(start))
920 return false;
921 start++;
924 return state.edid_size;
927 static bool extract_edid(int fd, FILE *error)
929 std::vector<char> edid_data;
930 char buf[EDID_PAGE_SIZE];
932 for (;;) {
933 ssize_t i = read(fd, buf, sizeof(buf));
935 if (i < 0)
936 return false;
937 if (i == 0)
938 break;
939 edid_data.insert(edid_data.end(), buf, buf + i);
942 if (edid_data.empty()) {
943 state.edid_size = 0;
944 return false;
947 const char *data = &edid_data[0];
948 const char *start;
950 /* Look for edid-decode output */
951 start = strstr(data, "EDID (hex):");
952 if (!start)
953 start = strstr(data, "edid-decode (hex):");
954 if (start)
955 return extract_edid_hex(strchr(start, ':'));
957 /* Look for C-array */
958 start = strstr(data, "unsigned char edid[] = {");
959 if (start)
960 return extract_edid_hex(strchr(start, '{') + 1, false);
962 /* Look for QuantumData EDID output */
963 start = strstr(data, "<BLOCK");
964 if (start)
965 return extract_edid_quantumdata(start);
967 /* Look for xrandr --verbose output (lines of 16 hex bytes) */
968 start = strstr(data, "EDID_DATA:");
969 if (!start)
970 start = strstr(data, "EDID:");
971 if (start)
972 return extract_edid_xrandr(start);
974 /* Look for an EDID in an Xorg.0.log file */
975 start = strstr(data, "EDID (in hex):");
976 if (start)
977 start = strstr(start, "(II)");
978 if (start)
979 return extract_edid_xorg(start);
981 unsigned i;
983 /* Is the EDID provided in hex? */
984 for (i = 0; i < 32 && (isspace(data[i]) || strchr(ignore_chars, data[i]) ||
985 tolower(data[i]) == 'x' || isxdigit(data[i])); i++);
987 if (i == 32)
988 return extract_edid_hex(data);
990 /* Assume binary */
991 if (edid_data.size() > sizeof(edid)) {
992 fprintf(error, "Binary EDID length %zu is greater than %zu.\n",
993 edid_data.size(), sizeof(edid));
994 return false;
996 memcpy(edid, data, edid_data.size());
997 state.edid_size = edid_data.size();
998 return true;
1001 static unsigned char crc_calc(const unsigned char *b)
1003 unsigned char sum = 0;
1004 unsigned i;
1006 for (i = 0; i < 127; i++)
1007 sum += b[i];
1008 return 256 - sum;
1011 static int crc_ok(const unsigned char *b)
1013 return crc_calc(b) == b[127];
1016 static void hexdumpedid(FILE *f, const unsigned char *edid, unsigned size)
1018 unsigned b, i, j;
1020 for (b = 0; b < size / 128; b++) {
1021 const unsigned char *buf = edid + 128 * b;
1023 if (b)
1024 fprintf(f, "\n");
1025 for (i = 0; i < 128; i += 0x10) {
1026 fprintf(f, "%02x", buf[i]);
1027 for (j = 1; j < 0x10; j++) {
1028 fprintf(f, " %02x", buf[i + j]);
1030 fprintf(f, "\n");
1032 if (!crc_ok(buf))
1033 fprintf(f, "Block %u has a checksum error (should be 0x%02x).\n",
1034 b, crc_calc(buf));
1038 static void carraydumpedid(FILE *f, const unsigned char *edid, unsigned size)
1040 unsigned b, i, j;
1042 fprintf(f, "const unsigned char edid[] = {\n");
1043 for (b = 0; b < size / 128; b++) {
1044 const unsigned char *buf = edid + 128 * b;
1046 if (b)
1047 fprintf(f, "\n");
1048 for (i = 0; i < 128; i += 8) {
1049 fprintf(f, "\t0x%02x,", buf[i]);
1050 for (j = 1; j < 8; j++) {
1051 fprintf(f, " 0x%02x,", buf[i + j]);
1053 fprintf(f, "\n");
1055 if (!crc_ok(buf))
1056 fprintf(f, "\t/* Block %u has a checksum error (should be 0x%02x). */\n",
1057 b, crc_calc(buf));
1059 fprintf(f, "};\n");
1062 // This format can be read by the QuantumData EDID editor
1063 static void xmldumpedid(FILE *f, const unsigned char *edid, unsigned size)
1065 fprintf(f, "<?xml version=\"1.0\" encoding=\"UTF-8\" standalone=\"no\"?>\n");
1066 fprintf(f, "<DATAOBJ>\n");
1067 fprintf(f, " <HEADER TYPE=\"DID\" VERSION=\"1.0\"/>\n");
1068 fprintf(f, " <DATA>\n");
1069 for (unsigned b = 0; b < size / 128; b++) {
1070 const unsigned char *buf = edid + 128 * b;
1072 fprintf(f, " <BLOCK%u>", b);
1073 for (unsigned i = 0; i < 128; i++)
1074 fprintf(f, "%02X", buf[i]);
1075 fprintf(f, "</BLOCK%u>\n", b);
1077 fprintf(f, " </DATA>\n");
1078 fprintf(f, "</DATAOBJ>\n");
1082 static int edid_to_file(const char *to_file, enum output_format out_fmt)
1084 FILE *out;
1086 if (!strcmp(to_file, "-")) {
1087 to_file = "stdout";
1088 out = stdout;
1089 } else if ((out = fopen(to_file, "w")) == NULL) {
1090 perror(to_file);
1091 return -1;
1093 if (out_fmt == OUT_FMT_DEFAULT)
1094 out_fmt = out == stdout ? OUT_FMT_HEX : OUT_FMT_RAW;
1096 switch (out_fmt) {
1097 default:
1098 case OUT_FMT_HEX:
1099 hexdumpedid(out, edid, state.edid_size);
1100 break;
1101 case OUT_FMT_RAW:
1102 fwrite(edid, state.edid_size, 1, out);
1103 break;
1104 case OUT_FMT_CARRAY:
1105 carraydumpedid(out, edid, state.edid_size);
1106 break;
1107 case OUT_FMT_XML:
1108 xmldumpedid(out, edid, state.edid_size);
1109 break;
1112 if (out != stdout)
1113 fclose(out);
1114 return 0;
1117 static int edid_from_file(const char *from_file, FILE *error)
1119 #ifdef O_BINARY
1120 // Windows compatibility
1121 int flags = O_RDONLY | O_BINARY;
1122 #else
1123 int flags = O_RDONLY;
1124 #endif
1125 int fd;
1127 if (!strcmp(from_file, "-")) {
1128 from_file = "stdin";
1129 fd = 0;
1130 } else if ((fd = open(from_file, flags)) == -1) {
1131 perror(from_file);
1132 return -1;
1135 odd_hex_digits = false;
1136 if (!extract_edid(fd, error)) {
1137 if (!state.edid_size) {
1138 fprintf(error, "EDID of '%s' was empty.\n", from_file);
1139 return -1;
1141 fprintf(error, "EDID extract of '%s' failed: ", from_file);
1142 if (odd_hex_digits)
1143 fprintf(error, "odd number of hexadecimal digits.\n");
1144 else
1145 fprintf(error, "unknown format.\n");
1146 return -1;
1148 if (state.edid_size % EDID_PAGE_SIZE) {
1149 fprintf(error, "EDID length %u is not a multiple of %u.\n",
1150 state.edid_size, EDID_PAGE_SIZE);
1151 return -1;
1153 state.num_blocks = state.edid_size / EDID_PAGE_SIZE;
1154 if (fd != 0)
1155 close(fd);
1157 if (memcmp(edid, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\x00", 8)) {
1158 fprintf(error, "No EDID header found in '%s'.\n", from_file);
1159 return -1;
1161 return 0;
1164 /* generic extension code */
1166 std::string block_name(unsigned char block)
1168 char buf[10];
1170 switch (block) {
1171 case 0x00: return "Base EDID";
1172 case 0x02: return "CTA-861 Extension Block";
1173 case 0x10: return "Video Timing Extension Block";
1174 case 0x20: return "EDID 2.0 Extension Block";
1175 case 0x40: return "Display Information Extension Block";
1176 case 0x50: return "Localized String Extension Block";
1177 case 0x60: return "Microdisplay Interface Extension Block";
1178 case 0x70: return "DisplayID Extension Block";
1179 case 0xf0: return "Block Map Extension Block";
1180 case 0xff: return "Manufacturer-Specific Extension Block";
1181 default:
1182 sprintf(buf, " 0x%02x", block);
1183 return std::string("Unknown EDID Extension Block") + buf;
1187 void edid_state::parse_block_map(const unsigned char *x)
1189 unsigned last_valid_block_tag = 0;
1190 bool fail_once = false;
1191 unsigned offset = 1;
1192 unsigned i;
1194 if (block_nr == 1)
1195 block_map.saw_block_1 = true;
1196 else if (!block_map.saw_block_1)
1197 fail("No EDID Block Map Extension found in block 1.\n");
1198 else if (block_nr == 128)
1199 block_map.saw_block_128 = true;
1201 if (block_nr > 1)
1202 offset = 128;
1204 for (i = 1; i < 127; i++) {
1205 unsigned block = offset + i;
1207 if (x[i]) {
1208 last_valid_block_tag++;
1209 if (i != last_valid_block_tag && !fail_once) {
1210 fail("Valid block tags are not consecutive.\n");
1211 fail_once = true;
1213 printf(" Block %3u: %s\n", block, block_name(x[i]).c_str());
1214 if (block >= num_blocks) {
1215 if (!fail_once)
1216 fail("Invalid block number %u.\n", block);
1217 fail_once = true;
1218 } else if (x[i] != edid[block * EDID_PAGE_SIZE]) {
1219 fail("Block %u tag mismatch: expected 0x%02x, but got 0x%02x.\n",
1220 block, edid[block * EDID_PAGE_SIZE], x[i]);
1222 } else if (block < num_blocks) {
1223 fail("Block %u tag mismatch: expected 0x%02x, but got 0x00.\n",
1224 block, edid[block * EDID_PAGE_SIZE]);
1229 void edid_state::preparse_extension(const unsigned char *x)
1231 switch (x[0]) {
1232 case 0x02:
1233 has_cta = true;
1234 preparse_cta_block(x);
1235 break;
1236 case 0x70:
1237 has_dispid = true;
1238 preparse_displayid_block(x);
1239 break;
1243 void edid_state::parse_extension(const unsigned char *x)
1245 block = block_name(x[0]);
1246 data_block.clear();
1248 printf("\n");
1249 if (block_nr && x[0] == 0)
1250 block = "Unknown EDID Extension Block 0x00";
1251 printf("Block %u, %s:\n", block_nr, block.c_str());
1253 switch (x[0]) {
1254 case 0x02:
1255 parse_cta_block(x);
1256 break;
1257 case 0x10:
1258 parse_vtb_ext_block(x);
1259 break;
1260 case 0x20:
1261 fail("Deprecated extension block for EDID 2.0, do not use.\n");
1262 break;
1263 case 0x40:
1264 parse_di_ext_block(x);
1265 break;
1266 case 0x50:
1267 parse_ls_ext_block(x);
1268 break;
1269 case 0x70:
1270 parse_displayid_block(x);
1271 break;
1272 case 0xf0:
1273 parse_block_map(x);
1274 if (block_nr != 1 && block_nr != 128)
1275 fail("Must be used in block 1 and 128.\n");
1276 break;
1277 default:
1278 hex_block(" ", x, EDID_PAGE_SIZE);
1279 fail("Unknown Extension Block.\n");
1280 break;
1283 data_block.clear();
1284 do_checksum("", x, EDID_PAGE_SIZE);
1287 int edid_state::parse_edid()
1289 hide_serial_numbers = options[OptHideSerialNumbers];
1291 for (unsigned i = 1; i < num_blocks; i++)
1292 preparse_extension(edid + i * EDID_PAGE_SIZE);
1294 if (options[OptPhysicalAddress]) {
1295 printf("%x.%x.%x.%x\n",
1296 (cta.preparsed_phys_addr >> 12) & 0xf,
1297 (cta.preparsed_phys_addr >> 8) & 0xf,
1298 (cta.preparsed_phys_addr >> 4) & 0xf,
1299 cta.preparsed_phys_addr & 0xf);
1300 return 0;
1303 if (!options[OptSkipHexDump]) {
1304 printf("edid-decode (hex):\n\n");
1305 for (unsigned i = 0; i < num_blocks; i++) {
1306 hex_block("", edid + i * EDID_PAGE_SIZE, EDID_PAGE_SIZE, false);
1307 if (i == num_blocks - 1 && options[OptOnlyHexDump])
1308 return 0;
1309 printf("\n");
1311 printf("----------------\n\n");
1314 block = block_name(0x00);
1315 printf("Block %u, %s:\n", block_nr, block.c_str());
1316 parse_base_block(edid);
1318 for (unsigned i = 1; i < num_blocks; i++) {
1319 block_nr++;
1320 printf("\n----------------\n");
1321 parse_extension(edid + i * EDID_PAGE_SIZE);
1324 block = "";
1325 block_nr = EDID_MAX_BLOCKS;
1327 if (has_cta)
1328 cta_resolve_svrs();
1330 if (options[OptPreferredTimings] && base.preferred_timing.is_valid()) {
1331 printf("\n----------------\n");
1332 printf("\nPreferred Video Timing if only Block 0 is parsed:\n");
1333 print_timings(" ", base.preferred_timing, true, false);
1336 if (options[OptNativeTimings] &&
1337 base.preferred_timing.is_valid() && base.preferred_is_also_native) {
1338 printf("\n----------------\n");
1339 printf("\nNative Video Timing if only Block 0 is parsed:\n");
1340 print_timings(" ", base.preferred_timing, true, false);
1343 if (options[OptPreferredTimings] && !cta.preferred_timings.empty()) {
1344 printf("\n----------------\n");
1345 printf("\nPreferred Video Timing%s if Block 0 and CTA-861 Blocks are parsed:\n",
1346 cta.preferred_timings.size() > 1 ? "s" : "");
1347 for (vec_timings_ext::iterator iter = cta.preferred_timings.begin();
1348 iter != cta.preferred_timings.end(); ++iter)
1349 print_timings(" ", *iter, true, false);
1352 if (options[OptNativeTimings] && !cta.native_timings.empty()) {
1353 printf("\n----------------\n");
1354 printf("\nNative Video Timing%s if Block 0 and CTA-861 Blocks are parsed:\n",
1355 cta.native_timings.size() > 1 ? "s" : "");
1356 for (vec_timings_ext::iterator iter = cta.native_timings.begin();
1357 iter != cta.native_timings.end(); ++iter)
1358 print_timings(" ", *iter, true, false);
1361 if (options[OptPreferredTimings] && !dispid.preferred_timings.empty()) {
1362 printf("\n----------------\n");
1363 printf("\nPreferred Video Timing%s if Block 0 and DisplayID Blocks are parsed:\n",
1364 dispid.preferred_timings.size() > 1 ? "s" : "");
1365 for (vec_timings_ext::iterator iter = dispid.preferred_timings.begin();
1366 iter != dispid.preferred_timings.end(); ++iter)
1367 print_timings(" ", *iter, true, false);
1370 if (!options[OptCheck] && !options[OptCheckInline])
1371 return 0;
1373 check_base_block();
1374 if (has_cta)
1375 check_cta_blocks();
1376 if (has_dispid)
1377 check_displayid_blocks();
1379 printf("\n----------------\n");
1381 if (!options[OptSkipSHA] && strlen(STRING(SHA))) {
1382 printf("\nedid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
1385 if (options[OptCheck]) {
1386 if (warnings)
1387 show_msgs(true);
1388 if (failures)
1389 show_msgs(false);
1391 printf("\nEDID conformity: %s\n", failures ? "FAIL" : "PASS");
1392 return failures ? -2 : 0;
1395 enum cvt_opts {
1396 CVT_WIDTH = 0,
1397 CVT_HEIGHT,
1398 CVT_FPS,
1399 CVT_INTERLACED,
1400 CVT_OVERSCAN,
1401 CVT_RB,
1402 CVT_ALT,
1403 CVT_RB_H_BLANK,
1404 CVT_RB_ADD_V_BLANK,
1407 static int parse_cvt_subopt(char **subopt_str, double *value)
1409 int opt;
1410 char *opt_str;
1412 static const char * const subopt_list[] = {
1413 "w",
1414 "h",
1415 "fps",
1416 "interlaced",
1417 "overscan",
1418 "rb",
1419 "alt",
1420 "hblank",
1421 "add-vblank",
1422 nullptr
1425 opt = getsubopt(subopt_str, (char* const*) subopt_list, &opt_str);
1427 if (opt == -1) {
1428 fprintf(stderr, "Invalid suboptions specified.\n");
1429 usage();
1430 std::exit(EXIT_FAILURE);
1432 if (opt_str == nullptr && opt != CVT_INTERLACED && opt != CVT_ALT &&
1433 opt != CVT_OVERSCAN) {
1434 fprintf(stderr, "No value given to suboption <%s>.\n",
1435 subopt_list[opt]);
1436 usage();
1437 std::exit(EXIT_FAILURE);
1440 if (opt_str)
1441 *value = strtod(opt_str, nullptr);
1442 return opt;
1445 static void parse_cvt(char *optarg)
1447 unsigned w = 0, h = 0;
1448 double fps = 0;
1449 unsigned rb = RB_NONE;
1450 unsigned rb_h_blank = 0;
1451 unsigned rb_add_v_blank = 0;
1452 bool interlaced = false;
1453 bool alt = false;
1454 bool overscan = false;
1456 while (*optarg != '\0') {
1457 int opt;
1458 double opt_val;
1460 opt = parse_cvt_subopt(&optarg, &opt_val);
1462 switch (opt) {
1463 case CVT_WIDTH:
1464 w = round(opt_val);
1465 break;
1466 case CVT_HEIGHT:
1467 h = round(opt_val);
1468 break;
1469 case CVT_FPS:
1470 fps = opt_val;
1471 break;
1472 case CVT_RB:
1473 rb = opt_val;
1474 break;
1475 case CVT_OVERSCAN:
1476 overscan = true;
1477 break;
1478 case CVT_INTERLACED:
1479 interlaced = opt_val;
1480 break;
1481 case CVT_ALT:
1482 alt = opt_val;
1483 break;
1484 case CVT_RB_H_BLANK:
1485 rb_h_blank = opt_val;
1486 break;
1487 case CVT_RB_ADD_V_BLANK:
1488 rb_add_v_blank = opt_val;
1489 break;
1490 default:
1491 break;
1495 if (!w || !h || !fps) {
1496 fprintf(stderr, "Missing width, height and/or fps.\n");
1497 usage();
1498 std::exit(EXIT_FAILURE);
1500 if (interlaced)
1501 fps /= 2;
1502 timings t = state.calc_cvt_mode(w, h, fps, rb, interlaced, overscan, alt,
1503 rb_h_blank, rb_add_v_blank);
1504 state.print_timings("", &t, "CVT", "", true, false);
1507 struct gtf_parsed_data {
1508 unsigned w, h;
1509 double freq;
1510 double C, M, K, J;
1511 bool overscan;
1512 bool interlaced;
1513 bool secondary;
1514 bool params_from_edid;
1515 enum gtf_ip_parm ip_parm;
1518 enum gtf_opts {
1519 GTF_WIDTH = 0,
1520 GTF_HEIGHT,
1521 GTF_FPS,
1522 GTF_HORFREQ,
1523 GTF_PIXCLK,
1524 GTF_INTERLACED,
1525 GTF_OVERSCAN,
1526 GTF_SECONDARY,
1527 GTF_C2,
1528 GTF_M,
1529 GTF_K,
1530 GTF_J2,
1533 static int parse_gtf_subopt(char **subopt_str, double *value)
1535 int opt;
1536 char *opt_str;
1538 static const char * const subopt_list[] = {
1539 "w",
1540 "h",
1541 "fps",
1542 "horfreq",
1543 "pixclk",
1544 "interlaced",
1545 "overscan",
1546 "secondary",
1547 "C",
1548 "M",
1549 "K",
1550 "J",
1551 nullptr
1554 opt = getsubopt(subopt_str, (char * const *)subopt_list, &opt_str);
1556 if (opt == -1) {
1557 fprintf(stderr, "Invalid suboptions specified.\n");
1558 usage();
1559 std::exit(EXIT_FAILURE);
1561 if (opt_str == nullptr && opt != GTF_INTERLACED && opt != GTF_OVERSCAN &&
1562 opt != GTF_SECONDARY) {
1563 fprintf(stderr, "No value given to suboption <%s>.\n",
1564 subopt_list[opt]);
1565 usage();
1566 std::exit(EXIT_FAILURE);
1569 if (opt == GTF_C2 || opt == GTF_J2)
1570 *value = round(2.0 * strtod(opt_str, nullptr));
1571 else if (opt_str)
1572 *value = strtod(opt_str, nullptr);
1573 return opt;
1576 static void parse_gtf(char *optarg, gtf_parsed_data &data)
1578 memset(&data, 0, sizeof(data));
1579 data.params_from_edid = true;
1580 data.C = 40;
1581 data.M = 600;
1582 data.K = 128;
1583 data.J = 20;
1585 while (*optarg != '\0') {
1586 int opt;
1587 double opt_val;
1589 opt = parse_gtf_subopt(&optarg, &opt_val);
1591 switch (opt) {
1592 case GTF_WIDTH:
1593 data.w = round(opt_val);
1594 break;
1595 case GTF_HEIGHT:
1596 data.h = round(opt_val);
1597 break;
1598 case GTF_FPS:
1599 data.freq = opt_val;
1600 data.ip_parm = gtf_ip_vert_freq;
1601 break;
1602 case GTF_HORFREQ:
1603 data.freq = opt_val;
1604 data.ip_parm = gtf_ip_hor_freq;
1605 break;
1606 case GTF_PIXCLK:
1607 data.freq = opt_val;
1608 data.ip_parm = gtf_ip_clk_freq;
1609 break;
1610 case GTF_INTERLACED:
1611 data.interlaced = true;
1612 break;
1613 case GTF_OVERSCAN:
1614 data.overscan = true;
1615 break;
1616 case GTF_SECONDARY:
1617 data.secondary = true;
1618 break;
1619 case GTF_C2:
1620 data.C = opt_val / 2.0;
1621 data.params_from_edid = false;
1622 break;
1623 case GTF_M:
1624 data.M = round(opt_val);
1625 data.params_from_edid = false;
1626 break;
1627 case GTF_K:
1628 data.K = round(opt_val);
1629 data.params_from_edid = false;
1630 break;
1631 case GTF_J2:
1632 data.J = opt_val / 2.0;
1633 data.params_from_edid = false;
1634 break;
1635 default:
1636 break;
1640 if (!data.w || !data.h) {
1641 fprintf(stderr, "Missing width and/or height.\n");
1642 usage();
1643 std::exit(EXIT_FAILURE);
1645 if (!data.freq) {
1646 fprintf(stderr, "One of fps, horfreq or pixclk must be given.\n");
1647 usage();
1648 std::exit(EXIT_FAILURE);
1650 if (!data.secondary)
1651 data.params_from_edid = false;
1652 if (data.interlaced && data.ip_parm == gtf_ip_vert_freq)
1653 data.freq /= 2;
1656 static void show_gtf(gtf_parsed_data &data)
1658 timings t;
1660 t = state.calc_gtf_mode(data.w, data.h, data.freq, data.interlaced,
1661 data.ip_parm, data.overscan, data.secondary,
1662 data.C, data.M, data.K, data.J);
1663 calc_ratio(&t);
1664 state.print_timings("", &t, "GTF", "", true, false);
1667 int main(int argc, char **argv)
1669 char short_options[26 * 2 * 2 + 1];
1670 enum output_format out_fmt = OUT_FMT_DEFAULT;
1671 gtf_parsed_data gtf_data;
1672 int ret;
1674 while (1) {
1675 int option_index = 0;
1676 unsigned idx = 0;
1677 unsigned i, val;
1678 const timings *t;
1679 char buf[16];
1681 for (i = 0; long_options[i].name; i++) {
1682 if (!isalpha(long_options[i].val))
1683 continue;
1684 short_options[idx++] = long_options[i].val;
1685 if (long_options[i].has_arg == required_argument)
1686 short_options[idx++] = ':';
1688 short_options[idx] = 0;
1689 int ch = getopt_long(argc, argv, short_options,
1690 long_options, &option_index);
1691 if (ch == -1)
1692 break;
1694 options[ch] = 1;
1695 switch (ch) {
1696 case OptHelp:
1697 usage();
1698 return -1;
1699 case OptOutputFormat:
1700 if (!strcmp(optarg, "hex")) {
1701 out_fmt = OUT_FMT_HEX;
1702 } else if (!strcmp(optarg, "raw")) {
1703 out_fmt = OUT_FMT_RAW;
1704 } else if (!strcmp(optarg, "carray")) {
1705 out_fmt = OUT_FMT_CARRAY;
1706 } else if (!strcmp(optarg, "xml")) {
1707 out_fmt = OUT_FMT_XML;
1708 } else {
1709 usage();
1710 exit(1);
1712 break;
1713 case OptSTD: {
1714 unsigned char byte1, byte2 = 0;
1715 char *endptr;
1717 byte1 = strtoul(optarg, &endptr, 0);
1718 if (*endptr == ',')
1719 byte2 = strtoul(endptr + 1, NULL, 0);
1720 state.print_standard_timing("", byte1, byte2, false, true);
1721 break;
1723 case OptDMT:
1724 val = strtoul(optarg, NULL, 0);
1725 t = find_dmt_id(val);
1726 if (t) {
1727 sprintf(buf, "DMT 0x%02x", val);
1728 state.print_timings("", t, buf, "", true, false);
1729 } else {
1730 fprintf(stderr, "Unknown DMT code 0x%02x.\n", val);
1732 break;
1733 case OptVIC:
1734 val = strtoul(optarg, NULL, 0);
1735 t = find_vic_id(val);
1736 if (t) {
1737 sprintf(buf, "VIC %3u", val);
1738 state.print_timings("", t, buf, "", true, false);
1739 } else {
1740 fprintf(stderr, "Unknown VIC code %u.\n", val);
1742 break;
1743 case OptHDMIVIC:
1744 val = strtoul(optarg, NULL, 0);
1745 t = find_hdmi_vic_id(val);
1746 if (t) {
1747 sprintf(buf, "HDMI VIC %u", val);
1748 state.print_timings("", t, buf, "", true, false);
1749 } else {
1750 fprintf(stderr, "Unknown HDMI VIC code %u.\n", val);
1752 break;
1753 case OptCVT:
1754 parse_cvt(optarg);
1755 break;
1756 case OptGTF:
1757 parse_gtf(optarg, gtf_data);
1758 break;
1759 case ':':
1760 fprintf(stderr, "Option '%s' requires a value.\n",
1761 argv[optind]);
1762 usage();
1763 return -1;
1764 case '?':
1765 fprintf(stderr, "Unknown argument '%s'.\n",
1766 argv[optind]);
1767 usage();
1768 return -1;
1771 if (optind == argc && options[OptVersion]) {
1772 if (strlen(STRING(SHA)))
1773 printf("edid-decode SHA: %s %s\n", STRING(SHA), STRING(DATE));
1774 else
1775 printf("edid-decode SHA: not available\n");
1776 return 0;
1779 if (options[OptListEstTimings])
1780 state.list_established_timings();
1781 if (options[OptListDMTs])
1782 state.list_dmts();
1783 if (options[OptListVICs])
1784 state.cta_list_vics();
1785 if (options[OptListHDMIVICs])
1786 state.cta_list_hdmi_vics();
1788 if (options[OptListEstTimings] || options[OptListDMTs] ||
1789 options[OptListVICs] || options[OptListHDMIVICs])
1790 return 0;
1792 if (options[OptCVT] || options[OptDMT] || options[OptVIC] ||
1793 options[OptHDMIVIC] || options[OptSTD])
1794 return 0;
1796 if (options[OptGTF] && (!gtf_data.params_from_edid || optind == argc)) {
1797 show_gtf(gtf_data);
1798 return 0;
1801 if (optind == argc)
1802 ret = edid_from_file("-", stdout);
1803 else
1804 ret = edid_from_file(argv[optind], argv[optind + 1] ? stderr : stdout);
1806 if (ret && options[OptPhysicalAddress]) {
1807 printf("f.f.f.f\n");
1808 return 0;
1810 if (optind < argc - 1)
1811 return ret ? ret : edid_to_file(argv[optind + 1], out_fmt);
1813 if (options[OptGTF]) {
1814 timings t;
1816 // Find the Secondary Curve
1817 state.preparse_detailed_block(edid + 0x36);
1818 state.preparse_detailed_block(edid + 0x48);
1819 state.preparse_detailed_block(edid + 0x5a);
1820 state.preparse_detailed_block(edid + 0x6c);
1822 t = state.calc_gtf_mode(gtf_data.w, gtf_data.h, gtf_data.freq,
1823 gtf_data.interlaced, gtf_data.ip_parm,
1824 gtf_data.overscan);
1825 unsigned hbl = t.hfp + t.hsync + t.hbp;
1826 unsigned htotal = t.hact + hbl;
1827 double hor_freq_khz = htotal ? (double)t.pixclk_khz / htotal : 0;
1829 if (state.base.supports_sec_gtf &&
1830 hor_freq_khz >= state.base.sec_gtf_start_freq) {
1831 t = state.calc_gtf_mode(gtf_data.w, gtf_data.h, gtf_data.freq,
1832 gtf_data.interlaced, gtf_data.ip_parm,
1833 gtf_data.overscan, true,
1834 state.base.C, state.base.M,
1835 state.base.K, state.base.J);
1837 calc_ratio(&t);
1838 if (t.hfp <= 0)
1839 state.print_timings("", &t, "GTF", "INVALID: Hfront <= 0", true, false);
1840 else
1841 state.print_timings("", &t, "GTF", "", true, false);
1842 return 0;
1845 return ret ? ret : state.parse_edid();
1848 #ifdef __EMSCRIPTEN__
1850 * The surrounding JavaScript implementation will call this function
1851 * each time it wants to decode an EDID. So this should reset all the
1852 * state and start over.
1854 extern "C" int parse_edid(const char *input)
1856 for (unsigned i = 0; i < EDID_MAX_BLOCKS + 1; i++) {
1857 s_msgs[i][0].clear();
1858 s_msgs[i][1].clear();
1860 options[OptCheck] = 1;
1861 options[OptPreferredTimings] = 1;
1862 options[OptNativeTimings] = 1;
1863 state = edid_state();
1864 int ret = edid_from_file(input, stderr);
1865 return ret ? ret : state.parse_edid();
1867 #endif