From: Martin Mares Date: Sat, 3 Dec 2011 15:48:30 +0000 (+0100) Subject: Added EDID parser X-Git-Url: http://mj.ucw.cz/gitweb/?a=commitdiff_plain;h=24077c0fc5b264a5b43e134ca12e5012239a283b;p=misc.git Added EDID parser --- diff --git a/edid.c b/edid.c new file mode 100644 index 0000000..155ffa8 --- /dev/null +++ b/edid.c @@ -0,0 +1,442 @@ +/* + * Parse VESA Extended Display Identification Data + * + * (c) 2011 Martin Mares + */ + +#include +#include +#include +#include +#include + +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 \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; +}