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 #include "biosvar.h" // GET_LOW
9 #include "output.h" // dprintf
10 #include "pic.h" // pic_eoi1
11 #include "ps2port.h" // ps2_kbd_command
12 #include "romfile.h" // romfile_loadint
13 #include "stacks.h" // yield
14 #include "util.h" // udelay
15 #include "x86.h" // inb
16
17
18 /****************************************************************
19 * Low level i8042 commands.
20 ****************************************************************/
21
22 // Timeout value.
23 #define I8042_CTL_TIMEOUT 10000
24
25 #define I8042_BUFFER_SIZE 16
26
27 static int
i8042_wait_read(void)28 i8042_wait_read(void)
29 {
30 dprintf(7, "i8042_wait_read\n");
31 int i;
32 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
33 u8 status = inb(PORT_PS2_STATUS);
34 if (status & I8042_STR_OBF)
35 return 0;
36 udelay(50);
37 }
38 warn_timeout();
39 return -1;
40 }
41
42 static int
i8042_wait_write(void)43 i8042_wait_write(void)
44 {
45 dprintf(7, "i8042_wait_write\n");
46 int i;
47 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
48 u8 status = inb(PORT_PS2_STATUS);
49 if (! (status & I8042_STR_IBF))
50 return 0;
51 udelay(50);
52 }
53 warn_timeout();
54 return -1;
55 }
56
57 static int
i8042_flush(void)58 i8042_flush(void)
59 {
60 dprintf(7, "i8042_flush\n");
61 int i;
62 for (i=0; i<I8042_BUFFER_SIZE; i++) {
63 u8 status = inb(PORT_PS2_STATUS);
64 if (! (status & I8042_STR_OBF))
65 return 0;
66 udelay(50);
67 u8 data = inb(PORT_PS2_DATA);
68 dprintf(7, "i8042 flushed %x (status=%x)\n", data, status);
69 }
70
71 warn_timeout();
72 return -1;
73 }
74
75 static int
__i8042_command(int command,u8 * param)76 __i8042_command(int command, u8 *param)
77 {
78 int receive = (command >> 8) & 0xf;
79 int send = (command >> 12) & 0xf;
80
81 // Send the command.
82 int ret = i8042_wait_write();
83 if (ret)
84 return ret;
85 outb(command, PORT_PS2_STATUS);
86
87 // Send parameters (if any).
88 int i;
89 for (i = 0; i < send; i++) {
90 ret = i8042_wait_write();
91 if (ret)
92 return ret;
93 outb(param[i], PORT_PS2_DATA);
94 }
95
96 // Receive parameters (if any).
97 for (i = 0; i < receive; i++) {
98 ret = i8042_wait_read();
99 if (ret)
100 return ret;
101 param[i] = inb(PORT_PS2_DATA);
102 dprintf(7, "i8042 param=%x\n", param[i]);
103 }
104
105 return 0;
106 }
107
108 static int
i8042_command(int command,u8 * param)109 i8042_command(int command, u8 *param)
110 {
111 dprintf(7, "i8042_command cmd=%x\n", command);
112 int ret = __i8042_command(command, param);
113 if (ret)
114 dprintf(2, "i8042 command %x failed\n", command);
115 return ret;
116 }
117
118 static int
i8042_kbd_write(u8 c)119 i8042_kbd_write(u8 c)
120 {
121 dprintf(7, "i8042_kbd_write c=%d\n", c);
122 int ret = i8042_wait_write();
123 if (! ret)
124 outb(c, PORT_PS2_DATA);
125 return ret;
126 }
127
128 static int
i8042_aux_write(u8 c)129 i8042_aux_write(u8 c)
130 {
131 return i8042_command(I8042_CMD_AUX_SEND, &c);
132 }
133
134 void
i8042_reboot(void)135 i8042_reboot(void)
136 {
137 if (! CONFIG_PS2PORT)
138 return;
139 int i;
140 for (i=0; i<10; i++) {
141 i8042_wait_write();
142 udelay(50);
143 outb(0xfe, PORT_PS2_STATUS); /* pulse reset low */
144 udelay(50);
145 }
146 }
147
148
149 /****************************************************************
150 * Device commands.
151 ****************************************************************/
152
153 #define PS2_RET_ACK 0xfa
154 #define PS2_RET_NAK 0xfe
155
156 static int
ps2_recvbyte(int aux,int needack,int timeout)157 ps2_recvbyte(int aux, int needack, int timeout)
158 {
159 u32 end = timer_calc(timeout);
160 for (;;) {
161 u8 status = inb(PORT_PS2_STATUS);
162 if (status & I8042_STR_OBF) {
163 u8 data = inb(PORT_PS2_DATA);
164 dprintf(7, "ps2 read %x\n", data);
165
166 if (!!(status & I8042_STR_AUXDATA) == aux) {
167 if (!needack)
168 return data;
169 if (data == PS2_RET_ACK)
170 return data;
171 if (data == PS2_RET_NAK) {
172 dprintf(1, "Got ps2 nak (status=%x)\n", status);
173 return data;
174 }
175 }
176
177 // This data not part of command - just discard it.
178 dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data, status);
179 }
180
181 if (timer_check(end)) {
182 warn_timeout();
183 return -1;
184 }
185 yield();
186 }
187 }
188
189 static int
ps2_sendbyte(int aux,u8 command,int timeout)190 ps2_sendbyte(int aux, u8 command, int timeout)
191 {
192 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
193 int ret;
194 if (aux)
195 ret = i8042_aux_write(command);
196 else
197 ret = i8042_kbd_write(command);
198 if (ret)
199 return ret;
200
201 // Read ack.
202 ret = ps2_recvbyte(aux, 1, timeout);
203 if (ret < 0)
204 return ret;
205 if (ret != PS2_RET_ACK)
206 return -1;
207
208 return 0;
209 }
210
211 u8 Ps2ctr VARLOW = I8042_CTR_KBDDIS | I8042_CTR_AUXDIS;
212
213 static int
__ps2_command(int aux,int command,u8 * param)214 __ps2_command(int aux, int command, u8 *param)
215 {
216 int ret2;
217 int receive = (command >> 8) & 0xf;
218 int send = (command >> 12) & 0xf;
219
220 // Disable interrupts and keyboard/mouse.
221 u8 ps2ctr = GET_LOW(Ps2ctr);
222 u8 newctr = ((ps2ctr | I8042_CTR_AUXDIS | I8042_CTR_KBDDIS)
223 & ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT));
224 dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
225 int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
226 if (ret)
227 return ret;
228
229 // Flush any interrupts already pending.
230 yield();
231
232 // Enable port command is being sent to.
233 SET_LOW(Ps2ctr, newctr);
234 if (aux)
235 newctr &= ~I8042_CTR_AUXDIS;
236 else
237 newctr &= ~I8042_CTR_KBDDIS;
238 ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
239 if (ret)
240 goto fail;
241
242 if ((u8)command == (u8)ATKBD_CMD_RESET_BAT) {
243 // Reset is special wrt timeouts.
244
245 // Send command.
246 ret = ps2_sendbyte(aux, command, 1000);
247 if (ret)
248 goto fail;
249
250 // Receive parameters.
251 ret = ps2_recvbyte(aux, 0, 4000);
252 if (ret < 0)
253 goto fail;
254 param[0] = ret;
255 if (receive > 1) {
256 ret = ps2_recvbyte(aux, 0, 500);
257 if (ret < 0)
258 goto fail;
259 param[1] = ret;
260 }
261 } else if (command == ATKBD_CMD_GETID) {
262 // Getid is special wrt bytes received.
263
264 // Send command.
265 ret = ps2_sendbyte(aux, command, 200);
266 if (ret)
267 goto fail;
268
269 // Receive parameters.
270 ret = ps2_recvbyte(aux, 0, 500);
271 if (ret < 0)
272 goto fail;
273 param[0] = ret;
274 if (ret == 0xab || ret == 0xac || ret == 0x2b || ret == 0x5d
275 || ret == 0x60 || ret == 0x47) {
276 // These ids (keyboards) return two bytes.
277 ret = ps2_recvbyte(aux, 0, 500);
278 if (ret < 0)
279 goto fail;
280 param[1] = ret;
281 } else {
282 param[1] = 0;
283 }
284 } else {
285 // Send command.
286 ret = ps2_sendbyte(aux, command, 200);
287 if (ret)
288 goto fail;
289
290 // Send parameters (if any).
291 int i;
292 for (i = 0; i < send; i++) {
293 ret = ps2_sendbyte(aux, param[i], 200);
294 if (ret)
295 goto fail;
296 }
297
298 // Receive parameters (if any).
299 for (i = 0; i < receive; i++) {
300 ret = ps2_recvbyte(aux, 0, 500);
301 if (ret < 0)
302 goto fail;
303 param[i] = ret;
304 }
305 }
306
307 ret = 0;
308
309 fail:
310 // Restore interrupts and keyboard/mouse.
311 SET_LOW(Ps2ctr, ps2ctr);
312 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
313 if (ret2)
314 return ret2;
315
316 return ret;
317 }
318
319 static int
ps2_command(int aux,int command,u8 * param)320 ps2_command(int aux, int command, u8 *param)
321 {
322 dprintf(7, "ps2_command aux=%d cmd=%x\n", aux, command);
323 int ret = __ps2_command(aux, command, param);
324 if (ret)
325 dprintf(2, "ps2 command %x failed (aux=%d)\n", command, aux);
326 return ret;
327 }
328
329 int
ps2_kbd_command(int command,u8 * param)330 ps2_kbd_command(int command, u8 *param)
331 {
332 if (! CONFIG_PS2PORT)
333 return -1;
334 return ps2_command(0, command, param);
335 }
336
337 int
ps2_mouse_command(int command,u8 * param)338 ps2_mouse_command(int command, u8 *param)
339 {
340 if (! CONFIG_PS2PORT)
341 return -1;
342
343 // Update ps2ctr for mouse enable/disable.
344 if (command == PSMOUSE_CMD_ENABLE || command == PSMOUSE_CMD_DISABLE) {
345 u8 ps2ctr = GET_LOW(Ps2ctr);
346 if (command == PSMOUSE_CMD_ENABLE)
347 ps2ctr = ((ps2ctr | (CONFIG_HARDWARE_IRQ ? I8042_CTR_AUXINT : 0))
348 & ~I8042_CTR_AUXDIS);
349 else
350 ps2ctr = (ps2ctr | I8042_CTR_AUXDIS) & ~I8042_CTR_AUXINT;
351 SET_LOW(Ps2ctr, ps2ctr);
352 }
353
354 return ps2_command(1, command, param);
355 }
356
357
358 /****************************************************************
359 * IRQ handlers
360 ****************************************************************/
361
362 // INT74h : PS/2 mouse hardware interrupt
363 void VISIBLE16
handle_74(void)364 handle_74(void)
365 {
366 if (! CONFIG_PS2PORT)
367 return;
368
369 debug_isr(DEBUG_ISR_74);
370
371 u8 v = inb(PORT_PS2_STATUS);
372 if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
373 != (I8042_STR_OBF|I8042_STR_AUXDATA)) {
374 dprintf(1, "ps2 mouse irq but no mouse data.\n");
375 goto done;
376 }
377 v = inb(PORT_PS2_DATA);
378
379 if (!(GET_LOW(Ps2ctr) & I8042_CTR_AUXINT))
380 // Interrupts not enabled.
381 goto done;
382
383 process_mouse(v);
384
385 done:
386 pic_eoi2();
387 }
388
389 // INT09h : Keyboard Hardware Service Entry Point
390 void VISIBLE16
handle_09(void)391 handle_09(void)
392 {
393 if (! CONFIG_PS2PORT)
394 return;
395
396 debug_isr(DEBUG_ISR_09);
397
398 // read key from keyboard controller
399 u8 v = inb(PORT_PS2_STATUS);
400 if (v & I8042_STR_AUXDATA) {
401 dprintf(1, "ps2 keyboard irq but found mouse data?!\n");
402 goto done;
403 }
404 v = inb(PORT_PS2_DATA);
405
406 if (!(GET_LOW(Ps2ctr) & I8042_CTR_KBDINT))
407 // Interrupts not enabled.
408 goto done;
409
410 process_key(v);
411
412 // Some old programs expect ISR to turn keyboard back on.
413 i8042_command(I8042_CMD_KBD_ENABLE, NULL);
414
415 done:
416 pic_eoi1();
417 }
418
419 // Check for ps2 activity on machines without hardware irqs
420 void
ps2_check_event(void)421 ps2_check_event(void)
422 {
423 if (! CONFIG_PS2PORT || CONFIG_HARDWARE_IRQ)
424 return;
425 u8 ps2ctr = GET_LOW(Ps2ctr);
426 if ((ps2ctr & (I8042_CTR_KBDDIS|I8042_CTR_AUXDIS))
427 == (I8042_CTR_KBDDIS|I8042_CTR_AUXDIS))
428 return;
429 for (;;) {
430 u8 status = inb(PORT_PS2_STATUS);
431 if (!(status & I8042_STR_OBF))
432 break;
433 u8 data = inb(PORT_PS2_DATA);
434 if (status & I8042_STR_AUXDATA) {
435 if (!(ps2ctr & I8042_CTR_AUXDIS))
436 process_mouse(data);
437 } else {
438 if (!(ps2ctr & I8042_CTR_KBDDIS))
439 process_key(data);
440 }
441 }
442 }
443
444
445 /****************************************************************
446 * Setup
447 ****************************************************************/
448
449 static void
ps2_keyboard_setup(void * data)450 ps2_keyboard_setup(void *data)
451 {
452 // flush incoming keys (also verifies port is likely present)
453 int ret = i8042_flush();
454 if (ret)
455 return;
456
457 // Disable keyboard / mouse and drain any input they may have sent
458 ret = i8042_command(I8042_CMD_KBD_DISABLE, NULL);
459 if (ret)
460 return;
461 ret = i8042_command(I8042_CMD_AUX_DISABLE, NULL);
462 if (ret)
463 return;
464 ret = i8042_flush();
465 if (ret)
466 return;
467
468 // Controller self-test.
469 u8 param[2];
470 ret = i8042_command(I8042_CMD_CTL_TEST, param);
471 if (ret)
472 return;
473 if (param[0] != 0x55) {
474 dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
475 return;
476 }
477
478 // Controller keyboard test.
479 ret = i8042_command(I8042_CMD_KBD_TEST, param);
480 if (ret)
481 return;
482 if (param[0] != 0x00) {
483 dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
484 return;
485 }
486
487
488 /* ------------------- keyboard side ------------------------*/
489 /* reset keyboard and self test (keyboard side) */
490 int spinupdelay = romfile_loadint("etc/ps2-keyboard-spinup", 0);
491 u32 end = timer_calc(spinupdelay);
492 for (;;) {
493 ret = ps2_kbd_command(ATKBD_CMD_RESET_BAT, param);
494 if (!ret)
495 break;
496 if (timer_check(end)) {
497 if (spinupdelay)
498 warn_timeout();
499 return;
500 }
501 yield();
502 }
503 if (param[0] != 0xaa) {
504 dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
505 return;
506 }
507
508 /* Disable keyboard */
509 ret = ps2_kbd_command(ATKBD_CMD_RESET_DIS, NULL);
510 if (ret)
511 return;
512
513 // Set scancode command (mode 2)
514 param[0] = 0x02;
515 ret = ps2_kbd_command(ATKBD_CMD_SSCANSET, param);
516 if (ret)
517 return;
518
519 // Keyboard Mode: disable mouse, scan code convert, enable kbd IRQ
520 Ps2ctr = (I8042_CTR_AUXDIS | I8042_CTR_XLATE
521 | (CONFIG_HARDWARE_IRQ ? I8042_CTR_KBDINT : 0));
522
523 /* Enable keyboard */
524 ret = ps2_kbd_command(ATKBD_CMD_ENABLE, NULL);
525 if (ret)
526 return;
527
528 dprintf(1, "PS2 keyboard initialized\n");
529 }
530
531 void
ps2port_setup(void)532 ps2port_setup(void)
533 {
534 ASSERT32FLAT();
535 if (! CONFIG_PS2PORT)
536 return;
537 dprintf(3, "init ps2port\n");
538
539 enable_hwirq(1, FUNC16(entry_09));
540 enable_hwirq(12, FUNC16(entry_74));
541
542 run_thread(ps2_keyboard_setup, NULL);
543 }
544