/*---------------------------------------------------------------------------- PS/2 mouse demo Chris Giese http://my.execpc.com/~geezer/ Release date: April 2, 2011 This code is public domain (no copyright). You can do whatever you want with it. ----------------------------------------------------------------------------*/ #include /* atexit() */ #include /* memcpy() */ #include /* kbhit(), getch() */ #include /* printf() */ /* getvect(), setvect(), enable(), disable(), peek(), peekb(), pokeb() */ #include /* debug */ #if 0 #define PRINTF printf #else #define PRINTF (void) #endif #define TIMEOUT 1000 /* milliseconds */ #define KBD_BUF_SIZE 16 typedef struct { /* circular queue */ unsigned char *data; unsigned size, in_ptr, out_ptr; } queue_t; static unsigned char g_buf[KBD_BUF_SIZE]; static queue_t g_kbd_queue = { g_buf, /* .data */ KBD_BUF_SIZE /* .size */ /* no need to initialize .in_ptr, .out_ptr */ }; static volatile char g_got_mouse_pkt; static unsigned char g_mouse_pkt[3]; static void interrupt (*g_old_irq1_handler)(void); static void interrupt (*g_old_irq12_handler)(void); /****************************************************************************/ static int deq(queue_t *q) { unsigned rv; /* if out_ptr reaches in_ptr, the queue is empty */ if(q->out_ptr == q->in_ptr) return -1; rv = q->data[q->out_ptr++]; if(q->out_ptr >= q->size) q->out_ptr = 0; return rv; } /****************************************************************************/ static int inq(queue_t *q, unsigned char data) { unsigned i; i = q->in_ptr + 1; if(i >= q->size) i = 0; /* if in_ptr reaches out_ptr, the queue is full */ if(i == q->out_ptr) return -1; q->data[q->in_ptr] = data; q->in_ptr = i; return 0; } /****************************************************************************/ static int my_kbhit(void) { return (g_kbd_queue.out_ptr == g_kbd_queue.in_ptr) ? 0 : 1; } /****************************************************************************/ static int my_getch(void) { int rv; do rv = deq(&g_kbd_queue); while(rv == -1); return rv; } /****************************************************************************/ static void interrupt kbd_irq(void) { pokeb(0xB801, 0, peekb(0xB801, 0) + 1); (void)inq(&g_kbd_queue, inportb(0x60)); _chain_intr(g_old_irq1_handler); } /****************************************************************************/ static void interrupt mouse_irq(void) { static unsigned char index, pkt[3]; pokeb(0xB800, 0, peekb(0xB800, 0) + 1); /* Get byte and store. Hope things don't get out of sync here. Why isn't there an unambiguous way to identify the first byte of the packet, as with serial mice? */ pkt[index] = inportb(0x60); index++; /* got the whole packet; transfer to global array and set global "got-packet" flag */ if(index > 2) { memcpy(g_mouse_pkt, pkt, 3); g_got_mouse_pkt = 1; index = 0; } /* acknowledge IRQ 12 at 8259 chips: */ outportb(0xA0, 0x20); outportb(0x20, 0x20); } /****************************************************************************/ static int write_port64(unsigned val, unsigned timeout) { unsigned t; PRINTF("write_port64(0x%02X)...", val); for(t = 0; t < timeout; t++) { if((inportb(0x64) & 0x02) == 0) { outportb(0x64, val); PRINTF("OK (waited %u ms)\n", t); return 0; } delay(1); } PRINTF("timeout\n"); return -1; } /****************************************************************************/ static int write_port60(unsigned val, unsigned timeout) { unsigned t; PRINTF("write_port60(0x%02X)...", val); for(t = 0; t < timeout; t++) { if((inportb(0x64) & 0x02) == 0) goto OK; delay(1); } PRINTF("timeout(1)\n"); return -1; OK: PRINTF("OK (waited %u ms)...", t); outportb(0x64, 0x60); for(t = 0; t < timeout; t++) { if((inportb(0x64) & 0x02) == 0) { outportb(0x60, val); PRINTF("OK (waited %u ms)\n", t); return 0; } delay(1); } PRINTF("timeout(2)\n"); return -1; } /****************************************************************************/ static int read_port60(unsigned timeout) { unsigned t, rv; PRINTF("read_port60..."); for(t = 0; t < timeout; t++) { if((inportb(0x64) & 0x02) == 0) goto OK; delay(1); } PRINTF("timeout(1)\n"); return -1; OK: PRINTF("OK (waited %u ms)...", t); outportb(0x64, 0x20); for(t = 0; t < timeout; t++) { if(inportb(0x64) & 0x01) { rv = inportb(0x60); PRINTF("OK (waited %u ms, got 0x%02X)\n", t, rv); return rv; } delay(1); } PRINTF("timeout(2)\n"); return -1; } /****************************************************************************/ static int write_mouse(unsigned cmd, unsigned timeout) { unsigned _try, t, reply; PRINTF("write_mouse(0x%02X)...", cmd); for(_try = 1; _try <= 3; _try++) { /* 0xD4: direct next port 0x60 write to PS/2 mouse */ for(t = 0; t < timeout; t++) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t >= timeout) { PRINTF("timeout(1, try=%u)\n", _try); return -1; } PRINTF("OK (waited %u ms)...", t); outportb(0x64, 0xD4); /* send mouse command byte */ for(t = 0; t < timeout; t++) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t >= timeout) { PRINTF("timeout(2, try=%u)\n", _try); return -1; } PRINTF("OK (waited %u ms)...", t); outportb(0x60, cmd); /* get reply: 0xFA=acknowledge, 0xFE=resend, other=error */ for(t = 0; t < timeout; t++) { if(inportb(0x64) & 0x01) { reply = inportb(0x60); if(reply == 0xFA) { PRINTF("OK (waited %u ms)\n", t); return 0; } /* success */ if(reply == 0xFE) { PRINTF("NACK; retrying..."); break; } PRINTF("strange byte from 8048: 0x%02X\n", reply); return +1; } /* anything else: error */ delay(1); } if(t >= timeout) { PRINTF("timeout(3, try=%u)\n", _try); return -1; }} return -1; } /* tried three times, got three RESENDs */ /****************************************************************************/ static void cleanup(void) { /* restore old interrupt handlers */ setvect(9, g_old_irq1_handler); setvect(116, g_old_irq12_handler); /* disable PS/2 mouse port */ (void)write_port64(0xA7, TIMEOUT); /* enable interrupts */ enable(); PRINTF("cleanup done\n"); } /****************************************************************************/ static unsigned g_scn_wd, g_scn_ht; static void blink(unsigned x, unsigned y) { unsigned offset; if(x >= g_scn_wd || y >= g_scn_ht) return; offset = (g_scn_wd * y + x) * 2 + 1; pokeb(0xB800, offset, peekb(0xB800, offset) ^ 0x40); } /****************************************************************************/ int main(void) { int i, x = 0, y = 0; /* MEM 0040h:0010h - INSTALLED HARDWARE "PS/2, some XT clones, newer BIOSes" */ if((peek(0x40, 0x10) & 0x04) == 0) { printf("No PS/2 mouse\n"); return 1; } /* get screen size */ g_scn_wd = peek(0x40, 0x4A); g_scn_ht = peekb(0x40, 0x84) + 1; /* !!!-->TURN OFF INTERRUPTS<--!!! while messing with the mouse in the foreground. Seriously -- it just does not work otherwise. I suspect the BIOS is grabbing IRQ 12 interrupts from the mouse (for INT 15h AX=C2xxh). I've tried lots of PS/2 mouse code and it doesn't work without shutting off interrupts like this. It might be sufficient to turn off IRQ 1 and IRQ 12 at the 8259 chips instead of using disable() */ disable(); /* save old interrupt handlers */ g_old_irq1_handler = getvect(9); g_old_irq12_handler = getvect(116); atexit(cleanup); /* enable PS/2 mouse port */ if(write_port64(0xA8, TIMEOUT)) { printf("Error: could not enable PS/2 mouse port\n"); return 1; } /* read "command byte" port */ i = read_port60(TIMEOUT); if(i < 0) { printf("Error: could not read \"command byte\" port\n"); return 1; } /* enable IRQ12 from PS/2 mouse at the 8048 chip */ i |= 0x02; /* write "command byte" port */ i = write_port60(i, TIMEOUT); if(i < 0) { printf("Error: could not write \"command byte\" port\n"); return 1; } /* set PS/2 mouse defaults */ if(write_mouse(0xF6, TIMEOUT)) { printf("Error: could not set PS/2 mouse defaults\n"); return 1; } /* enable PS/2 mouse */ if(write_mouse(0xF4, TIMEOUT)) { printf("Error: could not enable PS/2 mouse\n"); return 1; } printf("Click left mouse button to draw a block or press " "Esc to quit.\n"); /* install interrupt handler(s) */ setvect(9, kbd_irq); setvect(116, mouse_irq); /* enable IRQ12 at 8259 interrupt controller chips */ outportb(0xA1, inportb(0xA1) & ~0x10); outportb(0x21, inportb(0x21) & ~0x04); enable(); /* show "cursor" */ blink(x, y); while(1) { if(my_kbhit()) { i = my_getch(); /* my_getch() returns the scancode; not the ASCII value. 0x01 is the set 1 makecode for Esc. */ if(i == 1) break; } if(g_got_mouse_pkt) { int dx, dy; dy = g_mouse_pkt[2]; dx = g_mouse_pkt[1]; /* it's two's-complement -- not sign-magnitude; as some docs might suggest if(g_mouse_pkt[0] & 0x20) dy = -dy; if(g_mouse_pkt[0] & 0x10) dx = -dx; */ if(g_mouse_pkt[0] & 0x20) dy |= (-1 & ~0xFF); if(g_mouse_pkt[0] & 0x10) dx |= (-1 & ~0xFF); /* yep... */ dy = -dy; //printf("dx=%d, dy=%d\n", dx, dy); /* g_mouse_pkt[0]: b3=1, b2=middle button, b1=right button, b0=left button hide cursor unless left button pressed */ if((g_mouse_pkt[0] & 0x01) == 0) blink(x, y); /* move cursor */ x += dx / 4; y += dy / 4; /* clipping */ if(x < 0) x = 0; else if(x >= g_scn_wd) x = g_scn_wd - 1; if(y < 0) y = 0; else if(y >= g_scn_ht) y = g_scn_ht - 1; /* show cursor */ blink(x, y); /* ready for next mouse packet */ g_got_mouse_pkt = 0; }} /* hide cursor */ blink(x, y); /* see note above about this: */ disable(); /* disable PS/2 mouse */ if(write_mouse(0xF5, TIMEOUT)) { printf("Error: could not disable PS/2 mouse\n"); return 1; } /* disable IRQ12 at 8048 chip */ i = read_port60(TIMEOUT); if(i < 0) { printf("Error: could not read \"command byte\" port\n"); return 1; } i &= ~0x02; i = write_port60(i, TIMEOUT); if(i < 0) { printf("Error: could not write \"command byte\" port\n"); return 1; } /* disable PS/2 mouse port (done in cleanup()) if(write_port64(0xA7, TIMEOUT)) { printf("Error: could not disable PS/2 mouse port\n"); return 1; } enable interrupts (done in cleanup()) enable(); */ return 0; }