#include #include #include #include #include #include #include #define READ 1 #define WRITE 0 int monitor; //monitor port number. struct sci_caps { int sclwrite; int sdawrite; int sclread; int sdaread; int ver; //0xABCD: AB is pre-decimal, DC is post. Multiply by 10. int ports; }; struct sci_caps sci_caps; void handle(int signo) { //don't want an alarm clock to bug me... } void die(char *msg) { fputs(msg, stderr); exit(1); } int vbe_cmd(unsigned int b, unsigned int d, struct LRMI_regs *regs) { pause(); //100KHz... int ret; regs->eax = 0x4f15; regs->ebx = b; regs->ecx = monitor; regs->edx = d; #ifdef EBUG fprintf(stderr, "wakawaka - b = %u, d = %u\n", b, d); #endif ret = !LRMI_int(0x10, regs); #ifdef EBUG fprintf(stderr, "dodoyeah\n"); fprintf(stderr, "Return: 0x%x. Func %ssupported, call %s.\n", regs->eax, ((regs->eax & 0xff) == 0x4f) ? "" : "not ", ((regs->eax & 0xff00) == 0x0000) ? "successful" : "failed"); #endif return ret; } int get_sci_caps(struct LRMI_regs *regs) { if (vbe_cmd(0x10, 0, regs)) die("Report Capabilities failed\n"); sci_caps.sclwrite = regs->ebx & 0x01; sci_caps.sdawrite = regs->ebx & 0x02; sci_caps.sclread = regs->ebx & 0x04; sci_caps.sdaread = regs->ebx & 0x08; sci_caps.ver = (((regs->ecx & 0xff00) >> 8) * 10) + (regs->ecx & 0xff); fprintf(stderr, "VBE/SCI version %.1f\n", (float)sci_caps.ver / 10); sci_caps.ports = regs->edx; return 0; } int getclock(struct LRMI_regs *regs) { vbe_cmd(0x15, 0, regs); return regs->edx; } int clock(struct LRMI_regs *regs) { //clock down, clock up. Fast way of not changing data bits. vbe_cmd(0x13, 0, regs); vbe_cmd(0x13, 1, regs); while(!getclock(regs)){ fprintf(stderr, "clock being angry\n"); } return 0; } int clocktest(struct LRMI_regs *regs) { //clock down - is it down? good. Back up. //This'll take many false positive capabilities out. int ret; vbe_cmd(0x13, 0, regs); ret = getclock(regs); //0 if good, 1 if bad. vbe_cmd(0x13, 1, regs); return ret; } int data(int bit, struct LRMI_regs *regs) { //clock down, change data, clock up. vbe_cmd(0x13, 0, regs); vbe_cmd(0x14, bit, regs); vbe_cmd(0x13, 1, regs); while(!getclock(regs)){ fprintf(stderr, "clock being angry\n"); } return 0; } int readbit(struct LRMI_regs *regs) { clock(regs); vbe_cmd(0x16, 0, regs); return regs->edx; } int dostart(struct LRMI_regs *regs) { //clock down, up - in case it is a repeated start. Safe if it's not. //Will always result in a high clock, though. clock(regs); vbe_cmd(0x16, 0, regs); if (!regs->edx); vbe_cmd(0x14, 1, regs); //if data is low, make it high. //NB: this might end up being a stop... I think this is good... //I'll clock() just in case... clock(regs); //Start bit: SCL is high, SDA goes high->low vbe_cmd(0x14, 0, regs); #ifdef EBUG fprintf(stderr, "Start\n"); #endif return 0; } int dostop(struct LRMI_regs *regs) { //Data must be low, clock must be up. data(0, regs); //now bring data up. vbe_cmd(0x14, 1, regs); #ifdef EBUG fprintf(stderr, "Stop\n"); #endif return 0; } int doack(struct LRMI_regs *regs) { data(1, regs); vbe_cmd(0x16, 0, regs); if (regs->edx == 1) { fprintf(stderr, "an ACK was a no-ack... continuing\n"); return 1; } #ifdef EBUG fprintf(stderr, "ACK\n"); #endif return 0; } int writebyte(unsigned char byte, struct LRMI_regs *regs) { //not optimized - will re-do writes, but it's I2C, so it doesn't matter :P data((byte & 0x80) >> 7, regs); data((byte & 0x40) >> 6, regs); data((byte & 0x20) >> 5, regs); data((byte & 0x10) >> 4, regs); data((byte & 0x08) >> 3, regs); data((byte & 0x04) >> 2, regs); data((byte & 0x02) >> 1, regs); data((byte & 0x01), regs); //I'll let you do the ack. Just in case. #ifdef EBUG fprintf(stderr, "Wrote 0x%x\n", byte); #endif return 0; } unsigned char readbyte(struct LRMI_regs *regs) { unsigned char ret; ret = (readbit(regs) & 0x01) << 7; ret |= (readbit(regs) & 0x01) << 6; ret |= (readbit(regs) & 0x01) << 5; ret |= (readbit(regs) & 0x01) << 4; ret |= (readbit(regs) & 0x01) << 3; ret |= (readbit(regs) & 0x01) << 2; ret |= (readbit(regs) & 0x01) << 1; ret |= (readbit(regs) & 0x01); #ifdef EBUG fprintf(stderr, "Read 0x%x\n", ret); #endif return ret; //you do the ack - just in case. } int doaddr(int addr, int rw, struct LRMI_regs *regs) { //SCL should be high, SDA should be low - you just did a start, right? //7-bit address, 1 bit read/write. //I usually don't make an unnecessary variable, but casting can be a bitch. unsigned char byte = (addr << 1) | (rw & 0x01); writebyte(byte, regs); //addr + r/w is a byte, write? :D return 0; } int writesp(unsigned char val, struct LRMI_regs *regs) { //write val to the segment pointer. //start dostart(regs); //addr = 0x30, write. Spec sez 0x60, and lies. doaddr(0x30, WRITE, regs); //ack. Don't care about this, or any for that matter. doack(regs); writebyte(val, regs); doack(regs); return 0; } int writewo(unsigned char val, struct LRMI_regs *regs) { //write val as the word offset. dostart(regs); //addr = 0x50, write. Spec sez 0xA0, and lies. doaddr(0x50, WRITE, regs); doack(regs); writebyte(val, regs); doack(regs); return 0; } int readedid(unsigned char *buf, struct LRMI_regs *regs) { //read first 128-byte edid. int i; //Write 0 to the segment pointer - first 2 blocks. //writesp(0, regs); //Write 0 to the word offset - first byte of first block. writewo(0, regs); //Address 0x50, read. Spec sez 0xA1, and lies. doaddr(0x50, READ, regs); doack(regs); for (i = 0; i < 128; i++) { buf[i] = readbyte(regs); //This ack is special - first of all, it's me doing the ACKing, //and the last byte must end with a NO-ACK. data((i == 127), regs); } //Stop. If we don't do this, your monitor will catch fire // //It won't really, but that would be kinda cool... //The bus really will get confused, though. dostop(regs); return 0; } int main(int argc, char *argv[]) { int i; unsigned char edid[128]; struct itimerval delay; signal(SIGALRM, handle); delay.it_interval.tv_sec = 0; delay.it_interval.tv_usec = 10; //100KHz... delay.it_value.tv_sec = 0; delay.it_value.tv_usec = 10; //just another value. if (setitimer(ITIMER_REAL, &delay, NULL)) perror("setitimer"); if (!LRMI_init()) die("Real-mode Init failed\n"); struct LRMI_regs *regs = malloc(sizeof(struct LRMI_regs)); if (argc > 2) die("too many arguments\n"); if (argc == 1) monitor = 0; else monitor = atoi(argv[1]); //following command fixes permissions iopl(3); fprintf(stderr, "debug1\n"); get_sci_caps(regs); fprintf(stderr, "Trying monitor %i out of %i.\n", monitor, sci_caps.ports); if (!(sci_caps.sclwrite && sci_caps.sdawrite && sci_caps.sclread && sci_caps.sdaread)) { fprintf(stderr, "Email the following line to the author, poly-p man, at pyrophobicman@gmail.com\nsclwrite: %i, sdawrite: %i, sclread: %i, sdaread: %i\n", sci_caps.sclwrite, sci_caps.sdawrite, sci_caps.sclread, sci_caps.sdaread); //die("Not all capabilities present...\n"); fprintf(stderr, "Warning - not all capabilities present - trying anyway\n"); } if (vbe_cmd(0x11, 0, regs)) //Begin SCL/SDA control. die("Begin SCL/SDA control failed\n"); /* YOU MAY BEGIN I2C COMMANDS HERE */ #ifndef ONTSTOP if (clocktest(regs)) die("Clock not responding - no I2C support on video card.\n"); #endif fprintf(stderr, "About to start the read... hold on tight\n"); readedid(edid, regs); for (i = 0; i < 128; i++) printf("%c", edid[i]); /* NO MORE I2C COMMANDS! */ if (vbe_cmd(0x12, 0, regs)) //End SCL/SDA control. die("End SCL/SDA control failed\n"); free(regs); return 0; }