58335af913852b7893ec6f4fad1c743531aabdf4
[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     int i;
136     for (i=0; i<10; i++) {
137         i8042_wait_write();
138         udelay(50);
139         outb(0xfe, PORT_PS2_STATUS); /* pulse reset low */
140         udelay(50);
141     }
142 }
143
144
145 /****************************************************************
146  * Device commands.
147  ****************************************************************/
148
149 #define PS2_RET_ACK             0xfa
150 #define PS2_RET_NAK             0xfe
151
152 static int
153 ps2_recvbyte(int aux, int needack, int timeout)
154 {
155     u64 end = calc_future_tsc(timeout);
156     for (;;) {
157         u8 status = inb(PORT_PS2_STATUS);
158         if (status & I8042_STR_OBF) {
159             u8 data = inb(PORT_PS2_DATA);
160             dprintf(7, "ps2 read %x\n", data);
161
162             if (!!(status & I8042_STR_AUXDATA) == aux) {
163                 if (!needack)
164                     return data;
165                 if (data == PS2_RET_ACK)
166                     return data;
167                 if (data == PS2_RET_NAK) {
168                     dprintf(1, "Got ps2 nak (status=%x)\n", status);
169                     return data;
170                 }
171             }
172
173             // This data not part of command - just discard it.
174             dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data, status);
175         }
176
177         if (check_tsc(end)) {
178             // Don't warn on second byte of a reset
179             if (timeout > 100)
180                 warn_timeout();
181             return -1;
182         }
183         yield();
184     }
185 }
186
187 static int
188 ps2_sendbyte(int aux, u8 command, int timeout)
189 {
190     dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
191     int ret;
192     if (aux)
193         ret = i8042_aux_write(command);
194     else
195         ret = i8042_kbd_write(command);
196     if (ret)
197         return ret;
198
199     // Read ack.
200     ret = ps2_recvbyte(aux, 1, timeout);
201     if (ret < 0)
202         return ret;
203     if (ret != PS2_RET_ACK)
204         return -1;
205
206     return 0;
207 }
208
209 static int
210 __ps2_command(int aux, int command, u8 *param)
211 {
212     int ret2;
213     int receive = (command >> 8) & 0xf;
214     int send = (command >> 12) & 0xf;
215
216     // Disable interrupts and keyboard/mouse.
217     u8 ps2ctr = GET_EBDA(ps2ctr);
218     u8 newctr = ((ps2ctr | I8042_CTR_AUXDIS | I8042_CTR_KBDDIS)
219                  & ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT));
220     dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
221     int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
222     if (ret)
223         return ret;
224
225     // Flush any interrupts already pending.
226     yield();
227
228     // Enable port command is being sent to.
229     if (aux)
230         newctr &= ~I8042_CTR_AUXDIS;
231     else
232         newctr &= ~I8042_CTR_KBDDIS;
233     ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
234     if (ret)
235         goto fail;
236
237     if (command == ATKBD_CMD_RESET_BAT) {
238         // Reset is special wrt timeouts and bytes received.
239
240         // Send command.
241         ret = ps2_sendbyte(aux, command, 1000);
242         if (ret)
243             goto fail;
244
245         // Receive parameters.
246         ret = ps2_recvbyte(aux, 0, 4000);
247         if (ret < 0)
248             goto fail;
249         param[0] = ret;
250         ret = ps2_recvbyte(aux, 0, 100);
251         if (ret < 0)
252             // Some devices only respond with one byte on reset.
253             ret = 0;
254         param[1] = ret;
255     } else if (command == ATKBD_CMD_GETID) {
256         // Getid is special wrt bytes received.
257
258         // Send command.
259         ret = ps2_sendbyte(aux, command, 200);
260         if (ret)
261             goto fail;
262
263         // Receive parameters.
264         ret = ps2_recvbyte(aux, 0, 500);
265         if (ret < 0)
266             goto fail;
267         param[0] = ret;
268         if (ret == 0xab || ret == 0xac || ret == 0x2b || ret == 0x5d
269             || ret == 0x60 || ret == 0x47) {
270             // These ids (keyboards) return two bytes.
271             ret = ps2_recvbyte(aux, 0, 500);
272             if (ret < 0)
273                 goto fail;
274             param[1] = ret;
275         } else {
276             param[1] = 0;
277         }
278     } else {
279         // Send command.
280         ret = ps2_sendbyte(aux, command, 200);
281         if (ret)
282             goto fail;
283
284         // Send parameters (if any).
285         int i;
286         for (i = 0; i < send; i++) {
287             ret = ps2_sendbyte(aux, param[i], 200);
288             if (ret)
289                 goto fail;
290         }
291
292         // Receive parameters (if any).
293         for (i = 0; i < receive; i++) {
294             ret = ps2_recvbyte(aux, 0, 500);
295             if (ret < 0)
296                 goto fail;
297             param[i] = ret;
298         }
299     }
300
301     ret = 0;
302
303 fail:
304     // Restore interrupts and keyboard/mouse.
305     ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
306     if (ret2)
307         return ret2;
308
309     return ret;
310 }
311
312 static int
313 ps2_command(int aux, int command, u8 *param)
314 {
315     dprintf(7, "ps2_command aux=%d cmd=%x\n", aux, command);
316     int ret = __ps2_command(aux, command, param);
317     if (ret)
318         dprintf(2, "ps2 command %x failed (aux=%d)\n", command, aux);
319     return ret;
320 }
321
322 int
323 ps2_kbd_command(int command, u8 *param)
324 {
325     return ps2_command(0, command, param);
326 }
327
328 int
329 ps2_mouse_command(int command, u8 *param)
330 {
331     // Update ps2ctr for mouse enable/disable.
332     if (command == PSMOUSE_CMD_ENABLE || command == PSMOUSE_CMD_DISABLE) {
333         u16 ebda_seg = get_ebda_seg();
334         u8 ps2ctr = GET_EBDA2(ebda_seg, ps2ctr);
335         if (command == PSMOUSE_CMD_ENABLE)
336             ps2ctr = (ps2ctr | I8042_CTR_AUXINT) & ~I8042_CTR_AUXDIS;
337         else
338             ps2ctr = (ps2ctr | I8042_CTR_AUXDIS) & ~I8042_CTR_AUXINT;
339         SET_EBDA2(ebda_seg, ps2ctr, ps2ctr);
340     }
341
342     return ps2_command(1, command, param);
343 }
344
345
346 /****************************************************************
347  * IRQ handlers
348  ****************************************************************/
349
350 // INT74h : PS/2 mouse hardware interrupt
351 void VISIBLE16
352 handle_74(void)
353 {
354     if (! CONFIG_PS2PORT)
355         return;
356
357     debug_isr(DEBUG_ISR_74);
358
359     u8 v = inb(PORT_PS2_STATUS);
360     if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
361         != (I8042_STR_OBF|I8042_STR_AUXDATA)) {
362         dprintf(1, "ps2 mouse irq but no mouse data.\n");
363         goto done;
364     }
365     v = inb(PORT_PS2_DATA);
366
367     if (!(GET_EBDA(ps2ctr) & I8042_CTR_AUXINT))
368         // Interrupts not enabled.
369         goto done;
370
371     process_mouse(v);
372
373 done:
374     eoi_pic2();
375 }
376
377 // INT09h : Keyboard Hardware Service Entry Point
378 void VISIBLE16
379 handle_09(void)
380 {
381     if (! CONFIG_PS2PORT)
382         return;
383
384     debug_isr(DEBUG_ISR_09);
385
386     // read key from keyboard controller
387     u8 v = inb(PORT_PS2_STATUS);
388     if (v & I8042_STR_AUXDATA) {
389         dprintf(1, "ps2 keyboard irq but found mouse data?!\n");
390         goto done;
391     }
392     v = inb(PORT_PS2_DATA);
393
394     if (!(GET_EBDA(ps2ctr) & I8042_CTR_KBDINT))
395         // Interrupts not enabled.
396         goto done;
397
398     process_key(v);
399
400 done:
401     eoi_pic1();
402 }
403
404
405 /****************************************************************
406  * Setup
407  ****************************************************************/
408
409 static void
410 keyboard_init(void *data)
411 {
412     /* flush incoming keys */
413     int ret = i8042_flush();
414     if (ret)
415         return;
416
417     // Controller self-test.
418     u8 param[2];
419     ret = i8042_command(I8042_CMD_CTL_TEST, param);
420     if (ret)
421         return;
422     if (param[0] != 0x55) {
423         dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
424         return;
425     }
426
427     // Controller keyboard test.
428     ret = i8042_command(I8042_CMD_KBD_TEST, param);
429     if (ret)
430         return;
431     if (param[0] != 0x00) {
432         dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
433         return;
434     }
435
436     // Disable keyboard and mouse events.
437     SET_EBDA(ps2ctr, I8042_CTR_KBDDIS | I8042_CTR_AUXDIS);
438
439
440     /* ------------------- keyboard side ------------------------*/
441     /* reset keyboard and self test  (keyboard side) */
442     int spinupdelay = romfile_loadint("etc/ps2-keyboard-spinup", 0);
443     u64 end = calc_future_tsc(spinupdelay);
444     for (;;) {
445         ret = ps2_kbd_command(ATKBD_CMD_RESET_BAT, param);
446         if (!ret)
447             break;
448         if (check_tsc(end)) {
449             if (spinupdelay)
450                 warn_timeout();
451             return;
452         }
453         yield();
454     }
455     if (param[0] != 0xaa) {
456         dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
457         return;
458     }
459
460     /* Disable keyboard */
461     ret = ps2_kbd_command(ATKBD_CMD_RESET_DIS, NULL);
462     if (ret)
463         return;
464
465     // Set scancode command (mode 2)
466     param[0] = 0x02;
467     ret = ps2_kbd_command(ATKBD_CMD_SSCANSET, param);
468     if (ret)
469         return;
470
471     // Keyboard Mode: disable mouse, scan code convert, enable kbd IRQ
472     SET_EBDA(ps2ctr, I8042_CTR_AUXDIS | I8042_CTR_XLATE | I8042_CTR_KBDINT);
473
474     /* Enable keyboard */
475     ret = ps2_kbd_command(ATKBD_CMD_ENABLE, NULL);
476     if (ret)
477         return;
478
479     dprintf(1, "PS2 keyboard initialized\n");
480 }
481
482 void
483 ps2port_setup(void)
484 {
485     ASSERT32FLAT();
486     if (! CONFIG_PS2PORT)
487         return;
488     dprintf(3, "init ps2port\n");
489
490     enable_hwirq(1, FUNC16(entry_09));
491     enable_hwirq(12, FUNC16(entry_74));
492
493     run_thread(keyboard_init, NULL);
494 }