blob: 1f042999047ed818e7a5b458c0c03716b9b4cc0d [file] [log] [blame]
Kevin O'Connor3b897192008-07-20 10:08:59 -04001// Support for handling the PS/2 mouse/keyboard ports.
2//
3// Copyright (C) 2008 Kevin O'Connor <kevin@koconnor.net>
Kevin O'Connorb1b7c2a2009-01-15 20:52:58 -05004// Several ideas taken from code Copyright (c) 1999-2004 Vojtech Pavlik
Kevin O'Connor3b897192008-07-20 10:08:59 -04005//
Kevin O'Connorb1b7c2a2009-01-15 20:52:58 -05006// This file may be distributed under the terms of the GNU LGPLv3 license.
Kevin O'Connor3b897192008-07-20 10:08:59 -04007
8#include "ioport.h" // inb
9#include "util.h" // dprintf
Kevin O'Connoraafe4422011-07-05 20:44:00 -040010#include "paravirt.h" // romfile_loadint
Kevin O'Connor3b897192008-07-20 10:08:59 -040011#include "biosvar.h" // GET_EBDA
Kevin O'Connordd5a8a62010-05-01 19:59:34 -040012#include "ps2port.h" // ps2_kbd_command
Kevin O'Connor57877482009-12-09 21:00:41 -050013#include "pic.h" // eoi_pic1
Kevin O'Connor3b897192008-07-20 10:08:59 -040014
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
Kevin O'Connor3b897192008-07-20 10:08:59 -040025static int
26i8042_wait_read(void)
27{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050028 dprintf(7, "i8042_wait_read\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040029 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 }
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050036 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040037 return -1;
38}
39
40static int
41i8042_wait_write(void)
42{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050043 dprintf(7, "i8042_wait_write\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040044 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 }
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050051 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040052 return -1;
53}
54
Kevin O'Connoradaf3732010-09-13 20:22:07 -040055static int
Kevin O'Connor3b897192008-07-20 10:08:59 -040056i8042_flush(void)
57{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050058 dprintf(7, "i8042_flush\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040059 int i;
60 for (i=0; i<I8042_BUFFER_SIZE; i++) {
61 u8 status = inb(PORT_PS2_STATUS);
Kevin O'Connor10ad7992009-10-24 11:06:08 -040062 if (! (status & I8042_STR_OBF))
Kevin O'Connor3b897192008-07-20 10:08:59 -040063 return 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -040064 udelay(50);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050065 u8 data = inb(PORT_PS2_DATA);
Kevin O'Connor9e91c7b2009-01-17 02:30:21 -050066 dprintf(7, "i8042 flushed %x (status=%x)\n", data, status);
Kevin O'Connor3b897192008-07-20 10:08:59 -040067 }
68
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050069 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040070 return -1;
71}
72
73static 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);
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500100 dprintf(7, "i8042 param=%x\n", param[i]);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400101 }
102
103 return 0;
104}
105
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400106static int
Kevin O'Connor3b897192008-07-20 10:08:59 -0400107i8042_command(int command, u8 *param)
108{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500109 dprintf(7, "i8042_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400110 int ret = __i8042_command(command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400111 if (ret)
112 dprintf(2, "i8042 command %x failed\n", command);
113 return ret;
114}
115
116static int
117i8042_kbd_write(u8 c)
118{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500119 dprintf(7, "i8042_kbd_write c=%d\n", c);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400120 int ret = i8042_wait_write();
121 if (! ret)
122 outb(c, PORT_PS2_DATA);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400123 return ret;
124}
125
126static int
127i8042_aux_write(u8 c)
128{
129 return i8042_command(I8042_CMD_AUX_SEND, &c);
130}
131
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400132void
133i8042_reboot(void)
134{
Kevin O'Connore216ce82012-01-29 12:25:46 -0500135 if (! CONFIG_PS2PORT)
136 return;
Kevin O'Connoradaf3732010-09-13 20:22:07 -0400137 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
Kevin O'Connor3b897192008-07-20 10:08:59 -0400146
147/****************************************************************
148 * Device commands.
149 ****************************************************************/
150
151#define PS2_RET_ACK 0xfa
152#define PS2_RET_NAK 0xfe
153
154static int
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500155ps2_recvbyte(int aux, int needack, int timeout)
156{
157 u64 end = calc_future_tsc(timeout);
158 for (;;) {
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400159 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) {
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500170 dprintf(1, "Got ps2 nak (status=%x)\n", status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400171 return data;
172 }
173 }
174
Kevin O'Connorf6282442010-03-13 21:05:12 -0500175 // This data not part of command - just discard it.
176 dprintf(1, "Discarding ps2 data %02x (status=%02x)\n", data, status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400177 }
178
Kevin O'Connor144817b2010-05-23 10:46:49 -0400179 if (check_tsc(end)) {
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -0500180 // Don't warn on second byte of a reset
181 if (timeout > 100)
182 warn_timeout();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500183 return -1;
184 }
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400185 yield();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500186 }
187}
188
189static int
Kevin O'Connor235df202009-01-18 12:54:51 -0500190ps2_sendbyte(int aux, u8 command, int timeout)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400191{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500192 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400193 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.
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500202 ret = ps2_recvbyte(aux, 1, timeout);
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500203 if (ret < 0)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400204 return ret;
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500205 if (ret != PS2_RET_ACK)
206 return -1;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400207
208 return 0;
209}
210
211static int
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400212__ps2_command(int aux, int command, u8 *param)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400213{
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500214 int ret2;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400215 int receive = (command >> 8) & 0xf;
216 int send = (command >> 12) & 0xf;
217
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500218 // Disable interrupts and keyboard/mouse.
219 u8 ps2ctr = GET_EBDA(ps2ctr);
Kevin O'Connorf6282442010-03-13 21:05:12 -0500220 u8 newctr = ((ps2ctr | I8042_CTR_AUXDIS | I8042_CTR_KBDDIS)
221 & ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT));
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500222 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;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400226
Kevin O'Connor67a9eec2010-03-13 19:00:02 -0500227 // Flush any interrupts already pending.
228 yield();
229
Kevin O'Connorf6282442010-03-13 21:05:12 -0500230 // 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
Kevin O'Connor235df202009-01-18 12:54:51 -0500239 if (command == ATKBD_CMD_RESET_BAT) {
Kevin O'Connorb9ed5e22010-03-13 19:59:24 -0500240 // Reset is special wrt timeouts and bytes received.
Kevin O'Connor3b897192008-07-20 10:08:59 -0400241
Kevin O'Connor235df202009-01-18 12:54:51 -0500242 // Send command.
243 ret = ps2_sendbyte(aux, command, 1000);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400244 if (ret)
245 goto fail;
Kevin O'Connor235df202009-01-18 12:54:51 -0500246
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;
Kevin O'Connorb9ed5e22010-03-13 19:59:24 -0500257 } 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 }
Kevin O'Connor235df202009-01-18 12:54:51 -0500280 } 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 }
Kevin O'Connor3b897192008-07-20 10:08:59 -0400301 }
302
Kevin O'Connor235df202009-01-18 12:54:51 -0500303 ret = 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400304
305fail:
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500306 // Restore interrupts and keyboard/mouse.
307 ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
308 if (ret2)
309 return ret2;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400310
311 return ret;
312}
313
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400314static int
315ps2_command(int aux, int command, u8 *param)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400316{
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400317 dprintf(7, "ps2_command aux=%d cmd=%x\n", aux, command);
318 int ret = __ps2_command(aux, command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400319 if (ret)
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400320 dprintf(2, "ps2 command %x failed (aux=%d)\n", command, aux);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400321 return ret;
322}
323
324int
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400325ps2_kbd_command(int command, u8 *param)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400326{
Kevin O'Connore216ce82012-01-29 12:25:46 -0500327 if (! CONFIG_PS2PORT)
328 return -1;
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400329 return ps2_command(0, command, param);
330}
331
332int
333ps2_mouse_command(int command, u8 *param)
334{
Kevin O'Connore216ce82012-01-29 12:25:46 -0500335 if (! CONFIG_PS2PORT)
336 return -1;
337
Kevin O'Connor9ead6fc2011-05-07 14:19:29 -0400338 // 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
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400349 return ps2_command(1, command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400350}
Kevin O'Connor57877482009-12-09 21:00:41 -0500351
352
353/****************************************************************
354 * IRQ handlers
355 ****************************************************************/
356
357// INT74h : PS/2 mouse hardware interrupt
358void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500359handle_74(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500360{
361 if (! CONFIG_PS2PORT)
362 return;
363
364 debug_isr(DEBUG_ISR_74);
Kevin O'Connora5d84582010-03-13 20:06:34 -0500365
366 u8 v = inb(PORT_PS2_STATUS);
367 if ((v & (I8042_STR_OBF|I8042_STR_AUXDATA))
368 != (I8042_STR_OBF|I8042_STR_AUXDATA)) {
Kevin O'Connorf6282442010-03-13 21:05:12 -0500369 dprintf(1, "ps2 mouse irq but no mouse data.\n");
Kevin O'Connora5d84582010-03-13 20:06:34 -0500370 goto done;
371 }
372 v = inb(PORT_PS2_DATA);
373
Kevin O'Connorf6282442010-03-13 21:05:12 -0500374 if (!(GET_EBDA(ps2ctr) & I8042_CTR_AUXINT))
375 // Interrupts not enabled.
376 goto done;
377
Kevin O'Connora5d84582010-03-13 20:06:34 -0500378 process_mouse(v);
379
380done:
Kevin O'Connor57877482009-12-09 21:00:41 -0500381 eoi_pic2();
382}
383
384// INT09h : Keyboard Hardware Service Entry Point
385void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500386handle_09(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500387{
388 if (! CONFIG_PS2PORT)
389 return;
390
391 debug_isr(DEBUG_ISR_09);
Kevin O'Connora5d84582010-03-13 20:06:34 -0500392
393 // read key from keyboard controller
394 u8 v = inb(PORT_PS2_STATUS);
Kevin O'Connorf6282442010-03-13 21:05:12 -0500395 if (v & I8042_STR_AUXDATA) {
396 dprintf(1, "ps2 keyboard irq but found mouse data?!\n");
Kevin O'Connora5d84582010-03-13 20:06:34 -0500397 goto done;
398 }
399 v = inb(PORT_PS2_DATA);
400
Kevin O'Connorf6282442010-03-13 21:05:12 -0500401 if (!(GET_EBDA(ps2ctr) & I8042_CTR_KBDINT))
402 // Interrupts not enabled.
403 goto done;
404
Kevin O'Connora5d84582010-03-13 20:06:34 -0500405 process_key(v);
406
407done:
Kevin O'Connor57877482009-12-09 21:00:41 -0500408 eoi_pic1();
409}
410
411
412/****************************************************************
413 * Setup
414 ****************************************************************/
415
416static void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500417keyboard_init(void *data)
Kevin O'Connor57877482009-12-09 21:00:41 -0500418{
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
Kevin O'Connorf6282442010-03-13 21:05:12 -0500443 // Disable keyboard and mouse events.
444 SET_EBDA(ps2ctr, I8042_CTR_KBDDIS | I8042_CTR_AUXDIS);
Kevin O'Connor57877482009-12-09 21:00:41 -0500445
446
447 /* ------------------- keyboard side ------------------------*/
448 /* reset keyboard and self test (keyboard side) */
Kevin O'Connoraafe4422011-07-05 20:44:00 -0400449 int spinupdelay = romfile_loadint("etc/ps2-keyboard-spinup", 0);
450 u64 end = calc_future_tsc(spinupdelay);
Kevin O'Connor299dc132011-05-28 11:00:28 -0400451 for (;;) {
452 ret = ps2_kbd_command(ATKBD_CMD_RESET_BAT, param);
453 if (!ret)
454 break;
455 if (check_tsc(end)) {
Kevin O'Connoraafe4422011-07-05 20:44:00 -0400456 if (spinupdelay)
Kevin O'Connor299dc132011-05-28 11:00:28 -0400457 warn_timeout();
458 return;
459 }
460 yield();
461 }
Kevin O'Connor57877482009-12-09 21:00:41 -0500462 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 */
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400468 ret = ps2_kbd_command(ATKBD_CMD_RESET_DIS, NULL);
Kevin O'Connor57877482009-12-09 21:00:41 -0500469 if (ret)
470 return;
471
472 // Set scancode command (mode 2)
473 param[0] = 0x02;
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400474 ret = ps2_kbd_command(ATKBD_CMD_SSCANSET, param);
Kevin O'Connor57877482009-12-09 21:00:41 -0500475 if (ret)
476 return;
477
Kevin O'Connorf6282442010-03-13 21:05:12 -0500478 // Keyboard Mode: disable mouse, scan code convert, enable kbd IRQ
Kevin O'Connor6704cf92010-03-13 18:51:46 -0500479 SET_EBDA(ps2ctr, I8042_CTR_AUXDIS | I8042_CTR_XLATE | I8042_CTR_KBDINT);
Kevin O'Connor57877482009-12-09 21:00:41 -0500480
481 /* Enable keyboard */
Kevin O'Connordd5a8a62010-05-01 19:59:34 -0400482 ret = ps2_kbd_command(ATKBD_CMD_ENABLE, NULL);
Kevin O'Connor57877482009-12-09 21:00:41 -0500483 if (ret)
484 return;
485
Kevin O'Connor357bdfa2010-02-26 08:57:13 -0500486 dprintf(1, "PS2 keyboard initialized\n");
Kevin O'Connor57877482009-12-09 21:00:41 -0500487}
488
489void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500490ps2port_setup(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500491{
Kevin O'Connor59c75742010-02-13 18:49:24 -0500492 ASSERT32FLAT();
Kevin O'Connor57877482009-12-09 21:00:41 -0500493 if (! CONFIG_PS2PORT)
494 return;
495 dprintf(3, "init ps2port\n");
496
Kevin O'Connorcc9e1bf2010-07-28 21:31:38 -0400497 enable_hwirq(1, FUNC16(entry_09));
498 enable_hwirq(12, FUNC16(entry_74));
Kevin O'Connor57877482009-12-09 21:00:41 -0500499
500 run_thread(keyboard_init, NULL);
501}