#include #if defined(__i386__) || defined(__x86_64__) || defined(__ia64__) #include #endif #include #include #include #include #include #include /* for uintptr_t */ #include #include #include #include #include #include #include #include "get-edid.h" int box_is_xbox(); void log_err(char *format, ...) { if (verbose) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); } } /* * Create a 'canonical' version, i.e. no spaces at start and end. * * Note: removes chars >= 0x80 as well (due to (char *))! This * is currently considered a feature. */ static char *canon_str(char *s, int len) { char *start, *end; for (start = s; start < s + len; start++) if (start[0] > ' ') break; for (end = &s[len - 1]; end >= start; end--) { if (end[0] > ' ') break; end[0] = 0; } return start; } static unsigned segofs2addr(char *segofs) { return segofs[0] + (segofs[1] << 8) + (segofs[2] << 4)+ (segofs[3] << 12); } #define VBE_BLOCK_SIZE 0x200 static unsigned get_data(char *buf, unsigned buf_size, char *bufferaddr, unsigned addr) { unsigned len; char *real_addr = (char *) LRMI_base_addr() + addr; *buf = 0; len = 0; if(real_addr >= bufferaddr && real_addr < bufferaddr + VBE_BLOCK_SIZE) { len = bufferaddr + VBE_BLOCK_SIZE - real_addr; if(len >= buf_size) len = buf_size - 1; memcpy(buf, real_addr, len); } /* the pointer may also point to the BIOS instead: 0x0c0000: video BIOS 0x100000: end of ROM area */ else if(addr >= 0x0c0000 && addr < 0x100000) { len = 0x100000 - addr; if(len >= buf_size) len = buf_size - 1; memcpy(buf, (char *) LRMI_base_addr() + addr, len); } buf[len] = 0; return len; } static char *get_str(char *buf, unsigned buf_size, char *v, int offset) { int len = get_data(buf, buf_size, v, segofs2addr(v + offset)); return canon_str(buf, len); } #define GET_WORD(ADDR, OFS) (((unsigned char *)ADDR)[OFS] + (((unsigned char *)ADDR)[(OFS) + 1] << 8)) static void parse_vbe_info(char *v) { char tmp[1024]; /* Parse VBE block */ int version = GET_WORD(v, 0x04); int oem_version = GET_WORD(v, 0x14); int memory_size = GET_WORD(v, 0x12) << 16; log_err("VBE version: %u.%u, oem version = %u.%u\n", version >> 8, version & 0xff, oem_version >> 8, oem_version & 0xff); log_err("Memory: %uk\n", memory_size >> 10); log_err("OEM name: %s\n", get_str(tmp, sizeof tmp, v, 0x06)); log_err("Vendor name: %s\n", get_str(tmp, sizeof tmp, v, 0x16)); log_err("Product name: %s\n", get_str(tmp, sizeof tmp, v, 0x1a)); log_err("Product revision: %s\n", get_str(tmp, sizeof tmp, v, 0x1e)); } static void parse_ddc_info(int port, int info) { log_err("Port %d:\n", port); log_err(" DDC1 %ssupported\n", info & 1 ? "" : "not "); log_err(" DDC2 %ssupported\n", info & 2 ? "" : "not "); log_err(" Screen %sblanked during data transfer\n", info & 4 ? "" : "not "); log_err(" Time to transfer one EDID block: %d sec (rounded up)\n", (info & 0xff00) >> 8); } static int vbe_check_vbe_info(void) { int i; char *mem; struct LRMI_regs regs; /* initialize LRMI */ if (LRMI_init() == 0) { log_err("VBE: could not initialize LRMI\n"); return 0; } /* allocate a chunk of memory */ mem = LRMI_alloc_real(VBE_BLOCK_SIZE); if (mem == NULL) { log_err("VBE: could allocate real memory\n"); return 0; } memset(mem, 0, VBE_BLOCK_SIZE); /* set up registers for the interrupt call. */ memset(®s, 0, sizeof(regs)); regs.eax = 0x4f00; regs.es = (uintptr_t)(mem - LRMI_base_addr()) >> 4; regs.edi = (uintptr_t)(mem - LRMI_base_addr()) & 0x0f; memcpy(mem, "VBE2", 4); /* do it. */ iopl(3); ioperm(0, 0x400, 1); if (LRMI_int(0x10, ®s) == 0) { LRMI_free_real(mem); return 0; } /* check for successful return code */ i = regs.eax & 0xffff; if (i != 0x4f) { LRMI_free_real(mem); log_err("VBE: Error (0x4f00): 0x%04x\n", i); return 0; } parse_vbe_info(mem); LRMI_free_real(mem); return 1; } static int vbe_check_ddc_capabilities(int port) { int i; struct LRMI_regs regs; /* initialize LRMI */ if (LRMI_init() == 0) { log_err("DDC: could not initialize LRMI\n"); return 0; } /* set up registers for the interrupt call. */ memset(®s, 0, sizeof(regs)); regs.eax = 0x4f15; regs.ebx = 0x0000; regs.ecx = port; regs.es = 0; regs.edi = 0; /* do it. */ iopl(3); ioperm(0, 0x400, 1); if (LRMI_int(0x10, ®s) == 0) { return 0; } /* check for successful return code */ i = regs.eax & 0xffff; if (i != 0x4f) { log_err("DDC: Error (0x4f15:00): 0x%04x\n", i); return 0; } parse_ddc_info(port, regs.ebx); if (!(regs.ebx & 3)) { log_err("DDC (0x4f15:00): DDC not supported, not continuing\n", i); return 1; } return 2; } static int vbe_get_edid_info(char *edid, int port, int block) { int i; unsigned char *mem; struct LRMI_regs regs; /* initialize LRMI */ if (LRMI_init() == 0) { log_err("EDID: could not initialize LRMI\n"); return 0; } /* allocate a chunk of memory */ mem = LRMI_alloc_real(EDID_BLOCK_SIZE); if (mem == NULL) { log_err("EDID: could allocate real memory\n"); return 0; } memset(mem, 0, EDID_BLOCK_SIZE); /* set up registers for the interrupt call. */ memset(®s, 0, sizeof(regs)); regs.eax = 0x4f15; regs.ebx = 0x0001; regs.ecx = port; regs.edx = block; regs.es = (uintptr_t)(mem - LRMI_base_addr()) >> 4; regs.edi = (uintptr_t)(mem - LRMI_base_addr()) & 0x0f; /* do it. */ iopl(3); ioperm(0, 0x400, 1); if (LRMI_int(0x10, ®s) == 0) { LRMI_free_real(mem); return 0; } /* check for successful return code */ i = regs.eax & 0xffff; if (i != 0x4f) { LRMI_free_real(mem); log_err("EDID: Error (0x4f15:01): 0x%04x\n", i); return 0; } /* get memory to return the information */ memcpy(edid, mem, EDID_BLOCK_SIZE); LRMI_free_real(mem); return 1; } /* return values: * size of edid: success * 0: success but no edid * -1: failure, VBE info call worked * -2: failure, VBE info call didn't work */ int get_edid(char *edid, int port, int skip_vbe_check) { int i, extensions; int ok = 0; if (getuid() != 0) { fprintf(stderr, "you must be root to run this program\n"); return -1; } if (!box_is_xbox() && !skip_vbe_check) { if (!vbe_check_vbe_info()) return -1; } ok = vbe_check_ddc_capabilities(port); if (ok == 1) /* success but no DDC */ return 0; if (!ok || !vbe_get_edid_info(edid, port, 0)) return -2; extensions = ((unsigned char*)edid)[126]; if (extensions > MAX_EXTENSION_COUNT) { log_err("EDID: Reported %d extensions, only reading %d\n", extensions, MAX_EXTENSION_COUNT); extensions = MAX_EXTENSION_COUNT; } for (i = 1; i <= extensions; i++) { if (!vbe_get_edid_info(edid + i * EDID_BLOCK_SIZE, port, i)) { log_err("EDID: Failure reading extension block %d\n", i); break; } if (memcmp(edid, edid + i * EDID_BLOCK_SIZE, EDID_BLOCK_SIZE) == 0) { log_err("EDID: Extension block %d is identical to main EDID block, stopping retrieval\n", i); break; } } return i * EDID_BLOCK_SIZE; } int box_is_xbox() { int is_xbox = 0; int result = -1; int fd; size_t rd; char *xbox_id = "0000\t10de02a5"; char id[13]; if (!(fd = open("/proc/bus/pci/devices", O_RDONLY))) { printf("Unable to open /proc/bus/pci/devices\n"); } if (!(rd = read(fd, id, sizeof(id)))) { printf("Unable to read /proc/bus/pci/devices\n"); } if (fd > 0) close(fd); result = strncmp(id, xbox_id, 13); if (result == 0) is_xbox = 1; return is_xbox; }