source: mainline/uspace/drv/char/i8042/i8042.c@ 4e30369

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 4e30369 was 876f6463, checked in by Martin Decky <martin@…>, 14 years ago

always (always, always!) wait for the controller to be ready before sending any data to it
otherwise the i8042 breaks on real hardware, rendering the keyboard and mouse useless
this fixes #409

  • Property mode set to 100644
File size: 9.9 KB
Line 
1/*
2 * Copyright (c) 2001-2004 Jakub Jermar
3 * Copyright (c) 2006 Josef Cejka
4 * Copyright (c) 2009 Jiri Svoboda
5 * Copyright (c) 2011 Jan Vesely
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 *
12 * - Redistributions of source code must retain the above copyright
13 * notice, this list of conditions and the following disclaimer.
14 * - Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 * - The name of the author may not be used to endorse or promote products
18 * derived from this software without specific prior written permission.
19 *
20 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 */
31
32/** @addtogroup kbd_port
33 * @ingroup kbd
34 * @{
35 */
36
37/** @file
38 * @brief i8042 PS/2 port driver.
39 */
40
41#include <devman.h>
42#include <device/hw_res.h>
43#include <ddi.h>
44#include <libarch/ddi.h>
45#include <errno.h>
46#include <str_error.h>
47#include <inttypes.h>
48#include <ddf/log.h>
49#include <ddf/interrupt.h>
50#include "i8042.h"
51
52/* Interesting bits for status register */
53#define i8042_OUTPUT_FULL 0x01
54#define i8042_INPUT_FULL 0x02
55#define i8042_AUX_DATA 0x20
56
57/* Command constants */
58#define i8042_CMD_WRITE_CMDB 0x60 /**< Write command byte */
59#define i8042_CMD_WRITE_AUX 0xd4 /**< Write aux device */
60
61/* Command byte fields */
62#define i8042_KBD_IE 0x01
63#define i8042_AUX_IE 0x02
64#define i8042_KBD_DISABLE 0x10
65#define i8042_AUX_DISABLE 0x20
66#define i8042_KBD_TRANSLATE 0x40 /* Use this to switch to XT scancodes */
67
68#define CHECK_RET_DESTROY(ret, msg...) \
69 do { \
70 if (ret != EOK) { \
71 ddf_msg(LVL_ERROR, msg); \
72 if (dev->kbd_fun) { \
73 dev->kbd_fun->driver_data = NULL; \
74 ddf_fun_destroy(dev->kbd_fun); \
75 } \
76 if (dev->aux_fun) { \
77 dev->aux_fun->driver_data = NULL; \
78 ddf_fun_destroy(dev->aux_fun); \
79 } \
80 } \
81 } while (0)
82
83#define CHECK_RET_UNBIND_DESTROY(ret, msg...) \
84 do { \
85 if (ret != EOK) { \
86 ddf_msg(LVL_ERROR, msg); \
87 if (dev->kbd_fun) { \
88 ddf_fun_unbind(dev->kbd_fun); \
89 dev->kbd_fun->driver_data = NULL; \
90 ddf_fun_destroy(dev->kbd_fun); \
91 } \
92 if (dev->aux_fun) { \
93 ddf_fun_unbind(dev->aux_fun); \
94 dev->aux_fun->driver_data = NULL; \
95 ddf_fun_destroy(dev->aux_fun); \
96 } \
97 } \
98 } while (0)
99
100void default_handler(ddf_fun_t *, ipc_callid_t, ipc_call_t *);
101
102/** Port function operations. */
103static ddf_dev_ops_t ops = {
104 .default_handler = default_handler,
105};
106
107/** i8042 Interrupt pseudo-code. */
108static const irq_cmd_t i8042_cmds[] = {
109 {
110 .cmd = CMD_PIO_READ_8,
111 .addr = NULL, /* will be patched in run-time */
112 .dstarg = 1
113 },
114 {
115 .cmd = CMD_BTEST,
116 .value = i8042_OUTPUT_FULL,
117 .srcarg = 1,
118 .dstarg = 3
119 },
120 {
121 .cmd = CMD_PREDICATE,
122 .value = 2,
123 .srcarg = 3
124 },
125 {
126 .cmd = CMD_PIO_READ_8,
127 .addr = NULL, /* will be patched in run-time */
128 .dstarg = 2
129 },
130 {
131 .cmd = CMD_ACCEPT
132 }
133};
134
135/** Wait until it is safe to write to the device. */
136static void wait_ready(i8042_t *dev)
137{
138 assert(dev);
139 while (pio_read_8(&dev->regs->status) & i8042_INPUT_FULL);
140}
141
142/** Interrupt handler routine.
143 *
144 * Write new data to the corresponding buffer.
145 *
146 * @param dev Device that caued the interrupt.
147 * @param iid Call id.
148 * @param call pointerr to call data.
149 *
150 */
151static void i8042_irq_handler(ddf_dev_t *dev, ipc_callid_t iid,
152 ipc_call_t *call)
153{
154 if ((!dev) || (!dev->driver_data))
155 return;
156
157 i8042_t *controller = dev->driver_data;
158
159 const uint8_t status = IPC_GET_ARG1(*call);
160 const uint8_t data = IPC_GET_ARG2(*call);
161
162 buffer_t *buffer = (status & i8042_AUX_DATA) ?
163 &controller->aux_buffer : &controller->kbd_buffer;
164
165 buffer_write(buffer, data);
166}
167
168/** Initialize i8042 driver structure.
169 *
170 * @param dev Driver structure to initialize.
171 * @param regs I/O address of registers.
172 * @param reg_size size of the reserved I/O address space.
173 * @param irq_kbd IRQ for primary port.
174 * @param irq_mouse IRQ for aux port.
175 * @param ddf_dev DDF device structure of the device.
176 *
177 * @return Error code.
178 *
179 */
180int i8042_init(i8042_t *dev, void *regs, size_t reg_size, int irq_kbd,
181 int irq_mouse, ddf_dev_t *ddf_dev)
182{
183 assert(ddf_dev);
184 assert(dev);
185
186 if (reg_size < sizeof(i8042_regs_t))
187 return EINVAL;
188
189 if (pio_enable(regs, sizeof(i8042_regs_t), (void **) &dev->regs) != 0)
190 return -1;
191
192 dev->kbd_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2a");
193 if (!dev->kbd_fun)
194 return ENOMEM;
195
196 int ret = ddf_fun_add_match_id(dev->kbd_fun, "char/xtkbd", 90);
197 if (ret != EOK) {
198 ddf_fun_destroy(dev->kbd_fun);
199 return ret;
200 }
201
202 dev->aux_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2b");
203 if (!dev->aux_fun) {
204 ddf_fun_destroy(dev->kbd_fun);
205 return ENOMEM;
206 }
207
208 ret = ddf_fun_add_match_id(dev->aux_fun, "char/ps2mouse", 90);
209 if (ret != EOK) {
210 ddf_fun_destroy(dev->kbd_fun);
211 ddf_fun_destroy(dev->aux_fun);
212 return ret;
213 }
214
215 dev->kbd_fun->ops = &ops;
216 dev->aux_fun->ops = &ops;
217 dev->kbd_fun->driver_data = dev;
218 dev->aux_fun->driver_data = dev;
219
220 buffer_init(&dev->kbd_buffer, dev->kbd_data, BUFFER_SIZE);
221 buffer_init(&dev->aux_buffer, dev->aux_data, BUFFER_SIZE);
222 fibril_mutex_initialize(&dev->write_guard);
223
224 ret = ddf_fun_bind(dev->kbd_fun);
225 CHECK_RET_DESTROY(ret, "Failed to bind keyboard function: %s.",
226 str_error(ret));
227
228 ret = ddf_fun_bind(dev->aux_fun);
229 CHECK_RET_DESTROY(ret, "Failed to bind mouse function: %s.",
230 str_error(ret));
231
232 /* Disable kbd and aux */
233 wait_ready(dev);
234 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
235 wait_ready(dev);
236 pio_write_8(&dev->regs->data, i8042_KBD_DISABLE | i8042_AUX_DISABLE);
237
238 /* Flush all current IO */
239 while (pio_read_8(&dev->regs->status) & i8042_OUTPUT_FULL)
240 (void) pio_read_8(&dev->regs->data);
241
242 const size_t cmd_count = sizeof(i8042_cmds) / sizeof(irq_cmd_t);
243 irq_cmd_t cmds[cmd_count];
244 memcpy(cmds, i8042_cmds, sizeof(i8042_cmds));
245 cmds[0].addr = (void *) &dev->regs->status;
246 cmds[3].addr = (void *) &dev->regs->data;
247
248 irq_code_t irq_code = {
249 .cmdcount = cmd_count,
250 .cmds = cmds
251 };
252
253 ret = register_interrupt_handler(ddf_dev, irq_kbd, i8042_irq_handler,
254 &irq_code);
255 CHECK_RET_UNBIND_DESTROY(ret, "Failed set handler for kbd: %s.",
256 str_error(ret));
257
258 ret = register_interrupt_handler(ddf_dev, irq_mouse, i8042_irq_handler,
259 &irq_code);
260 CHECK_RET_UNBIND_DESTROY(ret, "Failed set handler for mouse: %s.",
261 str_error(ret));
262
263 /* Enable interrupts */
264 async_sess_t *parent_sess =
265 devman_parent_device_connect(EXCHANGE_SERIALIZE, ddf_dev->handle,
266 IPC_FLAG_BLOCKING);
267 ret = parent_sess ? EOK : ENOMEM;
268 CHECK_RET_UNBIND_DESTROY(ret, "Failed to create parent connection.");
269
270 const bool enabled = hw_res_enable_interrupt(parent_sess);
271 async_hangup(parent_sess);
272 ret = enabled ? EOK : EIO;
273 CHECK_RET_UNBIND_DESTROY(ret, "Failed to enable interrupts: %s.");
274
275 /* Enable port interrupts. */
276 wait_ready(dev);
277 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
278 wait_ready(dev);
279 pio_write_8(&dev->regs->data, i8042_KBD_IE | i8042_KBD_TRANSLATE |
280 i8042_AUX_IE);
281
282 return EOK;
283}
284
285// FIXME TODO use shared instead this
286enum {
287 IPC_CHAR_READ = DEV_FIRST_CUSTOM_METHOD,
288 IPC_CHAR_WRITE,
289};
290
291/** Write data to i8042 port.
292 *
293 * @param fun DDF function.
294 * @param buffer Data source.
295 * @param size Data size.
296 *
297 * @return Bytes written.
298 *
299 */
300static int i8042_write(ddf_fun_t *fun, char *buffer, size_t size)
301{
302 assert(fun);
303 assert(fun->driver_data);
304
305 i8042_t *controller = fun->driver_data;
306 fibril_mutex_lock(&controller->write_guard);
307
308 for (size_t i = 0; i < size; ++i) {
309 if (controller->aux_fun == fun) {
310 wait_ready(controller);
311 pio_write_8(&controller->regs->status,
312 i8042_CMD_WRITE_AUX);
313 }
314
315 wait_ready(controller);
316 pio_write_8(&controller->regs->data, buffer[i]);
317 }
318
319 fibril_mutex_unlock(&controller->write_guard);
320 return size;
321}
322
323/** Read data from i8042 port.
324 *
325 * @param fun DDF function.
326 * @param buffer Data place.
327 * @param size Data place size.
328 *
329 * @return Bytes read.
330 *
331 */
332static int i8042_read(ddf_fun_t *fun, char *data, size_t size)
333{
334 assert(fun);
335 assert(fun->driver_data);
336
337 i8042_t *controller = fun->driver_data;
338 buffer_t *buffer = (fun == controller->aux_fun) ?
339 &controller->aux_buffer : &controller->kbd_buffer;
340
341 for (size_t i = 0; i < size; ++i)
342 *data++ = buffer_read(buffer);
343
344 return size;
345}
346
347/** Handle data requests.
348 *
349 * @param fun ddf_fun_t function.
350 * @param id callid
351 * @param call IPC request.
352 *
353 */
354void default_handler(ddf_fun_t *fun, ipc_callid_t id, ipc_call_t *call)
355{
356 const sysarg_t method = IPC_GET_IMETHOD(*call);
357 const size_t size = IPC_GET_ARG1(*call);
358
359 switch (method) {
360 case IPC_CHAR_READ:
361 if (size <= 4 * sizeof(sysarg_t)) {
362 sysarg_t message[4] = {};
363
364 i8042_read(fun, (char *) message, size);
365 async_answer_4(id, size, message[0], message[1],
366 message[2], message[3]);
367 } else
368 async_answer_0(id, ELIMIT);
369 break;
370
371 case IPC_CHAR_WRITE:
372 if (size <= 3 * sizeof(sysarg_t)) {
373 const sysarg_t message[3] = {
374 IPC_GET_ARG2(*call),
375 IPC_GET_ARG3(*call),
376 IPC_GET_ARG4(*call)
377 };
378
379 i8042_write(fun, (char *) message, size);
380 async_answer_0(id, size);
381 } else
382 async_answer_0(id, ELIMIT);
383
384 default:
385 async_answer_0(id, EINVAL);
386 }
387}
388
389/**
390 * @}
391 */
Note: See TracBrowser for help on using the repository browser.