/*---------------------------------------------------------------------------- PS/2 mouse demo for Turbo C (DOS). Chris Giese http://SisAndHappy.com/ChrisGiese/ This code is public domain (no copyright). You can do whatever you want with it. 3 Jul 2015: - stylistic changes - changed "8048" to "8042" (typo) - read_port60() renamed to read_command_byte() and write_port60() renamed to write_command_byte(); which are what these functions actually do - added correct read_port60() and write_port60() functions - added support for scroll wheel - added code to get and display mouse ID bytes; before and after enabling the scroll wheel - added code to draw different colored blocks when any of the three mouse buttons are pressed; not just the left button 2 Apr 2011: initial release ----------------------------------------------------------------------------*/ #include /* atexit() */ #include /* memcpy() */ #include /* kbhit(), getch() */ #include /* printf() */ /* getvect(), setvect(), enable(), disable(), peek(), peekb(), pokeb() */ #include #if 0 #define DEBUG(X) X #else #define DEBUG(X) /* nothing */ #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; #define MAX_PKT_SIZE 4 static unsigned char g_mouse_pkt[MAX_PKT_SIZE]; static unsigned char g_mouse_pkt_size = 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) { /* it's a bad idea to call printf() in an interrupt handler, so poke video memory directly instead: */ DEBUG(pokeb(0xB800, 80, 'K');) DEBUG(pokeb(0xB800, 82, peekb(0xB800, 82) + 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[MAX_PKT_SIZE]; DEBUG(pokeb(0xB800, 86, 'M');) DEBUG(pokeb(0xB800, 88, peekb(0xB800, 88) + 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 >= g_mouse_pkt_size) { memcpy(g_mouse_pkt, pkt, g_mouse_pkt_size); g_got_mouse_pkt = 1; index = 0; } /* acknowledge IRQ 12 at 8259 interrupt controller chips: */ outportb(0xA0, 0x20); outportb(0x20, 0x20); } /****************************************************************************/ static int read_port60(unsigned timeout) { unsigned t, rv; DEBUG(printf("read_port60...");) for(t = timeout; t != 0; t--) { if(inportb(0x64) & 0x01) break; delay(1); } if(t == 0) { DEBUG(printf("timeout(2)\n");) return -1; } rv = inportb(0x60); DEBUG(printf("OK (waited %u ms, got 0x%02X)\n", TIMEOUT - t, rv);) return rv; } /****************************************************************************/ static int write_port60(unsigned val) { unsigned t; DEBUG(printf("write_port60(0x%02)...", val);) for(t = TIMEOUT; t != 0; t--) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t == 0) { DEBUG(printf("timeout(2)\n");) return -1; } outportb(0x60, val); DEBUG(printf("OK (waited %u ms)\n", TIMEOUT - t);) return 0; } /****************************************************************************/ static int write_port64(unsigned val) { unsigned t; DEBUG(printf("write_port64(0x%02X)...", val);) for(t = TIMEOUT; t != 0; t--) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t == 0) { DEBUG(printf("timeout\n");) return -1; } outportb(0x64, val); DEBUG(printf("OK (waited %u ms)\n", TIMEOUT - t);) return 0; } /***************************************************************************** The "command byte" is the byte at address 0 in the on-chip RAM of the 8042 keyboard/mouse controller chip. This code is only interested in bit b1; which enables IRQ 12 from the PS/2 mouse. From RBIL: Bitfields for HP Vectra command byte: Bit(s) Description (Table P0385) 7 reserved (0) 6 scancode conversion mode (1 = PC/XT, 0 = PC/AT) [*] 5 unused 4 disable keyboard (unless bit 3 set) 3 override keyboard disable 2 System Flag (may be read from PORT 0060h) 1 reserved 0 OBF interrupt enable [*] enable conversion of set 2 scancodes from keyboard to set 1 Bitfields for Compaq keyboard command byte: Bit(s) Description (Table P0403) 7 reserved 6 =1 convert KB codes to 8086 scan codes 5 =0 use 11-bit codes, 1=use 8086 codes 4 =0 enable keyboard, 1=disable keyboard 3 ignore security lock state 2 this bit goes into bit2 status reg. 1 reserved (0) 0 generate interrupt (IRQ1) when output buffer full Bitfields for keyboard command byte (alternate description): Bit(s) Description (Table P0404) 7 reserved (0) 6 IBM PC compatibility mode 5 IBM PC mode no parity, no stop bits, no translation (PS/2) force mouse clock low 4 disable keyboard (clock) 3 (AT) inhibit override -- ignore keyboard lock switch (PS/2) reserved 2 system flag 1 (AT) reserved (0) (PS/2) enable mouse output buffer full interrupt (IRQ12) 0 enable output buffer full interrupt (IRQ1) *****************************************************************************/ static int read_command_byte(void) { unsigned t; DEBUG(printf("read_command_byte...");) for(t = TIMEOUT; t != 0; t--) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t == 0) { DEBUG(printf("timeout(1)\n");) return -1; } outportb(0x64, 0x20); DEBUG(printf("OK (waited %u ms)...", TIMEOUT - t);) return read_port60(TIMEOUT); } /****************************************************************************/ static int write_command_byte(unsigned val) { unsigned t; DEBUG(printf("write_command_byte(0x%02X)...", val);) for(t = TIMEOUT; t != 0; t--) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t == 0) { DEBUG(printf("timeout(1)\n");) return -1; } DEBUG(printf("OK (waited %u ms)...", TIMEOUT - t);) outportb(0x64, 0x60); return write_port60(val); } /****************************************************************************/ static int write_mouse(unsigned cmd) { unsigned _try, t, reply; DEBUG(printf("write_mouse(0x%02X)...", cmd);) for(_try = 1; _try <= 3; _try++) { /* 0xD4: direct next port 0x60 write to PS/2 mouse */ for(t = TIMEOUT; t != 0; t--) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t == 0) { DEBUG(printf("timeout(1, try=%u)\n", _try);) return -1; } DEBUG(printf("OK (waited %u ms)...", TIMEOUT - t);) outportb(0x64, 0xD4); /* send mouse command byte */ for(t = TIMEOUT; t != 0; t--) { if((inportb(0x64) & 0x02) == 0) break; delay(1); } if(t == 0) { DEBUG(printf("timeout(2, try=%u)\n", _try);) return -1; } DEBUG(printf("OK (waited %u ms)...", TIMEOUT - t);) outportb(0x60, cmd); /* get reply: 0xFA=acknowledge, 0xFE=resend, other=error */ for(t = TIMEOUT; t != 0; t--) { if(inportb(0x64) & 0x01) { reply = inportb(0x60); if(reply == 0xFA) { DEBUG(printf("OK (waited %u ms)\n", TIMEOUT - t);) return 0; } /* success */ if(reply == 0xFE) { DEBUG(printf("NACK; retrying...");) break; } DEBUG(printf("strange byte from 8042: 0x%02X\n", reply);) return +1; } /* anything else: error */ delay(1); } if(t == 0) { DEBUG(printf("timeout(3, try=%u)\n", _try);) return -1; }} return -1; } /* tried three times, got three RESENDs */ /****************************************************************************/ static void cleanup(void) { disable(); /* 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); /* enable interrupts */ enable(); DEBUG(printf("cleanup done\n");) } /****************************************************************************/ static unsigned g_scn_wd, g_scn_ht; static int read_attribute(unsigned x, unsigned y) { unsigned offset; if(x >= g_scn_wd || y >= g_scn_ht) return -1; offset = (g_scn_wd * y + x) * 2 + 1; return peekb(0xB800, offset); } /****************************************************************************/ static void write_attribute(unsigned x, unsigned y, unsigned a) { unsigned offset; if(x >= g_scn_wd || y >= g_scn_ht) return; offset = (g_scn_wd * y + x) * 2 + 1; pokeb(0xB800, offset, a); } /****************************************************************************/ static void blink(unsigned x, unsigned y) { int c; if((c = read_attribute(x, y)) >= 0) write_attribute(x, y, c ^ 0x70); } /****************************************************************************/ #define BPERL 16 /* byte/line for dump */ static void dump(void *data_p, unsigned count) { unsigned char *data = (unsigned char *)data_p; unsigned byte1, byte2; while(count != 0) { for(byte1 = 0; byte1 < BPERL; byte1++) { if(count == 0) break; printf("%02X ", data[byte1]); count--; } printf("\t"); for(byte2 = 0; byte2 < byte1; byte2++) { if(data[byte2] < ' ') printf("."); else printf("%c", data[byte2]); } printf("\n"); data += BPERL; }} /****************************************************************************/ int main(void) { int i, j, x = 0, y = 0, scroll = 0; char buf[16]; /* 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); /* ensure old handlers are restored and interrupts are enabled when we exit */ atexit(cleanup); /* enable PS/2 mouse port */ if(write_port64(0xA8)) { printf("Error: can't enable PS/2 mouse port\n"); return 1; } /* read "command byte" port */ if((i = read_command_byte()) < 0) { printf("Error: can't read \"command byte\" port\n"); return 1; } /* enable IRQ12 from PS/2 mouse at the 8042 chip xxx - may also need i &= ~0x20; to enable mouse clock */ i |= 0x02; /* write "command byte" port */ if((i = write_command_byte(i)) < 0) { printf("Error: can't write \"command byte\" port\n"); return 1; } /* set PS/2 mouse defaults */ if(write_mouse(0xF6)) { printf("Error: can't set PS/2 mouse defaults\n"); return 1; } /* enable PS/2 mouse */ if(write_mouse(0xF4)) { printf("Error: can't enable PS/2 mouse\n"); return 1; } /* get mouse ID */ if(write_mouse(0xF2)) { printf("Error: can't ID mouse\n"); return 1; } for(j = 0; j < sizeof(buf); j++) { if((i = read_port60(100)) < 0) break; buf[j] = i; } printf("Mouse ID bytes: "); dump(buf, j); /* set sample rate to 200, then 100, then 80 to enable scroll wheel and 4-byte mouse packets */ if(write_mouse(0xF3) || write_mouse(200) || write_mouse(0xF3) || write_mouse(100) || write_mouse(0xF3) || write_mouse(80)) { printf("Error: can't set sample rate/enable scroll wheel\n"); return 1; } /* get mouse ID */ if(write_mouse(0xF2)) { printf("Error: can't ID mouse\n"); return 1; } for(j = 0; j < sizeof(buf); j++) { if((i = read_port60(100)) < 0) break; buf[j] = i; } printf("Mouse ID bytes after enabling scroll wheel: "); dump(buf, j); if(j == 1 && buf[0] == 3) { g_mouse_pkt_size = 4; printf("Scroll wheel and 4-byte packets enabled\n"); } printf("Move mouse to move cursor, click mouse buttons to draw\n" "colored blocks, 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()) { /* my_getch() returns the scancode; not the ASCII value. 0x01 is the set 1 make code for Esc */ if((i = my_getch()) == 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); /* hide cursor */ blink(x, y); /* 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) write_attribute(x, y, 0x10); else if(g_mouse_pkt[0] & 0x02) write_attribute(x, y, 0x20); else if(g_mouse_pkt[0] & 0x04) write_attribute(x, y, 0x40); /* 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; /* handle scroll wheel */ if(g_mouse_pkt_size > 3 && g_mouse_pkt[3] != 0) { scroll += (signed char)(g_mouse_pkt[3]); printf("scroll=%d\n", scroll); } /* 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)) { printf("Error: could not disable PS/2 mouse\n"); return 1; } /* disable IRQ12 at 8042 chip */ if((i = read_command_byte()) < 0) { printf("Error: could not read \"command byte\" port\n"); return 1; } i &= ~0x02; if((i = write_command_byte(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)) { printf("Error: could not disable PS/2 mouse port\n"); return 1; } enable interrupts (done in cleanup()) enable(); */ return 0; }