Replace irq_enable() regions with explicit calls to check for irqs.
[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 "biosvar.h" // GET_EBDA
11 #include "ps2port.h" // kbd_command
12
13
14 /****************************************************************
15  * Low level i8042 commands.
16  ****************************************************************/
17
18 // Timeout value.
19 #define I8042_CTL_TIMEOUT       10000
20
21 #define I8042_BUFFER_SIZE       16
22
23 static int
24 i8042_wait_read(void)
25 {
26     dprintf(7, "i8042_wait_read\n");
27     int i;
28     for (i=0; i<I8042_CTL_TIMEOUT; i++) {
29         u8 status = inb(PORT_PS2_STATUS);
30         if (status & I8042_STR_OBF)
31             return 0;
32         udelay(50);
33     }
34     dprintf(1, "i8042 timeout on wait read\n");
35     return -1;
36 }
37
38 static int
39 i8042_wait_write(void)
40 {
41     dprintf(7, "i8042_wait_write\n");
42     int i;
43     for (i=0; i<I8042_CTL_TIMEOUT; i++) {
44         u8 status = inb(PORT_PS2_STATUS);
45         if (! (status & I8042_STR_IBF))
46             return 0;
47         udelay(50);
48     }
49     dprintf(1, "i8042 timeout on wait write\n");
50     return -1;
51 }
52
53 int
54 i8042_flush(void)
55 {
56     dprintf(7, "i8042_flush\n");
57     int i;
58     for (i=0; i<I8042_BUFFER_SIZE; i++) {
59         u8 status = inb(PORT_PS2_STATUS);
60         if (! (status & I8042_STR_OBF))
61             return 0;
62         udelay(50);
63         u8 data = inb(PORT_PS2_DATA);
64         dprintf(7, "i8042 flushed %x (status=%x)\n", data, status);
65     }
66
67     dprintf(1, "i8042 timeout on flush\n");
68     return -1;
69 }
70
71 static int
72 __i8042_command(int command, u8 *param)
73 {
74     int receive = (command >> 8) & 0xf;
75     int send = (command >> 12) & 0xf;
76
77     // Send the command.
78     int ret = i8042_wait_write();
79     if (ret)
80         return ret;
81     outb(command, PORT_PS2_STATUS);
82
83     // Send parameters (if any).
84     int i;
85     for (i = 0; i < send; i++) {
86         ret = i8042_wait_write();
87         if (ret)
88             return ret;
89         outb(param[i], PORT_PS2_DATA);
90     }
91
92     // Receive parameters (if any).
93     for (i = 0; i < receive; i++) {
94         ret = i8042_wait_read();
95         if (ret)
96             return ret;
97         param[i] = inb(PORT_PS2_DATA);
98         dprintf(7, "i8042 param=%x\n", param[i]);
99     }
100
101     return 0;
102 }
103
104 int
105 i8042_command(int command, u8 *param)
106 {
107     dprintf(7, "i8042_command cmd=%x\n", command);
108     int ret = __i8042_command(command, param);
109     if (ret)
110         dprintf(2, "i8042 command %x failed\n", command);
111     return ret;
112 }
113
114 static int
115 i8042_kbd_write(u8 c)
116 {
117     dprintf(7, "i8042_kbd_write c=%d\n", c);
118     int ret = i8042_wait_write();
119     if (! ret)
120         outb(c, PORT_PS2_DATA);
121     return ret;
122 }
123
124 static int
125 i8042_aux_write(u8 c)
126 {
127     return i8042_command(I8042_CMD_AUX_SEND, &c);
128 }
129
130
131 /****************************************************************
132  * Device commands.
133  ****************************************************************/
134
135 #define PS2_RET_ACK             0xfa
136 #define PS2_RET_NAK             0xfe
137
138 static int
139 ps2_recvbyte(int aux, int needack, int timeout)
140 {
141     u64 end = calc_future_tsc(timeout);
142     for (;;) {
143         u8 status = inb(PORT_PS2_STATUS);
144         if (status & I8042_STR_OBF) {
145             u8 data = inb(PORT_PS2_DATA);
146             dprintf(7, "ps2 read %x\n", data);
147
148             if (!!(status & I8042_STR_AUXDATA) == aux) {
149                 if (!needack)
150                     return data;
151                 if (data == PS2_RET_ACK)
152                     return data;
153                 if (data == PS2_RET_NAK) {
154                     dprintf(1, "Got ps2 nak (status=%x); continuing\n", status);
155                     return data;
156                 }
157             }
158
159             // This data not for us - XXX - just discard it for now.
160             dprintf(1, "Discarding ps2 data %x (status=%x)\n", data, status);
161         }
162
163         if (check_time(end)) {
164             dprintf(1, "ps2_recvbyte timeout\n");
165             return -1;
166         }
167         yield();
168     }
169 }
170
171 static int
172 ps2_sendbyte(int aux, u8 command, int timeout)
173 {
174     dprintf(7, "ps2_sendbyte aux=%d cmd=%x\n", aux, command);
175     int ret;
176     if (aux)
177         ret = i8042_aux_write(command);
178     else
179         ret = i8042_kbd_write(command);
180     if (ret)
181         return ret;
182
183     // Read ack.
184     ret = ps2_recvbyte(aux, 1, timeout);
185     if (ret < 0)
186         return ret;
187
188     return 0;
189 }
190
191 static int
192 ps2_command(int aux, int command, u8 *param)
193 {
194     int ret2;
195     int receive = (command >> 8) & 0xf;
196     int send = (command >> 12) & 0xf;
197
198     // Disable interrupts and keyboard/mouse.
199     u8 ps2ctr = GET_EBDA(ps2ctr);
200     u8 newctr = ps2ctr;
201     if (aux)
202         newctr |= I8042_CTR_KBDDIS;
203     else
204         newctr |= I8042_CTR_AUXDIS;
205     newctr &= ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT);
206     dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
207     int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
208     if (ret)
209         return ret;
210
211     if (command == ATKBD_CMD_RESET_BAT) {
212         // Reset is special wrt timeouts.
213
214         // Send command.
215         ret = ps2_sendbyte(aux, command, 1000);
216         if (ret)
217             goto fail;
218
219         // Receive parameters.
220         ret = ps2_recvbyte(aux, 0, 4000);
221         if (ret < 0)
222             goto fail;
223         param[0] = ret;
224         ret = ps2_recvbyte(aux, 0, 100);
225         if (ret < 0)
226             // Some devices only respond with one byte on reset.
227             ret = 0;
228         param[1] = ret;
229     } else {
230         // Send command.
231         ret = ps2_sendbyte(aux, command, 200);
232         if (ret)
233             goto fail;
234
235         // Send parameters (if any).
236         int i;
237         for (i = 0; i < send; i++) {
238             ret = ps2_sendbyte(aux, param[i], 200);
239             if (ret)
240                 goto fail;
241         }
242
243         // Receive parameters (if any).
244         for (i = 0; i < receive; i++) {
245             ret = ps2_recvbyte(aux, 0, 500);
246             if (ret < 0)
247                 goto fail;
248             param[i] = ret;
249         }
250     }
251
252     ret = 0;
253
254 fail:
255     // Restore interrupts and keyboard/mouse.
256     ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
257     if (ret2)
258         return ret2;
259
260     return ret;
261 }
262
263 int
264 kbd_command(int command, u8 *param)
265 {
266     dprintf(7, "kbd_command cmd=%x\n", command);
267     int ret = ps2_command(0, command, param);
268     if (ret)
269         dprintf(2, "keyboard command %x failed\n", command);
270     return ret;
271 }
272
273 int
274 aux_command(int command, u8 *param)
275 {
276     dprintf(7, "aux_command cmd=%x\n", command);
277     int ret = ps2_command(1, command, param);
278     if (ret)
279         dprintf(2, "mouse command %x failed\n", command);
280     return ret;
281 }