--- /dev/null
+/*
+ * Parse VESA Extended Display Identification Data
+ *
+ * (c) 2011 Martin Mares <mj@ucw.cz>
+ *
+ * This program can be distributed and used under the terms
+ * of the GNU General Public License version 2 or later.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <unistd.h>
+
+typedef unsigned int uns;
+typedef unsigned char byte;
+
+static byte edid[128];
+static int version;
+
+static void
+#ifdef __GNUC__
+ __attribute__((format(printf,1,2)))
+ __attribute__((noreturn))
+#endif
+die(char *msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+ fprintf(stderr, "edid: ");
+ vfprintf(stderr, msg, args);
+ fputc('\n', stderr);
+ va_end(args);
+ exit(1);
+}
+
+static uns
+u16_le(byte *p)
+{
+ return p[0] | (p[1] << 8);
+}
+
+static uns
+u32_le(byte *p)
+{
+ return u16_le(p) | (u16_le(p+2) << 16);
+}
+
+static uns
+B(uns x)
+{
+ return x ? '+' : '-';
+}
+
+static void
+show_model(byte *p)
+{
+ printf("Model: %c%c%c %04x #%u\n",
+ ('A' - 1 + ((p[0] >> 2) & 0x1f)),
+ ('A' - 1 + ((p[0] & 3) << 3) | ((p[1] & 0xe0) >> 5)),
+ ('A' - 1 + (p[1] & 0x1f)),
+ u16_le(p+2),
+ u32_le(p+4));
+ printf("Manufactured: %d week %d\n", p[9] + 1990, p[8]);
+}
+
+static void
+show_basic(byte *p)
+{
+ uns input = p[0];
+ if (input & 0x80)
+ printf("Input: Digital DFP%c\n", B(input & 1));
+ else
+ printf("Input: Analog SigLev=%d Setup%c SepSync%c CompSync%c GreenSync%c SerrSync%c\n",
+ ((input >> 5) & 3),
+ B(input & 0x10),
+ B(input & 0x08),
+ B(input & 0x04),
+ B(input & 0x02),
+ B(input & 0x01));
+
+ printf("Image size: %dx%dcm\n", p[1], p[2]);
+ if (p[3] == 0xff)
+ printf("Gamma: not given\n");
+ else
+ printf("Gamma: %d.%02d\n", (p[3] / 100) + 1, p[3] % 100);
+
+ uns flags = p[4];
+ static const char * const types[4] = { "Mono/Gray", "RGB", "NonRGB", "Unknown" };
+ printf("Flags: Suspend%c Standby%c ActiveOff%c Colors=%s HasSRGB%c PrefTiming%c GTF%c\n",
+ B(flags & 0x80),
+ B(flags & 0x40),
+ B(flags & 0x20),
+ types[(flags >> 3) & 3],
+ B(flags & 0x04),
+ B(flags & 0x02),
+ B(flags & 0x01));
+}
+
+static void
+show_color(byte *p)
+{
+ uns rx = 4*p[2] | ((p[0] >> 6) & 3);
+ uns ry = 4*p[3] | ((p[0] >> 4) & 3);
+ uns gx = 4*p[4] | ((p[0] >> 2) & 3);
+ uns gy = 4*p[5] | ((p[0] ) & 3);
+ uns bx = 4*p[5] | ((p[1] >> 6) & 3);
+ uns by = 4*p[7] | ((p[1] >> 4) & 3);
+ uns wx = 4*p[8] | ((p[1] >> 2) & 3);
+ uns wy = 4*p[9] | ((p[1] ) & 3);
+#define C(c) ((double)c / 1024)
+ printf("Chromaticity: R=(%.3f,%.3f) G=(%.3f,%.3f) B=(%.3f,%.3f) W=(%.3f,%.3f)\n",
+ C(rx), C(ry), C(gx), C(gy), C(bx), C(by), C(wx), C(wy));
+#undef C
+}
+
+static void
+show_timings(byte *p)
+{
+ static const char * const estab_timings[24] = {
+ "720x400@70",
+ "720x400@88",
+ "640x480@60",
+ "640x480@67",
+ "640x480@72",
+ "640x480@75",
+ "800x600@56",
+ "800x600@60",
+ "800x600@72",
+ "800x600@75",
+ "832x624@75",
+ "1024x768@87i",
+ "1024x768@60",
+ "1024x768@70",
+ "1024x768@75",
+ "1280x1024@75",
+ "MFG7",
+ "MFG6",
+ "MFG5",
+ "MFG4",
+ "MFG3",
+ "MFG2",
+ "MFG1",
+ "MFG0",
+ };
+ printf("Basic timings:");
+ for (int i=0; i<24; i++)
+ if (p[i/8] & (0x80 >> (i%8)))
+ printf(" %s", estab_timings[i]);
+ printf("\n");
+
+ printf("Extended timings:");
+ for (int i=0; i<8; i++)
+ {
+ byte *q = p + 3 + 2*i;
+ if (q[0] == 1 && q[1] == 1)
+ continue;
+ uns h = (31 + q[0]) * 8;
+ uns v;
+ switch (q[1] >> 6)
+ {
+ case 0:
+ if (version < 0x0103)
+ v = h;
+ else
+ v = 10*h/16;
+ break;
+ case 1:
+ v = 3*h/4;
+ break;
+ case 2:
+ v = 4*h/5;
+ break;
+ default:
+ v = 9*h/16;
+ break;
+ }
+ printf(" %dx%d@%d", h, v, 60 + (q[1] & 0x3f));
+ }
+ printf("\n");
+}
+
+static void
+show_detailed_timing(byte *p)
+{
+ uns hactive = p[2] | ((p[4] << 4) & 0xf00);
+ uns hblank = p[3] | ((p[4] << 8) & 0xf00);
+ uns vactive = p[5] | ((p[7] << 4) & 0xf00);
+ uns vblank = p[6] | ((p[7] << 8) & 0xf00);
+ uns hs_offset = p[8] | ((p[11] << 2) & 0x300);
+ uns hs_width = p[9] | ((p[11] << 4) & 0x300);
+ uns vs_offset = (p[10] >> 4) | ((p[11] << 2) & 0x30);
+ uns vs_width = (p[10] & 0xf) | ((p[11] << 4) & 0x30);
+ uns hsize = p[12] | ((p[14] << 4) & 0xf00);
+ uns vsize = p[13] | ((p[14] << 8) & 0xf00);
+ uns hborder = p[15];
+ uns vborder = p[16];
+
+ printf("Detailed timing: %dx%d (%.1f Hz)\n",
+ hactive, vactive,
+ u16_le(p) * 10000. / (hactive+hblank) / (vactive+vblank));
+ printf("\tPixClk: %.2fMHz\n", u16_le(p) / 100.);
+ printf("\tH: Active=%d Blank=%d SyncOffset=%d SyncWidth=%d Border=%d\n",
+ hactive, hblank, hs_offset, hs_width, hborder);
+ printf("\tV: Active=%d Blank=%d SyncOffset=%d SyncWidth=%d Border=%d\n",
+ vactive, vblank, vs_offset, vs_width, vborder);
+ printf("\tSize: %dx%dmm\n", hsize, vsize);
+
+ uns flags = p[17];
+ uns stereo = ((flags >> 4) & 6) | (flags & 1);
+ uns input = (flags >> 3) & 3;
+ if (stereo >= 2)
+ printf("\tStereo mode: #%d\n", stereo);
+
+ static const char * const input_types[4] = { "AnalogComposite", "BipolarAnalogComposite", "DigitalComposite", "DigitalSeparate" };
+ printf("\tInput: %s", input_types[input]);
+ if (input < 3)
+ printf(" Serrate%c", B(flags & 4));
+ else
+ printf(" VPolarity%c", B(flags & 4));
+ if (input < 2)
+ printf(" OnRGB%c", B(flags & 2));
+ else if (input == 2)
+ printf(" CompPolarity%c", B(flags & 2));
+ else
+ printf(" HPolarity%c", B(flags & 2));
+ printf("\n");
+
+ if (hsize && vsize)
+ printf("\tCalculated DPI: %dx%d\n",
+ (int)(hactive / (hsize / 25.4)),
+ (int)(vactive / (vsize / 25.4)));
+}
+
+static void
+show_ascii(char *field, byte *p, uns len)
+{
+ printf("%s: ", field);
+ while (len--)
+ {
+ if (*p == '\n')
+ break;
+ else if (*p >= 0x20 && *p <= 0x7e)
+ putchar(*p);
+ else
+ printf("<%02x>", *p);
+ p++;
+ }
+ putchar('\n');
+}
+
+static void
+show_descriptor(byte *p)
+{
+ switch (p[3])
+ {
+ case 0xff: // Serial number
+ show_ascii("Serial", p+5, 13);
+ break;
+ case 0xfe: // ASCII data
+ show_ascii("Comment", p+5, 13);
+ break;
+ case 0xfd: // Range limits
+ printf("Limits: Vert=%d-%dHz Horiz=%d-%dkHz PixClk=%dMHz\n",
+ p[5], p[6], p[7], p[8], p[9]*10);
+ switch (p[10])
+ {
+ case 0: // No secondary timing formula
+ break;
+ case 2: // Secondary GTF
+ printf("Secondary GTF parameters: fStart=%dkHz C=%.1f M=%d K=%d J=%.1f\n",
+ p[12]*2, p[13]/2., u16_le(p+14), p[16], p[17]/2.);
+ break;
+ default:
+ printf("Unidentified secondary timing formula #%02x\n", p[10]);
+ }
+ break;
+ case 0xfc: // Monitor name
+ show_ascii("Monitor name", p+5, 13);
+ break;
+ case 0xfb: // Color point
+ printf("Color point: [...]\n");
+ break;
+ case 0xfa: // More standard timings
+ printf("More standard timings: [...]\n");
+ break;
+ case 0x10: // Dummy descriptor -- entry unused
+ return;
+ default:
+ printf("Unknown descriptor type %02x\n", p[3]);
+ }
+}
+
+static void
+show_edid(void)
+{
+ if (memcmp(edid, "\000\377\377\377\377\377\377\000", 8))
+ die("Invalid EDID signature, giving up");
+ int ver = edid[0x12];
+ int rev = edid[0x13];
+ printf("EDID %d.%d\n", ver, rev);
+ version = (ver << 8) | rev;
+
+ byte sum = 0;
+ for (int i=0; i<128; i++)
+ sum += edid[i];
+ if (sum)
+ printf("Invalid checksum: off by %02x\n", sum);
+
+ show_model(edid + 8);
+ show_basic(edid + 0x14);
+ show_color(edid + 0x19);
+ show_timings(edid + 0x23);
+ for (int i=0; i<4; i++)
+ {
+ byte *p = edid + 0x36 + 18*i;
+ if (p[0] || p[1])
+ show_detailed_timing(p);
+ else
+ show_descriptor(p);
+ }
+
+ if (edid[0x7e])
+ printf("### %d extension blocks follow, but they are not supported yet ###\n", edid[0x7e]);
+}
+
+static void read_binary(void)
+{
+ int c = read(0, edid, 128);
+ if (c < 0)
+ die("Read error: %m");
+ else if (c < 128)
+ die("Read only %d bytes, need 128", c);
+}
+
+static void
+read_xorg_log(void)
+{
+ char line[1024];
+ int mode = 0;
+ int cnt = 0;
+
+ while (fgets(line, sizeof(line), stdin))
+ {
+ if (!mode)
+ {
+ if (strstr(line, "EDID (in hex):"))
+ mode = 1;
+ }
+ else
+ {
+ char *c = strchr(line, '\n');
+ if (!c)
+ goto parseerr;
+ cnt += 16;
+ for (int i=0; i<32; i++)
+ {
+ if (c == line)
+ goto parseerr;
+ int x = *--c;
+ if (x >= '0' && x <= '9')
+ x -= '0';
+ else if (x >= 'A' && x <= 'F')
+ x -= 'A' - 10;
+ else if (x >= 'a' && x <= 'f')
+ x -= 'a' - 10;
+ else
+ goto parseerr;
+ if (!(i % 2))
+ edid[cnt-1-i/2] = x;
+ else
+ edid[cnt-1-i/2] |= x << 4;
+ }
+ if (c > line && c[-1] != ' ' && c[-1] != '\t')
+ goto parseerr;
+ if (cnt == 128)
+ return;
+ }
+ }
+ if (!cnt)
+ die("No EDID log found");
+ else
+ die("EDID log found, but it is too short (only %d bytes)", cnt);
+
+parseerr:
+ die("EDID log parse error at: %s", line);
+}
+
+static void
+show_hex(void)
+{
+ printf("\n");
+ for (int i=0; i<128; i+=16)
+ {
+ printf("@%02x:", i);
+ for (int j=0; j<16; j++)
+ printf(" %02x", edid[i+j]);
+ printf("\n");
+ }
+}
+
+static void
+usage(void)
+{
+ fprintf(stderr, "Usage: edid <options>\n\n\
+Options:\n\
+-l\t\tParse input as Xorg log file and try to find EDID dump there\n\
+-x\t\tShow hexdump of the whole EDID block\n\
+");
+ exit(1);
+}
+
+int main(int argc, char **argv)
+{
+ int opt;
+ int hex_mode = 0;
+ int log_mode = 0;
+
+ while ((opt = getopt(argc, argv, "xl")) >= 0)
+ switch (opt)
+ {
+ case 'l':
+ log_mode = 1;
+ break;
+ case 'x':
+ hex_mode = 1;
+ break;
+ default:
+ usage();
+ }
+ if (optind < argc)
+ usage();
+
+ if (log_mode)
+ read_xorg_log();
+ else
+ read_binary();
+
+ show_edid();
+
+ if (hex_mode)
+ show_hex();
+ return 0;
+}