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