blob: 49bf551c58e2fe9f5283485b3673d8547685318d [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
10#include "biosvar.h" // GET_EBDA
11#include "ps2port.h" // kbd_command
Kevin O'Connor57877482009-12-09 21:00:41 -050012#include "pic.h" // eoi_pic1
Kevin O'Connor3b897192008-07-20 10:08:59 -040013
14
15/****************************************************************
16 * Low level i8042 commands.
17 ****************************************************************/
18
19// Timeout value.
20#define I8042_CTL_TIMEOUT 10000
21
22#define I8042_BUFFER_SIZE 16
23
Kevin O'Connor3b897192008-07-20 10:08:59 -040024static int
25i8042_wait_read(void)
26{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050027 dprintf(7, "i8042_wait_read\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040028 int i;
29 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
30 u8 status = inb(PORT_PS2_STATUS);
31 if (status & I8042_STR_OBF)
32 return 0;
33 udelay(50);
34 }
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050035 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040036 return -1;
37}
38
39static int
40i8042_wait_write(void)
41{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050042 dprintf(7, "i8042_wait_write\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040043 int i;
44 for (i=0; i<I8042_CTL_TIMEOUT; i++) {
45 u8 status = inb(PORT_PS2_STATUS);
46 if (! (status & I8042_STR_IBF))
47 return 0;
48 udelay(50);
49 }
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050050 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040051 return -1;
52}
53
54int
55i8042_flush(void)
56{
Kevin O'Connordd3588f2008-11-29 12:41:48 -050057 dprintf(7, "i8042_flush\n");
Kevin O'Connor3b897192008-07-20 10:08:59 -040058 int i;
59 for (i=0; i<I8042_BUFFER_SIZE; i++) {
60 u8 status = inb(PORT_PS2_STATUS);
Kevin O'Connor10ad7992009-10-24 11:06:08 -040061 if (! (status & I8042_STR_OBF))
Kevin O'Connor3b897192008-07-20 10:08:59 -040062 return 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -040063 udelay(50);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050064 u8 data = inb(PORT_PS2_DATA);
Kevin O'Connor9e91c7b2009-01-17 02:30:21 -050065 dprintf(7, "i8042 flushed %x (status=%x)\n", data, status);
Kevin O'Connor3b897192008-07-20 10:08:59 -040066 }
67
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -050068 warn_timeout();
Kevin O'Connor3b897192008-07-20 10:08:59 -040069 return -1;
70}
71
72static int
73__i8042_command(int command, u8 *param)
74{
75 int receive = (command >> 8) & 0xf;
76 int send = (command >> 12) & 0xf;
77
78 // Send the command.
79 int ret = i8042_wait_write();
80 if (ret)
81 return ret;
82 outb(command, PORT_PS2_STATUS);
83
84 // Send parameters (if any).
85 int i;
86 for (i = 0; i < send; i++) {
87 ret = i8042_wait_write();
88 if (ret)
89 return ret;
90 outb(param[i], PORT_PS2_DATA);
91 }
92
93 // Receive parameters (if any).
94 for (i = 0; i < receive; i++) {
95 ret = i8042_wait_read();
96 if (ret)
97 return ret;
98 param[i] = inb(PORT_PS2_DATA);
Kevin O'Connor0234cd92009-01-04 12:20:02 -050099 dprintf(7, "i8042 param=%x\n", param[i]);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400100 }
101
102 return 0;
103}
104
105int
106i8042_command(int command, u8 *param)
107{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500108 dprintf(7, "i8042_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400109 int ret = __i8042_command(command, param);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400110 if (ret)
111 dprintf(2, "i8042 command %x failed\n", command);
112 return ret;
113}
114
115static int
116i8042_kbd_write(u8 c)
117{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500118 dprintf(7, "i8042_kbd_write c=%d\n", c);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400119 int ret = i8042_wait_write();
120 if (! ret)
121 outb(c, PORT_PS2_DATA);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400122 return ret;
123}
124
125static int
126i8042_aux_write(u8 c)
127{
128 return i8042_command(I8042_CMD_AUX_SEND, &c);
129}
130
131
132/****************************************************************
133 * Device commands.
134 ****************************************************************/
135
136#define PS2_RET_ACK 0xfa
137#define PS2_RET_NAK 0xfe
138
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500139static void
140process_ps2byte(u8 status, u8 data)
141{
142 if (!MODE16) {
143 // Don't pull in all of keyboard/mouse code into 32bit code -
144 // just discard the data.
145 dprintf(1, "Discarding ps2 data %x (status=%x)\n", data, status);
146 return;
147 }
148 if (status & I8042_STR_AUXDATA)
149 process_mouse(data);
150 else
151 process_key(data);
152}
153
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500154static void
155process_ps2bytes(void)
156{
157 for (;;) {
158 u8 status = inb(PORT_PS2_STATUS);
159 if (!(status & I8042_STR_OBF))
160 return;
161 u8 data = inb(PORT_PS2_DATA);
162 process_ps2byte(status, data);
163 }
164}
165
Kevin O'Connor3b897192008-07-20 10:08:59 -0400166static int
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500167ps2_recvbyte(int aux, int needack, int timeout)
168{
169 u64 end = calc_future_tsc(timeout);
170 for (;;) {
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400171 u8 status = inb(PORT_PS2_STATUS);
172 if (status & I8042_STR_OBF) {
173 u8 data = inb(PORT_PS2_DATA);
174 dprintf(7, "ps2 read %x\n", data);
175
176 if (!!(status & I8042_STR_AUXDATA) == aux) {
177 if (!needack)
178 return data;
179 if (data == PS2_RET_ACK)
180 return data;
181 if (data == PS2_RET_NAK) {
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500182 dprintf(1, "Got ps2 nak (status=%x)\n", status);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400183 return data;
184 }
185 }
186
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500187 // Data not part of this command.
188 process_ps2byte(status, data);
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400189 }
190
Kevin O'Connor89eb6242009-10-22 22:30:37 -0400191 if (check_time(end)) {
Kevin O'Connorcfdc13f2010-02-14 13:07:54 -0500192 // Don't warn on second byte of a reset
193 if (timeout > 100)
194 warn_timeout();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500195 return -1;
196 }
Kevin O'Connor10ad7992009-10-24 11:06:08 -0400197 yield();
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500198 }
199}
200
201static int
Kevin O'Connor235df202009-01-18 12:54:51 -0500202ps2_sendbyte(int aux, u8 command, int timeout)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400203{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500204 dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400205 int ret;
206 if (aux)
207 ret = i8042_aux_write(command);
208 else
209 ret = i8042_kbd_write(command);
210 if (ret)
211 return ret;
212
213 // Read ack.
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500214 ret = ps2_recvbyte(aux, 1, timeout);
Kevin O'Connor0234cd92009-01-04 12:20:02 -0500215 if (ret < 0)
Kevin O'Connor3b897192008-07-20 10:08:59 -0400216 return ret;
Kevin O'Connor6080fe02009-12-02 21:19:30 -0500217 if (ret != PS2_RET_ACK)
218 return -1;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400219
220 return 0;
221}
222
223static int
224ps2_command(int aux, int command, u8 *param)
225{
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500226 int ret;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400227 int receive = (command >> 8) & 0xf;
228 int send = (command >> 12) & 0xf;
229
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500230 // Disable processing of interrupts.
231 u8 kbdflag = GET_BDA(kbd_flag3);
232 SET_BDA(kbd_flag3, kbdflag | KF3_CMD_PENDING);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400233
Kevin O'Connor235df202009-01-18 12:54:51 -0500234 if (command == ATKBD_CMD_RESET_BAT) {
235 // Reset is special wrt timeouts.
Kevin O'Connor3b897192008-07-20 10:08:59 -0400236
Kevin O'Connor235df202009-01-18 12:54:51 -0500237 // Send command.
238 ret = ps2_sendbyte(aux, command, 1000);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400239 if (ret)
240 goto fail;
Kevin O'Connor235df202009-01-18 12:54:51 -0500241
242 // Receive parameters.
243 ret = ps2_recvbyte(aux, 0, 4000);
244 if (ret < 0)
245 goto fail;
246 param[0] = ret;
247 ret = ps2_recvbyte(aux, 0, 100);
248 if (ret < 0)
249 // Some devices only respond with one byte on reset.
250 ret = 0;
251 param[1] = ret;
252 } else {
253 // Send command.
254 ret = ps2_sendbyte(aux, command, 200);
255 if (ret)
256 goto fail;
257
258 // Send parameters (if any).
259 int i;
260 for (i = 0; i < send; i++) {
261 ret = ps2_sendbyte(aux, param[i], 200);
262 if (ret)
263 goto fail;
264 }
265
266 // Receive parameters (if any).
267 for (i = 0; i < receive; i++) {
268 ret = ps2_recvbyte(aux, 0, 500);
269 if (ret < 0)
270 goto fail;
271 param[i] = ret;
272 }
Kevin O'Connor3b897192008-07-20 10:08:59 -0400273 }
274
Kevin O'Connor235df202009-01-18 12:54:51 -0500275 ret = 0;
Kevin O'Connor3b897192008-07-20 10:08:59 -0400276
277fail:
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500278 // Restore processing of interrupts.
279 if (!(kbdflag & KF3_CMD_PENDING))
280 process_ps2bytes();
281 SET_BDA(kbd_flag3, kbdflag);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400282
283 return ret;
284}
285
286int
287kbd_command(int command, u8 *param)
288{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500289 dprintf(7, "kbd_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400290 int ret = ps2_command(0, command, param);
291 if (ret)
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500292 dprintf(2, "keyboard command %x failed\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400293 return ret;
294}
295
296int
297aux_command(int command, u8 *param)
298{
Kevin O'Connordd3588f2008-11-29 12:41:48 -0500299 dprintf(7, "aux_command cmd=%x\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400300 int ret = ps2_command(1, command, param);
301 if (ret)
Kevin O'Connorb44a8522009-01-17 23:30:01 -0500302 dprintf(2, "mouse command %x failed\n", command);
Kevin O'Connor3b897192008-07-20 10:08:59 -0400303 return ret;
304}
Kevin O'Connor57877482009-12-09 21:00:41 -0500305
306
307/****************************************************************
308 * IRQ handlers
309 ****************************************************************/
310
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500311static void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500312process_ps2irq(void)
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500313{
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500314 if (GET_BDA(kbd_flag3) & KF3_CMD_PENDING)
315 // PS/2 command in progress - it will handle this event.
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500316 return;
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500317 process_ps2bytes();
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500318}
319
Kevin O'Connor57877482009-12-09 21:00:41 -0500320// INT74h : PS/2 mouse hardware interrupt
321void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500322handle_74(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500323{
324 if (! CONFIG_PS2PORT)
325 return;
326
327 debug_isr(DEBUG_ISR_74);
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500328 process_ps2irq();
Kevin O'Connor57877482009-12-09 21:00:41 -0500329 eoi_pic2();
330}
331
332// INT09h : Keyboard Hardware Service Entry Point
333void VISIBLE16
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500334handle_09(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500335{
336 if (! CONFIG_PS2PORT)
337 return;
338
339 debug_isr(DEBUG_ISR_09);
Kevin O'Connor84f7b802009-12-09 21:15:59 -0500340 process_ps2irq();
Kevin O'Connor57877482009-12-09 21:00:41 -0500341 eoi_pic1();
342}
343
344
345/****************************************************************
346 * Setup
347 ****************************************************************/
348
349static void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500350keyboard_init(void *data)
Kevin O'Connor57877482009-12-09 21:00:41 -0500351{
352 /* flush incoming keys */
353 int ret = i8042_flush();
354 if (ret)
355 return;
356
357 // Controller self-test.
358 u8 param[2];
359 ret = i8042_command(I8042_CMD_CTL_TEST, param);
360 if (ret)
361 return;
362 if (param[0] != 0x55) {
363 dprintf(1, "i8042 self test failed (got %x not 0x55)\n", param[0]);
364 return;
365 }
366
367 // Controller keyboard test.
368 ret = i8042_command(I8042_CMD_KBD_TEST, param);
369 if (ret)
370 return;
371 if (param[0] != 0x00) {
372 dprintf(1, "i8042 keyboard test failed (got %x not 0x00)\n", param[0]);
373 return;
374 }
375
376 // Enable keyboard and mouse ports.
377 ret = i8042_command(I8042_CMD_KBD_ENABLE, NULL);
378 if (ret)
379 return;
380 ret = i8042_command(I8042_CMD_AUX_ENABLE, NULL);
381 if (ret)
382 return;
383
384
385 /* ------------------- keyboard side ------------------------*/
386 /* reset keyboard and self test (keyboard side) */
387 ret = kbd_command(ATKBD_CMD_RESET_BAT, param);
388 if (ret)
389 return;
390 if (param[0] != 0xaa) {
391 dprintf(1, "keyboard self test failed (got %x not 0xaa)\n", param[0]);
392 return;
393 }
394
395 /* Disable keyboard */
396 ret = kbd_command(ATKBD_CMD_RESET_DIS, NULL);
397 if (ret)
398 return;
399
400 // Set scancode command (mode 2)
401 param[0] = 0x02;
402 ret = kbd_command(ATKBD_CMD_SSCANSET, param);
403 if (ret)
404 return;
405
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500406 // Mode: scan code convert, enable IRQ 1, enable IRQ 12
407 param[0] = I8042_CTR_XLATE | I8042_CTR_KBDINT | I8042_CTR_AUXINT;
408 ret = i8042_command(I8042_CMD_CTL_WCTR, param);
409 if (ret)
410 return;
411 CLEARBITS_BDA(kbd_flag3, KF3_CMD_PENDING);
Kevin O'Connor57877482009-12-09 21:00:41 -0500412
413 /* Enable keyboard */
414 ret = kbd_command(ATKBD_CMD_ENABLE, NULL);
415 if (ret)
416 return;
417
418 dprintf(1, "keyboard initialized\n");
419}
420
421void
Kevin O'Connor1ca05b02010-01-03 17:43:37 -0500422ps2port_setup(void)
Kevin O'Connor57877482009-12-09 21:00:41 -0500423{
Kevin O'Connor59c75742010-02-13 18:49:24 -0500424 ASSERT32FLAT();
Kevin O'Connor57877482009-12-09 21:00:41 -0500425 if (! CONFIG_PS2PORT)
426 return;
427 dprintf(3, "init ps2port\n");
428
Kevin O'Connor6f702dd2010-01-28 20:35:21 -0500429 // Setup irqs, but disable them until init complete.
430 SETBITS_BDA(kbd_flag3, KF3_CMD_PENDING);
Kevin O'Connor57877482009-12-09 21:00:41 -0500431 enable_hwirq(1, entry_09);
432 enable_hwirq(12, entry_74);
433
434 run_thread(keyboard_init, NULL);
435}