Fail PS2 port commands when receiving a NAK.
[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)\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     if (ret != PS2_RET_ACK)
188         return -1;
189
190     return 0;
191 }
192
193 static int
194 ps2_command(int aux, int command, u8 *param)
195 {
196     int ret2;
197     int receive = (command >> 8) & 0xf;
198     int send = (command >> 12) & 0xf;
199
200     // Disable interrupts and keyboard/mouse.
201     u8 ps2ctr = GET_EBDA(ps2ctr);
202     u8 newctr = ps2ctr;
203     if (aux)
204         newctr |= I8042_CTR_KBDDIS;
205     else
206         newctr |= I8042_CTR_AUXDIS;
207     newctr &= ~(I8042_CTR_KBDINT|I8042_CTR_AUXINT);
208     dprintf(6, "i8042 ctr old=%x new=%x\n", ps2ctr, newctr);
209     int ret = i8042_command(I8042_CMD_CTL_WCTR, &newctr);
210     if (ret)
211         return ret;
212
213     if (command == ATKBD_CMD_RESET_BAT) {
214         // Reset is special wrt timeouts.
215
216         // Send command.
217         ret = ps2_sendbyte(aux, command, 1000);
218         if (ret)
219             goto fail;
220
221         // Receive parameters.
222         ret = ps2_recvbyte(aux, 0, 4000);
223         if (ret < 0)
224             goto fail;
225         param[0] = ret;
226         ret = ps2_recvbyte(aux, 0, 100);
227         if (ret < 0)
228             // Some devices only respond with one byte on reset.
229             ret = 0;
230         param[1] = ret;
231     } else {
232         // Send command.
233         ret = ps2_sendbyte(aux, command, 200);
234         if (ret)
235             goto fail;
236
237         // Send parameters (if any).
238         int i;
239         for (i = 0; i < send; i++) {
240             ret = ps2_sendbyte(aux, param[i], 200);
241             if (ret)
242                 goto fail;
243         }
244
245         // Receive parameters (if any).
246         for (i = 0; i < receive; i++) {
247             ret = ps2_recvbyte(aux, 0, 500);
248             if (ret < 0)
249                 goto fail;
250             param[i] = ret;
251         }
252     }
253
254     ret = 0;
255
256 fail:
257     // Restore interrupts and keyboard/mouse.
258     ret2 = i8042_command(I8042_CMD_CTL_WCTR, &ps2ctr);
259     if (ret2)
260         return ret2;
261
262     return ret;
263 }
264
265 int
266 kbd_command(int command, u8 *param)
267 {
268     dprintf(7, "kbd_command cmd=%x\n", command);
269     int ret = ps2_command(0, command, param);
270     if (ret)
271         dprintf(2, "keyboard command %x failed\n", command);
272     return ret;
273 }
274
275 int
276 aux_command(int command, u8 *param)
277 {
278     dprintf(7, "aux_command cmd=%x\n", command);
279     int ret = ps2_command(1, command, param);
280     if (ret)
281         dprintf(2, "mouse command %x failed\n", command);
282     return ret;
283 }