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