1 // Support for handling the PS/2 mouse/keyboard ports.
3 // Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
4 // Several ideas taken from code Copyright (c) 1999-2004 Vojtech Pavlik
6 // This file may be distributed under the terms of the GNU LGPLv3 license.
8 // This file is copied (mostly) intact from SeaBIOS.
16 #define dprintf(lvl, fmt, ...)
17 #define warn_timeout()
20 calc_future_tsc(int timeout
)
22 return get_wall_time() + timeout
;
28 return get_wall_time() > end
;
41 #define GET_EBDA(VAR) ebda.VAR
42 #define SET_EBDA(VAR, VAL) (ebda.VAR = (VAL))
44 #define ASSERT32FLAT()
45 #define CONFIG_PS2PORT 1
47 #define enable_hwirq(level, func)
48 #define run_thread(func, val) func(val)
50 /****************************************************************
51 * Low level i8042 commands.
52 ****************************************************************/
55 #define I8042_CTL_TIMEOUT 10000
57 #define I8042_BUFFER_SIZE 16
62 dprintf(7, "i8042_wait_read\n");
64 for (i
=0; i
<I8042_CTL_TIMEOUT
; i
++) {
65 u8 status
= inb(PORT_PS2_STATUS
);
66 if (status
& I8042_STR_OBF
)
75 i8042_wait_write(void)
77 dprintf(7, "i8042_wait_write\n");
79 for (i
=0; i
<I8042_CTL_TIMEOUT
; i
++) {
80 u8 status
= inb(PORT_PS2_STATUS
);
81 if (! (status
& I8042_STR_IBF
))
92 dprintf(7, "i8042_flush\n");
94 for (i
=0; i
<I8042_BUFFER_SIZE
; i
++) {
95 u8 status
= inb(PORT_PS2_STATUS
);
96 if (! (status
& I8042_STR_OBF
))
107 __i8042_command(int command
, u8
*param
)
109 int receive
= (command
>> 8) & 0xf;
110 int send
= (command
>> 12) & 0xf;
113 int ret
= i8042_wait_write();
116 outb(command
, PORT_PS2_STATUS
);
118 // Send parameters (if any).
120 for (i
= 0; i
< send
; i
++) {
121 ret
= i8042_wait_write();
124 outb(param
[i
], PORT_PS2_DATA
);
127 // Receive parameters (if any).
128 for (i
= 0; i
< receive
; i
++) {
129 ret
= i8042_wait_read();
132 param
[i
] = inb(PORT_PS2_DATA
);
133 dprintf(7, "i8042 param=%x\n", param
[i
]);
140 i8042_command(int command
, u8
*param
)
142 dprintf(7, "i8042_command cmd=%x\n", command
);
143 int ret
= __i8042_command(command
, param
);
145 dprintf(2, "i8042 command %x failed\n", command
);
150 i8042_kbd_write(u8 c
)
152 dprintf(7, "i8042_kbd_write c=%d\n", c
);
153 int ret
= i8042_wait_write();
155 outb(c
, PORT_PS2_DATA
);
160 i8042_aux_write(u8 c
)
162 return i8042_command(I8042_CMD_AUX_SEND
, &c
);
169 for (i
=0; i
<10; i
++) {
172 outb(0xfe, PORT_PS2_STATUS
); /* pulse reset low */
178 /****************************************************************
180 ****************************************************************/
182 #define PS2_RET_ACK 0xfa
183 #define PS2_RET_NAK 0xfe
186 ps2_recvbyte(int aux
, int needack
, int timeout
)
188 u64 end
= calc_future_tsc(timeout
);
190 u8 status
= inb(PORT_PS2_STATUS
);
191 if (status
& I8042_STR_OBF
) {
192 u8 data
= inb(PORT_PS2_DATA
);
193 dprintf(7, "ps2 read %x\n", data
);
195 if (!!(status
& I8042_STR_AUXDATA
) == aux
) {
198 if (data
== PS2_RET_ACK
)
200 if (data
== PS2_RET_NAK
) {
201 dprintf(1, "Got ps2 nak (status=%x)\n", status
);
206 // This data not part of command - just discard it.
207 dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data
, status
);
210 if (check_tsc(end
)) {
211 // Don't warn on second byte of a reset
221 ps2_sendbyte(int aux
, u8 command
, int timeout
)
223 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux
, command
);
226 ret
= i8042_aux_write(command
);
228 ret
= i8042_kbd_write(command
);
233 ret
= ps2_recvbyte(aux
, 1, timeout
);
236 if (ret
!= PS2_RET_ACK
)
243 __ps2_command(int aux
, int command
, u8
*param
)
246 int receive
= (command
>> 8) & 0xf;
247 int send
= (command
>> 12) & 0xf;
249 // Disable interrupts and keyboard/mouse.
250 u8 ps2ctr
= GET_EBDA(ps2ctr
);
251 u8 newctr
= ((ps2ctr
| I8042_CTR_AUXDIS
| I8042_CTR_KBDDIS
)
252 & ~(I8042_CTR_KBDINT
|I8042_CTR_AUXINT
));
253 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr
, newctr
);
254 int ret
= i8042_command(I8042_CMD_CTL_WCTR
, &newctr
);
258 // Flush any interrupts already pending.
261 // Enable port command is being sent to.
263 newctr
&= ~I8042_CTR_AUXDIS
;
265 newctr
&= ~I8042_CTR_KBDDIS
;
266 ret
= i8042_command(I8042_CMD_CTL_WCTR
, &newctr
);
270 if (command
== ATKBD_CMD_RESET_BAT
) {
271 // Reset is special wrt timeouts and bytes received.
274 ret
= ps2_sendbyte(aux
, command
, 1000);
278 // Receive parameters.
279 ret
= ps2_recvbyte(aux
, 0, 4000);
283 ret
= ps2_recvbyte(aux
, 0, 100);
285 // Some devices only respond with one byte on reset.
288 } else if (command
== ATKBD_CMD_GETID
) {
289 // Getid is special wrt bytes received.
292 ret
= ps2_sendbyte(aux
, command
, 200);
296 // Receive parameters.
297 ret
= ps2_recvbyte(aux
, 0, 500);
301 if (ret
== 0xab || ret
== 0xac || ret
== 0x2b || ret
== 0x5d
302 || ret
== 0x60 || ret
== 0x47) {
303 // These ids (keyboards) return two bytes.
304 ret
= ps2_recvbyte(aux
, 0, 500);
313 ret
= ps2_sendbyte(aux
, command
, 200);
317 // Send parameters (if any).
319 for (i
= 0; i
< send
; i
++) {
320 ret
= ps2_sendbyte(aux
, param
[i
], 200);
325 // Receive parameters (if any).
326 for (i
= 0; i
< receive
; i
++) {
327 ret
= ps2_recvbyte(aux
, 0, 500);
337 // Restore interrupts and keyboard/mouse.
338 ret2
= i8042_command(I8042_CMD_CTL_WCTR
, &ps2ctr
);
346 ps2_command(int aux
, int command
, u8
*param
)
348 dprintf(7, "ps2_command aux=%d cmd=%x\n", aux
, command
);
349 int ret
= __ps2_command(aux
, command
, param
);
351 dprintf(2, "ps2 command %x failed (aux=%d)\n", command
, aux
);
356 ps2_kbd_command(int command
, u8
*param
)
358 return ps2_command(0, command
, param
);
362 ps2_mouse_command(int command
, u8
*param
)
364 return ps2_command(1, command
, param
);
368 /****************************************************************
370 ****************************************************************/
373 // INT74h : PS/2 mouse hardware interrupt
377 if (! CONFIG_PS2PORT
)
380 debug_isr(DEBUG_ISR_74
);
382 u8 v
= inb(PORT_PS2_STATUS
);
383 if ((v
& (I8042_STR_OBF
|I8042_STR_AUXDATA
))
384 != (I8042_STR_OBF
|I8042_STR_AUXDATA
)) {
385 dprintf(1, "ps2 mouse irq but no mouse data.\n");
388 v
= inb(PORT_PS2_DATA
);
390 if (!(GET_EBDA(ps2ctr
) & I8042_CTR_AUXINT
))
391 // Interrupts not enabled.
400 // INT09h : Keyboard Hardware Service Entry Point
404 if (! CONFIG_PS2PORT
)
407 debug_isr(DEBUG_ISR_09
);
409 // read key from keyboard controller
410 u8 v
= inb(PORT_PS2_STATUS
);
411 if (v
& I8042_STR_AUXDATA
) {
412 dprintf(1, "ps2 keyboard irq but found mouse data?!\n");
415 v
= inb(PORT_PS2_DATA
);
417 if (!(GET_EBDA(ps2ctr
) & I8042_CTR_KBDINT
))
418 // Interrupts not enabled.
428 /****************************************************************
430 ****************************************************************/
433 keyboard_init(void *data
)
435 /* flush incoming keys */
436 int ret
= i8042_flush();
440 // Controller self-test.
442 ret
= i8042_command(I8042_CMD_CTL_TEST
, param
);
445 if (param
[0] != 0x55) {
446 dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param
[0]);
450 // Controller keyboard test.
451 ret
= i8042_command(I8042_CMD_KBD_TEST
, param
);
454 if (param
[0] != 0x00) {
455 dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param
[0]);
459 // Disable keyboard and mouse events.
460 SET_EBDA(ps2ctr
, I8042_CTR_KBDDIS
| I8042_CTR_AUXDIS
);
463 /* ------------------- keyboard side ------------------------*/
464 /* reset keyboard and self test (keyboard side) */
465 ret
= ps2_kbd_command(ATKBD_CMD_RESET_BAT
, param
);
468 if (param
[0] != 0xaa) {
469 dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param
[0]);
473 /* Disable keyboard */
474 ret
= ps2_kbd_command(ATKBD_CMD_RESET_DIS
, NULL
);
478 // Set scancode command (mode 2)
480 ret
= ps2_kbd_command(ATKBD_CMD_SSCANSET
, param
);
484 // Keyboard Mode: disable mouse, scan code convert, enable kbd IRQ
485 SET_EBDA(ps2ctr
, I8042_CTR_AUXDIS
| I8042_CTR_XLATE
| I8042_CTR_KBDINT
);
487 /* Enable keyboard */
488 ret
= ps2_kbd_command(ATKBD_CMD_ENABLE
, NULL
);
492 dprintf(1, "PS2 keyboard initialized\n");
499 if (! CONFIG_PS2PORT
)
501 dprintf(3, "init ps2port\n");
503 enable_hwirq(1, FUNC16(entry_09
));
504 enable_hwirq(12, FUNC16(entry_74
));
506 run_thread(keyboard_init
, NULL
);