Palacios Public Git Repository

To checkout Palacios execute

  git clone http://v3vee.org/palacios/palacios.web/palacios.git
This will give you the master branch. You probably want the devel branch or one of the release branches. To switch to the devel branch, simply execute
  cd palacios
  git checkout --track -b devel origin/devel
The other branches are similar.


imported SEABIOS source tree
[palacios.git] / bios / seabios / 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 }