Add Cserve_Get_VM_Time
[qemu-palcode.git] / ps2port.c
blob761c2e60b2bc501b674c3495af3541faa4ee6c78
1 // Support for handling the PS/2 mouse/keyboard ports.
2 //
3 // Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
4 // Several ideas taken from code Copyright (c) 1999-2004 Vojtech Pavlik
5 //
6 // This file may be distributed under the terms of the GNU LGPLv3 license.
7 //
8 // This file is copied (mostly) intact from SeaBIOS.
10 #include "protos.h"
11 #include "ioport.h"
12 #include "ps2port.h"
14 typedef uint64_t u64;
16 #define dprintf(lvl, fmt, ...)
17 #define warn_timeout()
19 static inline u64
20 calc_future_tsc(int timeout)
22 return get_wall_time() + timeout;
25 static inline bool
26 check_tsc(u64 end)
28 return get_wall_time() > end;
31 static inline void
32 yield(void)
34 udelay(1);
37 static struct {
38 u8 ps2ctr;
39 } ebda;
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 ****************************************************************/
54 // Timeout value.
55 #define I8042_CTL_TIMEOUT 10000
57 #define I8042_BUFFER_SIZE 16
59 static int
60 i8042_wait_read(void)
62 dprintf(7, "i8042_wait_read\n");
63 int i;
64 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
65 u8 status = inb(PORT_PS2_STATUS);
66 if (status & I8042_STR_OBF)
67 return 0;
68 udelay(50);
70 warn_timeout();
71 return -1;
74 static int
75 i8042_wait_write(void)
77 dprintf(7, "i8042_wait_write\n");
78 int i;
79 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
80 u8 status = inb(PORT_PS2_STATUS);
81 if (! (status & I8042_STR_IBF))
82 return 0;
83 udelay(50);
85 warn_timeout();
86 return -1;
89 static int
90 i8042_flush(void)
92 dprintf(7, "i8042_flush\n");
93 int i;
94 for (i=0; i<I8042_BUFFER_SIZE; i++) {
95 u8 status = inb(PORT_PS2_STATUS);
96 if (! (status & I8042_STR_OBF))
97 return 0;
98 udelay(50);
99 inb(PORT_PS2_DATA);
102 warn_timeout();
103 return -1;
106 static int
107 __i8042_command(int command, u8 *param)
109 int receive = (command >> 8) & 0xf;
110 int send = (command >> 12) & 0xf;
112 // Send the command.
113 int ret = i8042_wait_write();
114 if (ret)
115 return ret;
116 outb(command, PORT_PS2_STATUS);
118 // Send parameters (if any).
119 int i;
120 for (i = 0; i < send; i++) {
121 ret = i8042_wait_write();
122 if (ret)
123 return ret;
124 outb(param[i], PORT_PS2_DATA);
127 // Receive parameters (if any).
128 for (i = 0; i < receive; i++) {
129 ret = i8042_wait_read();
130 if (ret)
131 return ret;
132 param[i] = inb(PORT_PS2_DATA);
133 dprintf(7, "i8042 param=%x\n", param[i]);
136 return 0;
139 static int
140 i8042_command(int command, u8 *param)
142 dprintf(7, "i8042_command cmd=%x\n", command);
143 int ret = __i8042_command(command, param);
144 if (ret)
145 dprintf(2, "i8042 command %x failed\n", command);
146 return ret;
149 static int
150 i8042_kbd_write(u8 c)
152 dprintf(7, "i8042_kbd_write c=%d\n", c);
153 int ret = i8042_wait_write();
154 if (! ret)
155 outb(c, PORT_PS2_DATA);
156 return ret;
159 static int
160 i8042_aux_write(u8 c)
162 return i8042_command(I8042_CMD_AUX_SEND, &c);
165 void
166 i8042_reboot(void)
168 int i;
169 for (i=0; i<10; i++) {
170 i8042_wait_write();
171 udelay(50);
172 outb(0xfe, PORT_PS2_STATUS); /* pulse reset low */
173 udelay(50);
178 /****************************************************************
179 * Device commands.
180 ****************************************************************/
182 #define PS2_RET_ACK 0xfa
183 #define PS2_RET_NAK 0xfe
185 static int
186 ps2_recvbyte(int aux, int needack, int timeout)
188 u64 end = calc_future_tsc(timeout);
189 for (;;) {
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) {
196 if (!needack)
197 return data;
198 if (data == PS2_RET_ACK)
199 return data;
200 if (data == PS2_RET_NAK) {
201 dprintf(1, "Got ps2 nak (status=%x)\n", status);
202 return data;
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
212 if (timeout > 100)
213 warn_timeout();
214 return -1;
216 yield();
220 static int
221 ps2_sendbyte(int aux, u8 command, int timeout)
223 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
224 int ret;
225 if (aux)
226 ret = i8042_aux_write(command);
227 else
228 ret = i8042_kbd_write(command);
229 if (ret)
230 return ret;
232 // Read ack.
233 ret = ps2_recvbyte(aux, 1, timeout);
234 if (ret < 0)
235 return ret;
236 if (ret != PS2_RET_ACK)
237 return -1;
239 return 0;
242 static int
243 __ps2_command(int aux, int command, u8 *param)
245 int ret2;
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);
255 if (ret)
256 return ret;
258 // Flush any interrupts already pending.
259 yield();
261 // Enable port command is being sent to.
262 if (aux)
263 newctr &= ~I8042_CTR_AUXDIS;
264 else
265 newctr &= ~I8042_CTR_KBDDIS;
266 ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
267 if (ret)
268 goto fail;
270 if (command == ATKBD_CMD_RESET_BAT) {
271 // Reset is special wrt timeouts and bytes received.
273 // Send command.
274 ret = ps2_sendbyte(aux, command, 1000);
275 if (ret)
276 goto fail;
278 // Receive parameters.
279 ret = ps2_recvbyte(aux, 0, 4000);
280 if (ret < 0)
281 goto fail;
282 param[0] = ret;
283 ret = ps2_recvbyte(aux, 0, 100);
284 if (ret < 0)
285 // Some devices only respond with one byte on reset.
286 ret = 0;
287 param[1] = ret;
288 } else if (command == ATKBD_CMD_GETID) {
289 // Getid is special wrt bytes received.
291 // Send command.
292 ret = ps2_sendbyte(aux, command, 200);
293 if (ret)
294 goto fail;
296 // Receive parameters.
297 ret = ps2_recvbyte(aux, 0, 500);
298 if (ret < 0)
299 goto fail;
300 param[0] = ret;
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);
305 if (ret < 0)
306 goto fail;
307 param[1] = ret;
308 } else {
309 param[1] = 0;
311 } else {
312 // Send command.
313 ret = ps2_sendbyte(aux, command, 200);
314 if (ret)
315 goto fail;
317 // Send parameters (if any).
318 int i;
319 for (i = 0; i < send; i++) {
320 ret = ps2_sendbyte(aux, param[i], 200);
321 if (ret)
322 goto fail;
325 // Receive parameters (if any).
326 for (i = 0; i < receive; i++) {
327 ret = ps2_recvbyte(aux, 0, 500);
328 if (ret < 0)
329 goto fail;
330 param[i] = ret;
334 ret = 0;
336 fail:
337 // Restore interrupts and keyboard/mouse.
338 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
339 if (ret2)
340 return ret2;
342 return ret;
345 static int
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);
350 if (ret)
351 dprintf(2, "ps2 command %x failed (aux=%d)\n", command, aux);
352 return ret;
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 /****************************************************************
369 * IRQ handlers
370 ****************************************************************/
371 #if 0
373 // INT74h : PS/2 mouse hardware interrupt
374 void VISIBLE16
375 handle_74(void)
377 if (! CONFIG_PS2PORT)
378 return;
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");
386 goto done;
388 v = inb(PORT_PS2_DATA);
390 if (!(GET_EBDA(ps2ctr) & I8042_CTR_AUXINT))
391 // Interrupts not enabled.
392 goto done;
394 process_mouse(v);
396 done:
397 eoi_pic2();
400 // INT09h : Keyboard Hardware Service Entry Point
401 void VISIBLE16
402 handle_09(void)
404 if (! CONFIG_PS2PORT)
405 return;
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");
413 goto done;
415 v = inb(PORT_PS2_DATA);
417 if (!(GET_EBDA(ps2ctr) & I8042_CTR_KBDINT))
418 // Interrupts not enabled.
419 goto done;
421 process_key(v);
423 done:
424 eoi_pic1();
427 #endif
428 /****************************************************************
429 * Setup
430 ****************************************************************/
432 static void
433 keyboard_init(void *data)
435 /* flush incoming keys */
436 int ret = i8042_flush();
437 if (ret)
438 return;
440 // Controller self-test.
441 u8 param[2];
442 ret = i8042_command(I8042_CMD_CTL_TEST, param);
443 if (ret)
444 return;
445 if (param[0] != 0x55) {
446 dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
447 return;
450 // Controller keyboard test.
451 ret = i8042_command(I8042_CMD_KBD_TEST, param);
452 if (ret)
453 return;
454 if (param[0] != 0x00) {
455 dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
456 return;
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);
466 if (ret)
467 return;
468 if (param[0] != 0xaa) {
469 dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
470 return;
473 /* Disable keyboard */
474 ret = ps2_kbd_command(ATKBD_CMD_RESET_DIS, NULL);
475 if (ret)
476 return;
478 // Set scancode command (mode 2)
479 param[0] = 0x02;
480 ret = ps2_kbd_command(ATKBD_CMD_SSCANSET, param);
481 if (ret)
482 return;
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);
489 if (ret)
490 return;
492 dprintf(1, "PS2 keyboard initialized\n");
495 void
496 ps2port_setup(void)
498 ASSERT32FLAT();
499 if (! CONFIG_PS2PORT)
500 return;
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);