]> mj.ucw.cz Git - edid.git/blob - edid.c
Released as v1.2
[edid.git] / edid.c
1 /*
2  *      Parse VESA Extended Display Identification Data
3  *
4  *      (c) 2011--2015 Martin Mares <mj@ucw.cz>
5  *
6  *      This program can be distributed and used under the terms
7  *      of the GNU General Public License version 2 or later.
8  */
9
10 #include <stdio.h>
11 #include <stdlib.h>
12 #include <stdarg.h>
13 #include <string.h>
14 #include <unistd.h>
15
16 typedef unsigned int uns;
17 typedef unsigned char byte;
18
19 static byte edid[128];
20 static int version;
21 static uns block;
22 static uns num_ext_blocks;
23
24 static void
25 #ifdef __GNUC__
26         __attribute__((format(printf,1,2)))
27         __attribute__((noreturn))
28 #endif
29 die(char *msg, ...)
30 {
31   va_list args;
32   va_start(args, msg);
33   fprintf(stderr, "edid: ");
34   vfprintf(stderr, msg, args);
35   fputc('\n', stderr);
36   va_end(args);
37   exit(1);
38 }
39
40 static uns
41 u16_le(byte *p)
42 {
43   return p[0] | (p[1] << 8);
44 }
45
46 static uns
47 u32_le(byte *p)
48 {
49   return u16_le(p) | (u16_le(p+2) << 16);
50 }
51
52 static uns
53 B(uns x)
54 {
55   return x ? '+' : '-';
56 }
57
58 /*** EDID Basics ***/
59
60 static void
61 show_model(byte *p)
62 {
63   printf("Model: %c%c%c %04x #%u\n",
64         ('A' - 1 + ((p[0] >> 2) & 0x1f)),
65         ('A' - 1 + ((p[0] & 3) << 3) | ((p[1] & 0xe0) >> 5)),
66         ('A' - 1 + (p[1] & 0x1f)),
67         u16_le(p+2),
68         u32_le(p+4));
69
70   uns y = p[9] + 1990;
71   if (p[8] == 0xff)
72     printf("Model year: %d\n", y);
73   else if (p[8])
74     printf("Manufactured: %d week %d\n", y, p[8]);
75   else
76     printf("Manufactured: %d, week unspecified\n", y);
77 }
78
79 static void
80 show_basic(byte *p)
81 {
82   uns input = p[0];
83   if (input & 0x80)
84     {
85       printf("Input: Digital,");
86       if (version < 0x104)
87         printf(" DFP%c\n", B(input & 1));
88       else
89         {
90           uns depth = (input >> 4) & 7;
91           static const char * const depths[] = { "undefined", "6", "8", "10", "12", "14", "16", "RFU7" };
92
93           uns iface = input & 0x0f;
94           static const char * const ifaces[] = {
95             "undefined", "DVI", "HDMI-a", "HDMI-b", "MDDI", "DisplayPort", "RFU6", "RFU7",
96             "RFU8", "RFU9", "RFU10", "RFU11", "RFU12", "RFU13", "RFU14", "RFU16"
97           };
98
99           printf(" Depth=%s Iface=%s\n", depths[depth], ifaces[iface]);
100         }
101     }
102   else
103     printf("Input: Analog, SigLev=%d Setup%c SepSync%c CompSync%c GreenSync%c SerrSync%c\n",
104         ((input >> 5) & 3),
105         B(input & 0x10),
106         B(input & 0x08),
107         B(input & 0x04),
108         B(input & 0x02),
109         B(input & 0x01));
110
111   if (!p[1] && !p[2])
112     printf("Image size: undefined\n");
113   else if (!p[2])
114     printf("Image aspect ratio: %u.%02u:1\n", 1 + p[1]/100, p[1] % 100);
115   else if (!p[1])
116     printf("Image aspect ratio: 1:%u.%02u\n", 1 + p[2]/100, p[2] % 100);
117   else
118     printf("Image size: %dx%dcm\n", p[1], p[2]);
119
120   if (p[3] == 0xff)
121     printf("Gamma: not given\n");
122   else
123     printf("Gamma: %d.%02d\n", 1 + p[3]/100, p[3] % 100);
124
125   uns flags = p[4];
126   static const char * const color_types[4] = { "Mono/Gray", "RGB", "NonRGB", "Unknown" };
127   static const char * const color_encodings[4] = {
128     "RGB444",
129     "RGB444/YCrCb444",
130     "RGB444/YCrCb422",
131     "RGB444/YCrCb444/YCrCb422",
132   };
133   printf("Flags: Suspend%c Standby%c ActiveOff%c",
134         B(flags & 0x80),
135         B(flags & 0x40),
136         B(flags & 0x20));
137   if (version >= 0x104 && (input & 0x80))
138     printf(" Colors=%s", color_encodings[(flags >> 3) & 3]);
139   else
140     printf(" Colors=%s", color_types[(flags >> 3) & 3]);
141   printf(" HasSRGB%c", B(flags & 0x04));
142   printf(" %s%c", (version >= 0x104 ? "PreferredIsNative" : "PrefTiming"), B(flags & 0x02));
143   printf(" %s%c\n", (version >= 0x104 ? "ContinuousFreq" : "GTF"), B(flags & 0x01));
144 }
145
146 static void
147 show_color(byte *p)
148 {
149   uns rx = 4*p[2] | ((p[0] >> 6) & 3);
150   uns ry = 4*p[3] | ((p[0] >> 4) & 3);
151   uns gx = 4*p[4] | ((p[0] >> 2) & 3);
152   uns gy = 4*p[5] | ((p[0]     ) & 3);
153   uns bx = 4*p[5] | ((p[1] >> 6) & 3);
154   uns by = 4*p[7] | ((p[1] >> 4) & 3);
155   uns wx = 4*p[8] | ((p[1] >> 2) & 3);
156   uns wy = 4*p[9] | ((p[1]     ) & 3);
157 #define C(c) ((double)c / 1024)
158   printf("Chromaticity: R=(%.3f,%.3f) G=(%.3f,%.3f) B=(%.3f,%.3f) W=(%.3f,%.3f)\n",
159         C(rx), C(ry), C(gx), C(gy), C(bx), C(by), C(wx), C(wy));
160 #undef C
161 }
162
163 static void
164 show_timings(byte *p)
165 {
166   static const char * const estab_timings[24] = {
167         "720x400@70",
168         "720x400@88",
169         "640x480@60",
170         "640x480@67",
171         "640x480@72",
172         "640x480@75",
173         "800x600@56",
174         "800x600@60",
175         "800x600@72",
176         "800x600@75",
177         "832x624@75",
178         "1024x768@87i",
179         "1024x768@60",
180         "1024x768@70",
181         "1024x768@75",
182         "1280x1024@75",
183         "1152x870@75",
184         "MFG6",
185         "MFG5",
186         "MFG4",
187         "MFG3",
188         "MFG2",
189         "MFG1",
190         "MFG0",
191   };
192   printf("Basic timings:");
193   for (int i=0; i<24; i++)
194     if (p[i/8] & (0x80 >> (i%8)))
195       printf(" %s", estab_timings[i]);
196   printf("\n");
197
198   printf("Standard timings:");
199   for (int i=0; i<8; i++)
200     {
201       byte *q = p + 3 + 2*i;
202       if (q[0] == 1 && q[1] == 1)
203         continue;
204       uns h = (31 + q[0]) * 8;
205       uns v;
206       switch (q[1] >> 6)
207         {
208         case 0:
209           if (version < 0x0103)
210             v = h;
211           else
212             v = 10*h/16;
213           break;
214         case 1:
215           v = 3*h/4;
216           break;
217         case 2:
218           v = 4*h/5;
219           break;
220         default:
221           v = 9*h/16;
222           break;
223         }
224       printf(" %dx%d@%d", h, v, 60 + (q[1] & 0x3f));
225     }
226   printf("\n");
227 }
228
229 /*** EDID Descriptors ***/
230
231 static void
232 show_detailed_timing(byte *p)
233 {
234   uns hactive = p[2] | ((p[4] << 4) & 0xf00);
235   uns hblank  = p[3] | ((p[4] << 8) & 0xf00);
236   uns vactive = p[5] | ((p[7] << 4) & 0xf00);
237   uns vblank  = p[6] | ((p[7] << 8) & 0xf00);
238   uns hs_offset = p[8] | ((p[11] << 2) & 0x300);
239   uns hs_width  = p[9] | ((p[11] << 4) & 0x300);
240   uns vs_offset = (p[10] >> 4) | ((p[11] << 2) & 0x30);
241   uns vs_width  = (p[10] & 0xf) | ((p[11] << 4) & 0x30);
242   uns hsize   = p[12] | ((p[14] << 4) & 0xf00);
243   uns vsize   = p[13] | ((p[14] << 8) & 0xf00);
244   uns hborder = p[15];
245   uns vborder = p[16];
246
247   printf("Detailed timing: %dx%d (%.1f Hz)\n",
248         hactive, vactive,
249         u16_le(p) * 10000. / (hactive+hblank) / (vactive+vblank));
250   printf("\tPixClk: %.2fMHz\n", u16_le(p) / 100.);
251   printf("\tH: Active=%d Blank=%d SyncOffset=%d SyncWidth=%d Border=%d\n",
252         hactive, hblank, hs_offset, hs_width, hborder);
253   printf("\tV: Active=%d Blank=%d SyncOffset=%d SyncWidth=%d Border=%d\n",
254         vactive, vblank, vs_offset, vs_width, vborder);
255   printf("\tSize: %dx%dmm\n", hsize, vsize);
256
257   uns flags = p[17];
258   uns stereo = ((flags >> 4) & 6) | (flags & 1);
259   static const char * const stereo_types[8] = {
260     "none",
261     "none",
262     "FieldSeq/RightOn1",
263     "2-wayIL/RightEven",
264     "FieldSeq/LeftOn1",
265     "2-wayIL/LeftEven",
266     "4-wayIL",
267     "SideBySide",
268   };
269   printf("\tFlags: Interlace%c Stereo=%s\n", B(flags & 0x80), stereo_types[stereo]);
270   if (stereo >= 2)
271     printf("\tStereo mode: #%d\n", stereo);
272
273   uns sync = (flags >> 3) & 3;
274
275   static const char * const sync_types[4] = { "AnalogComposite", "BipolarAnalogComposite", "DigitalComposite", "DigitalSeparate" };
276   printf("\tSync: %s", sync_types[sync]);
277   if (sync < 3)
278     printf(" Serrate%c", B(flags & 4));
279   else
280     printf(" VPolarity%c", B(flags & 4));
281   if (sync < 2)
282     printf(" OnRGB%c", B(flags & 2));
283   else if (sync == 2)
284     printf(" Polarity%c", B(flags & 2));
285   else
286     printf(" HPolarity%c", B(flags & 2));
287   printf("\n");
288
289   if (hsize && vsize)
290     printf("\tCalculated DPI: %dx%d\n",
291         (int)(hactive / (hsize / 25.4)),
292         (int)(vactive / (vsize / 25.4)));
293 }
294
295 static void
296 show_ascii(char *field, byte *p, uns len)
297 {
298   printf("%s: ", field);
299   while (len--)
300     {
301       if (*p == '\n')
302         break;
303       else if (*p >= 0x20 && *p <= 0x7e)
304         putchar(*p);
305       else
306         printf("<%02x>", *p);
307       p++;
308     }
309   putchar('\n');
310 }
311
312 static void
313 show_limits(byte *p)
314 {
315   uns flags = p[4];
316   uns voffset = flags & 3;
317   uns hoffset = (flags >> 2) & 3;
318
319   printf("Limits: Vert=%d-%dHz Horiz=%d-%dkHz PixClk=%dMHz",
320     p[5] + (voffset == 3 ? 255 : 0),
321     p[6] + (voffset & 2 ? 255 : 0),
322     p[7] + (hoffset == 3 ? 255 : 0),
323     p[8] + (hoffset & 2 ? 255 : 0),
324     p[9]*10);
325
326   switch (p[10])
327     {
328     case 0:             // No secondary timing formula
329       printf(" (no formula)\n");
330       break;
331     case 1:             // Range limits only
332       printf(" (range only)\n");
333       break;
334     case 2:             // Secondary GTF
335       printf(" (2ndary GTF)\n");
336       printf("\tSecondary GTF parameters: fStart=%dkHz C=%.1f M=%d K=%d J=%.1f\n",
337         p[12]*2, p[13]/2., u16_le(p+14), p[16], p[17]/2.);
338       break;
339     case 4:
340       printf(" (CVT v%d.%d)\n", p[11] >> 4, p[11] & 0x0f);
341       printf("\t[...]\n");
342       break;
343     default:
344       printf(" (unknown formula #%02x)\n", p[10]);
345     }
346 }
347
348 static void
349 show_descriptor(byte *p)
350 {
351   switch (p[3])
352     {
353     case 0xff:          // Serial number
354       show_ascii("Serial", p+5, 13);
355       break;
356     case 0xfe:          // ASCII data
357       show_ascii("Comment", p+5, 13);
358       break;
359     case 0xfd:          // Range limits
360       show_limits(p);
361       break;
362     case 0xfc:          // Product name
363       show_ascii("Product name", p+5, 13);
364       break;
365     case 0xfb:          // Color point
366       printf("Color point: [...]\n");
367       break;
368     case 0xfa:          // More standard timings
369       printf("More standard timings: [...]\n");
370       break;
371     case 0xf9:
372       printf("Display color management: [...]\n");
373       break;
374     case 0xf8:
375       printf("CVT-3: [...]\n");
376       break;
377     case 0xf7:
378       printf("Established timings 3: [...]\n");
379       break;
380     case 0x10:          // Dummy descriptor -- entry unused
381       return;
382     default:
383       printf("%s descriptor type %02x\n", (p[3] <= 0x0f ? "Vendor-specific" : "Unknown"), p[3]);
384     }
385 }
386
387 static void
388 check_sum(void)
389 {
390   byte sum = 0;
391   for (int i=0; i<128; i++)
392     sum += edid[i];
393   if (sum)
394     printf("Invalid checksum: off by %02x\n", sum);
395 }
396
397 static void
398 show_edid(void)
399 {
400   if (memcmp(edid, "\000\377\377\377\377\377\377\000", 8))
401     die("Invalid EDID signature, giving up");
402   int ver = edid[0x12];
403   int rev = edid[0x13];
404   printf("EDID %d.%d\n", ver, rev);
405   version = (ver << 8) | rev;
406
407   if (version >= 0x200)
408     die("Unsupported version");
409   check_sum();
410
411   show_model(edid + 8);
412   show_basic(edid + 0x14);
413   show_color(edid + 0x19);
414   show_timings(edid + 0x23);
415   for (int i=0; i<4; i++)
416     {
417       byte *p = edid + 0x36 + 18*i;
418       if (p[0] || p[1])
419         show_detailed_timing(p);
420       else
421         show_descriptor(p);
422     }
423
424   num_ext_blocks = edid[0x7e];
425 }
426
427 /*** Extensions ***/
428
429 static void ext_block_map(void)
430 {
431   for (uns i=1; i<=0x7e; i++)
432     if (edid[i])
433       printf("Block %u: extension %u\n", i, edid[i]);
434 }
435
436 static void ext_hdmi(byte *p, uns len)
437 {
438   if (len < 6)
439     {
440       printf("\t!!! Truncated\n");
441       return;
442     }
443   printf("\tPhysical address: %u.%u.%u.%u\n", p[4], p[5], p[6], p[7]);
444
445   if (len < 7)
446     return;
447   uns f = p[6];
448   printf("\tFlags: AI%c 48bpp%c 36bpp%c 30bpp%c DeepColor-YCbCr444%c DVIDual%c\n",
449     B(f & 0x80), B(f & 0x40), B(f & 0x20), B(f & 0x10),
450     B(f & 0x08), B(f & 0x01));
451
452   if (len < 8)
453     return;
454   printf("\tMax TMDS Clock [MHz]: %u\n", 5 * p[7]);
455
456   if (len < 9)
457     return;
458   uns f2 = p[8];
459
460   uns i = 9;
461   if (f2 & 0x80)                // Latency fields present
462     {
463       if (i+2 > len)
464         {
465           printf("\t!!! Truncated latency fields\n");
466           return;
467         }
468       printf("\tLatency [ms]:");
469       if (p[i] && p[i] != 0xff)
470         printf(" Video=%u", (p[i]-1)*2);
471       if (p[i+1] && p[i+1] != 0xff)
472         printf(" Audio=%u", (p[i+1]-1)*2);
473       i += 2;
474
475       if (f2 & 0x40)            // Interlaced latency present
476         {
477           if (i+2 > len)
478             {
479               printf("\t!!! Truncated latency fields\n");
480               return;
481             }
482           printf("\tInterlaced latency [ms]:");
483           if (p[i] && p[i] != 0xff)
484             printf(" Video=%u", (p[i]-1)*2);
485           if (p[i+1] && p[i+1] != 0xff)
486             printf(" Audio=%u", (p[i+1]-1)*2);
487           i += 2;
488         }
489     }
490 }
491
492 // A table of CEA video modes taken from https://en.wikipedia.org/wiki/Extended_Display_Identification_Data
493 static const char * const cea_modes[] = {
494   [1] = "640x480p@60 4:3",
495   [2] = "720x480p@60 4:3",
496   [3] = "720x480p@60 16:9",
497   [4] = "1280x720p@60 16:9",
498   [5] = "1920x1080i@60 16:9",
499   [6] = "720(1440)x480i@60 4:3",
500   [7] = "720(1440)x480i@60 16:9",
501   [8] = "720(1440)x240p@60 4:3",
502   [9] = "720(1440)x240p@60 16:9",
503   [10] = "(2880)x480i@60 4:3",
504   [11] = "(2880)x480i@60 16:9",
505   [12] = "(2880)x240p@60 4:3",
506   [13] = "(2880)x240p@60 16:9",
507   [14] = "1440x480p@60 4:3",
508   [15] = "1440x480p@60 16:9",
509   [16] = "1920x1080p@60 16:9",
510   [17] = "720x576p@50 4:3",
511   [18] = "720x576p@50 16:9",
512   [19] = "1280x720p@50 16:9",
513   [20] = "1920x1080i@50 16:9",
514   [21] = "720(1440)x576i@50 4:3",
515   [22] = "720(1440)x576i@50 16:9",
516   [23] = "720(1440)x288p@50 4:3",
517   [24] = "720(1440)x288p@50 16:9",
518   [25] = "(2880)x576i@50 4:3",
519   [26] = "(2880)x576i@50 16:9",
520   [27] = "(2880)x288p@50 4:3",
521   [28] = "(2880)x288p@50 16:9",
522   [29] = "1440x576p@50 4:3",
523   [30] = "1440x576p@50 16:9",
524   [31] = "1920x1080p@50 16:9",
525   [32] = "1920x1080p@24 16:9",
526   [33] = "1920x1080p@25 16:9",
527   [34] = "1920x1080p@30 16:9",
528   [35] = "(2880)x480p@60 4:3",
529   [36] = "(2880)x480p@60 16:9",
530   [37] = "(2880)x576p@50 4:3",
531   [38] = "(2880)x576p@50 16:9",
532   [39] = "1920x1080i@50 16:9",
533   [40] = "1920x1080i@100 16:9",
534   [41] = "1280x720p@100 16:9",
535   [42] = "720x576p@100 4:3",
536   [43] = "720x576p@100 16:9",
537   [44] = "720(1440)x576i@100 4:3",
538   [45] = "720(1440)x576i@100 16:9",
539   [46] = "1920x1080i@120 16:9",
540   [47] = "1280x720p@120 16:9",
541   [48] = "720x480p@120 4:3",
542   [49] = "720x480p@120 16:9",
543   [50] = "720(1440)x480i@120 4:3",
544   [51] = "720(1440)x480i@120 16:9",
545   [52] = "720x576p@200 4:3",
546   [53] = "720x576p@200 16:9",
547   [54] = "720(1440)x576i@200 4:3",
548   [55] = "720(1440)x576i@200 16:9",
549   [56] = "720x480p@240 4:3",
550   [57] = "720x480p@240 16:9",
551   [58] = "720(1440)x480i@240 4:3",
552   [59] = "720(1440)x480i@240 16:9",
553   [60] = "1280x720p@24 16:9",
554   [61] = "1280x720p@25 16:9",
555   [62] = "1280x720p@30 16:9",
556   [63] = "1920x1080p@120 16:9",
557   [64] = "1920x1080p@100 16:9",
558   [65] = "1280x720p@24 64:27",
559   [66] = "1280x720p@25 64:27",
560   [67] = "1280x720p@30 64:27",
561   [68] = "1280x720p@50 64:27",
562   [69] = "1280x720p@60 64:27",
563   [70] = "1280x720p@100 64:27",
564   [71] = "1280x720p@120 64:27",
565   [72] = "1920x1080p@24 64:27",
566   [73] = "1920x1080p@25 64:27",
567   [74] = "1920x1080p@30 64:27",
568   [75] = "1920x1080p@50 64:27",
569   [76] = "1920x1080p@60 64:27",
570   [77] = "1920x1080p@100 64:27",
571   [78] = "1920x1080p@120 64:27",
572   [79] = "1680x720p@24 64:27",
573   [80] = "1680x720p@25 64:27",
574   [81] = "1680x720p@30 64:27",
575   [82] = "1680x720p@50 64:27",
576   [83] = "1680x720p@60 64:27",
577   [84] = "1680x720p@100 64:27",
578   [85] = "1680x720p@120 64:27",
579   [86] = "2560x1080p@24 64:27",
580   [87] = "2560x1080p@25 64:27",
581   [88] = "2560x1080p@30 64:27",
582   [89] = "2560x1080p@50 64:27",
583   [90] = "2560x1080p@60 64:27",
584   [91] = "2560x1080p@100 64:27",
585   [92] = "2560x1080p@120 64:27",
586   [93] = "3840x2160p@24 16:9",
587   [94] = "3840x2160p@25 16:9",
588   [95] = "3840x2160p@30 16:9",
589   [96] = "3840x2160p@50 16:9",
590   [97] = "3840x2160p@60 16:9",
591   [98] = "4096x2160p@24 256:135",
592   [99] = "4096x2160p@25 256:135",
593   [100] = "4096x2160p@30 256:135",
594   [101] = "4096x2160p@50 256:135",
595   [102] = "4096x2160p@60 256:135",
596   [103] = "3840x2160p@24 64:27",
597   [104] = "3840x2160p@25 64:27",
598   [105] = "3840x2160p@30 64:27",
599   [106] = "3840x2160p@50 64:27",
600   [107] = "3840x2160p@60 64:27",
601 };
602
603 static void ext_cea_data(uns pos, uns max_pos)
604 {
605   while (pos < max_pos)
606     {
607       byte *p = edid + pos;
608       uns type = p[0] >> 5;
609       uns len = (p[0] & 31) + 1;
610       if (pos + len > max_pos)
611         {
612           printf("!!! Truncated data block (pos=%u, type=%02x, len=%u)\n", pos, type, len);
613           break;
614         }
615
616       switch (type)
617         {
618         case 1:                 // Audio
619           printf("Audio formats:\n");
620           for (uns j=1; j < len; j += 3)
621             {
622               byte *q = p + j;
623               uns fmt = (q[0] >> 3) & 0x0f;
624               static const char * const fmt_names[16] = {
625                 "RFU0", "LPCM", "AC-3", "MPEG1",
626                 "MP3", "MPEG2", "AAC", "DTS",
627                 "ATRAC", "SACD", "DD+", "DTS+HD",
628                 "MLP", "DST", "WMApro", "RFU15",
629               };
630               uns chans = (q[0] & 7) + 1;
631               printf("\t%s, Channels=%u\n", fmt_names[fmt], chans);
632
633               uns rates = q[1];
634               printf("\t\tSample rates [kHz]: 192%c 176%c 96%c 88%c 48%c 44%c 32%c\n",
635                 B(rates & 0x40), B(rates & 0x20), B(rates & 0x10),
636                 B(rates & 0x08), B(rates & 0x04), B(rates & 0x02), B(rates & 0x01));
637
638               uns br = q[2];
639               if (fmt == 1)
640                 printf("\t\tBit depths: 24%c 20%c 16%c\n", B(br & 4), B(br & 2), B(br & 1));
641               else
642                 printf("\t\tMax bit rate [kHz]: %u\n", br*8);
643             }
644           break;
645         case 2:                 // Video
646           printf("Video formats:\n");
647           for (uns j=1; j < len; j++)
648             {
649               uns vf = p[j] & 0x7f;
650               if (vf && vf < sizeof(cea_modes) / sizeof(cea_modes[0]))
651                 printf("\t%s", cea_modes[vf]);
652               else
653                 printf("\t#%02x", vf);
654               if (p[j] & 0x80)
655                 printf(" (native)");
656               printf("\n");
657             }
658           break;
659         case 3:                 // Vendor specific
660           {
661             printf("Vendor-specific: ");
662             if (len < 4)
663               {
664                 printf("\t!!! Truncated\n");
665                 break;
666               }
667             uns oid = p[1] | (p[2] << 8) | (p[3] << 16);
668             if (oid == 0x000c03)
669               {
670                 printf("HDMI\n");
671                 ext_hdmi(p, len);
672               }
673             else
674               printf("Vendor %06x\n", oid);
675             break;
676           }
677         case 4:                 // Speaker allocation
678           printf("Speakers present:\n");
679           if (len < 4)
680             {
681               printf("\t!!! Truncated\n");
682               break;
683             }
684           static const char * const spk_types[8] = {
685             "Front Left+Right", "LFE", "Front Center", "Rear Left+Right",
686             "Rear Center", "Front Left+Right Center", "Rear Left+Right Center", "RFU7",
687           };
688           for (uns j=0; j<8; j++)
689             if (p[1] & (1 << j))
690               printf("\t%s\n", spk_types[j]);
691           break;
692         default:
693           printf("Unknown data block #%02x (pos=%u, len=%u)\n", type, pos, len);
694         }
695
696       pos += len;
697     }
698 }
699
700 static void ext_cea_dtd(uns pos, uns max_pos)
701 {
702   // Detailed Timing Descriptors follow the same syntax as in basic EDID
703   while (pos + 18 <= max_pos && edid[pos] && edid[pos+1])
704     {
705       show_detailed_timing(edid + pos);
706       pos += 18;
707     }
708 }
709
710 static void ext_cea_861(void)
711 {
712   uns version = edid[1];
713   uns dtd_pos = edid[2];
714   uns flags = edid[3];
715
716   if (version >= 2)
717     printf("Flags: Underscan%c BasicAudio%c YCbCr444%c YCbCr422%c\n",
718       B(flags & 0x80),
719       B(flags & 0x40),
720       B(flags & 0x20),
721       B(flags & 0x10));
722
723   ext_cea_data(4, (dtd_pos < 127 ? dtd_pos : 127));
724   ext_cea_dtd(dtd_pos, 127);
725 }
726
727 struct extension {
728   const char *name;
729   void (*parser)(void);
730 };
731
732 static const struct extension ext_table[0x100] = {
733   [0x02] = { "CEA 861",                         ext_cea_861 },
734   [0x10] = { "Video Timing Block",              NULL },
735   [0x40] = { "Display Information",             NULL },
736   [0x50] = { "Localized Strings",               NULL },
737   [0x60] = { "Digital Packet Video Link",       NULL },
738   [0xf0] = { "Block map",                       ext_block_map },
739   [0xff] = { "Vendor-specific",                 NULL },
740 };
741
742 static void show_ext(void)
743 {
744   byte ext = edid[0];
745   const struct extension *e = &ext_table[ext];
746
747   printf("\n>> Extension block %u: ", block);
748   if (e->name)
749     printf("%s", e->name);
750   else
751     printf("Extension #%02x", ext);
752   if (ext != 0xf0)
753     printf(" (version %02x)", edid[1]);
754   printf("\n\n");
755
756   check_sum();
757
758   if (e->parser)
759     e->parser();
760   else
761     printf("!!! Not decoded yet\n");
762 }
763
764 /*** Main loop ***/
765
766 static void read_binary(void)
767 {
768   int c = read(0, edid, 128);
769   if (c < 0)
770     die("Read error: %m");
771   else if (c < 128)
772     die("Read only %d bytes, need 128", c);
773 }
774
775 static void
776 read_xorg_log(void)
777 {
778   char line[1024];
779   static int mode = 0;
780   int cnt = 0;
781
782   while (fgets(line, sizeof(line), stdin))
783     {
784       if (!mode)
785         {
786           if (strstr(line, "EDID (in hex):"))
787             mode = 1;
788         }
789       else
790         {
791           char *c = strchr(line, '\n');
792           if (!c)
793             goto parseerr;
794           cnt += 16;
795           for (int i=0; i<32; i++)
796             {
797               if (c == line)
798                 goto parseerr;
799               int x = *--c;
800               if (x >= '0' && x <= '9')
801                 x -= '0';
802               else if (x >= 'A' && x <= 'F')
803                 x -= 'A' - 10;
804               else if (x >= 'a' && x <= 'f')
805                 x -= 'a' - 10;
806               else
807                 goto parseerr;
808               if (!(i % 2))
809                 edid[cnt-1-i/2] = x;
810               else
811                 edid[cnt-1-i/2] |= x << 4;
812             }
813           if (c > line && c[-1] != ' ' && c[-1] != '\t')
814             goto parseerr;
815           if (cnt == 128)
816             return;
817         }
818     }
819   if (!cnt)
820     die("No EDID log found");
821   else
822     die("EDID log found, but it is too short (only %d bytes)", cnt);
823
824 parseerr:
825   die("EDID log parse error at: %s", line);
826 }
827
828 static void
829 show_hex(void)
830 {
831   printf("\n");
832   for (int i=0; i<128; i+=16)
833     {
834       printf("@%02x:", i);
835       for (int j=0; j<16; j++)
836         printf(" %02x", edid[i+j]);
837       printf("\n");
838     }
839 }
840
841 static void
842 usage(void)
843 {
844   fprintf(stderr, "Usage: edid <options>\n\n\
845 Options:\n\
846 -l\t\tParse input as Xorg log file and try to find EDID dump there\n\
847 -x\t\tShow hexdump of the whole EDID block\n\
848 ");
849   exit(1);
850 }
851
852 int main(int argc, char **argv)
853 {
854   int opt;
855   int hex_mode = 0;
856   int log_mode = 0;
857
858   while ((opt = getopt(argc, argv, "xl")) >= 0)
859     switch (opt)
860       {
861       case 'l':
862         log_mode = 1;
863         break;
864       case 'x':
865         hex_mode = 1;
866         break;
867       default:
868         usage();
869       }
870   if (optind < argc)
871     usage();
872
873   while (block <= num_ext_blocks)
874     {
875       if (log_mode)
876         read_xorg_log();
877       else
878         read_binary();
879
880       if (!block)
881         show_edid();
882       else
883         show_ext();
884
885       if (hex_mode)
886         show_hex();
887       block++;
888     }
889
890   return 0;
891 }