source: mainline/uspace/drv/char/i8042/i8042.c@ 3e6a98c5

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 3e6a98c5 was 1ae74c6, checked in by Jan Vesely <jano.vesely@…>, 13 years ago

Fix ddi.h header includes.

  • Property mode set to 100644
File size: 10.6 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 <device/hw_res.h>
42#include <ddi.h>
43#include <errno.h>
44#include <str_error.h>
45#include <inttypes.h>
46#include <ddf/log.h>
47#include <ddf/interrupt.h>
48#include "i8042.h"
49
50/* Interesting bits for status register */
51#define i8042_OUTPUT_FULL 0x01
52#define i8042_INPUT_FULL 0x02
53#define i8042_AUX_DATA 0x20
54
55/* Command constants */
56#define i8042_CMD_WRITE_CMDB 0x60 /**< Write command byte */
57#define i8042_CMD_WRITE_AUX 0xd4 /**< Write aux device */
58
59/* Command byte fields */
60#define i8042_KBD_IE 0x01
61#define i8042_AUX_IE 0x02
62#define i8042_KBD_DISABLE 0x10
63#define i8042_AUX_DISABLE 0x20
64#define i8042_KBD_TRANSLATE 0x40 /* Use this to switch to XT scancodes */
65
66#define CHECK_RET_DESTROY(ret, msg...) \
67 do { \
68 if (ret != EOK) { \
69 ddf_msg(LVL_ERROR, msg); \
70 if (dev->kbd_fun) { \
71 dev->kbd_fun->driver_data = NULL; \
72 ddf_fun_destroy(dev->kbd_fun); \
73 } \
74 if (dev->aux_fun) { \
75 dev->aux_fun->driver_data = NULL; \
76 ddf_fun_destroy(dev->aux_fun); \
77 } \
78 } \
79 } while (0)
80
81#define CHECK_RET_UNBIND_DESTROY(ret, msg...) \
82 do { \
83 if (ret != EOK) { \
84 ddf_msg(LVL_ERROR, msg); \
85 if (dev->kbd_fun) { \
86 ddf_fun_unbind(dev->kbd_fun); \
87 dev->kbd_fun->driver_data = NULL; \
88 ddf_fun_destroy(dev->kbd_fun); \
89 } \
90 if (dev->aux_fun) { \
91 ddf_fun_unbind(dev->aux_fun); \
92 dev->aux_fun->driver_data = NULL; \
93 ddf_fun_destroy(dev->aux_fun); \
94 } \
95 } \
96 } while (0)
97
98void default_handler(ddf_fun_t *, ipc_callid_t, ipc_call_t *);
99
100/** Port function operations. */
101static ddf_dev_ops_t ops = {
102 .default_handler = default_handler,
103};
104
105static const irq_pio_range_t i8042_ranges[] = {
106 {
107 .base = 0,
108 .size = sizeof(i8042_regs_t)
109 }
110};
111
112/** i8042 Interrupt pseudo-code. */
113static const irq_cmd_t i8042_cmds[] = {
114 {
115 .cmd = CMD_PIO_READ_8,
116 .addr = NULL, /* will be patched in run-time */
117 .dstarg = 1
118 },
119 {
120 .cmd = CMD_AND,
121 .value = i8042_OUTPUT_FULL,
122 .srcarg = 1,
123 .dstarg = 3
124 },
125 {
126 .cmd = CMD_PREDICATE,
127 .value = 2,
128 .srcarg = 3
129 },
130 {
131 .cmd = CMD_PIO_READ_8,
132 .addr = NULL, /* will be patched in run-time */
133 .dstarg = 2
134 },
135 {
136 .cmd = CMD_ACCEPT
137 }
138};
139
140/** Get i8042 soft state from device node. */
141static i8042_t *dev_i8042(ddf_dev_t *dev)
142{
143 return ddf_dev_data_get(dev);
144}
145
146/** Wait until it is safe to write to the device. */
147static void wait_ready(i8042_t *dev)
148{
149 assert(dev);
150 while (pio_read_8(&dev->regs->status) & i8042_INPUT_FULL);
151}
152
153/** Interrupt handler routine.
154 *
155 * Write new data to the corresponding buffer.
156 *
157 * @param dev Device that caued the interrupt.
158 * @param iid Call id.
159 * @param call pointerr to call data.
160 *
161 */
162static void i8042_irq_handler(ddf_dev_t *dev, ipc_callid_t iid,
163 ipc_call_t *call)
164{
165 i8042_t *controller = dev_i8042(dev);
166
167 const uint8_t status = IPC_GET_ARG1(*call);
168 const uint8_t data = IPC_GET_ARG2(*call);
169
170 buffer_t *buffer = (status & i8042_AUX_DATA) ?
171 &controller->aux_buffer : &controller->kbd_buffer;
172
173 buffer_write(buffer, data);
174}
175
176/** Initialize i8042 driver structure.
177 *
178 * @param dev Driver structure to initialize.
179 * @param regs I/O address of registers.
180 * @param reg_size size of the reserved I/O address space.
181 * @param irq_kbd IRQ for primary port.
182 * @param irq_mouse IRQ for aux port.
183 * @param ddf_dev DDF device structure of the device.
184 *
185 * @return Error code.
186 *
187 */
188int i8042_init(i8042_t *dev, void *regs, size_t reg_size, int irq_kbd,
189 int irq_mouse, ddf_dev_t *ddf_dev)
190{
191 const size_t range_count = sizeof(i8042_ranges) /
192 sizeof(irq_pio_range_t);
193 irq_pio_range_t ranges[range_count];
194 const size_t cmd_count = sizeof(i8042_cmds) / sizeof(irq_cmd_t);
195 irq_cmd_t cmds[cmd_count];
196
197 int rc;
198 bool kbd_bound = false;
199 bool aux_bound = false;
200
201 dev->kbd_fun = NULL;
202 dev->aux_fun = NULL;
203
204 if (reg_size < sizeof(i8042_regs_t)) {
205 rc = EINVAL;
206 goto error;
207 }
208
209 if (pio_enable(regs, sizeof(i8042_regs_t), (void **) &dev->regs) != 0) {
210 rc = EIO;
211 goto error;
212 }
213
214 dev->kbd_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2a");
215 if (dev->kbd_fun == NULL) {
216 rc = ENOMEM;
217 goto error;
218 };
219
220 rc = ddf_fun_add_match_id(dev->kbd_fun, "char/xtkbd", 90);
221 if (rc != EOK)
222 goto error;
223
224 dev->aux_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2b");
225 if (dev->aux_fun == NULL) {
226 rc = ENOMEM;
227 goto error;
228 }
229
230 rc = ddf_fun_add_match_id(dev->aux_fun, "char/ps2mouse", 90);
231 if (rc != EOK)
232 goto error;
233
234 ddf_fun_set_ops(dev->kbd_fun, &ops);
235 ddf_fun_set_ops(dev->aux_fun, &ops);
236
237 buffer_init(&dev->kbd_buffer, dev->kbd_data, BUFFER_SIZE);
238 buffer_init(&dev->aux_buffer, dev->aux_data, BUFFER_SIZE);
239 fibril_mutex_initialize(&dev->write_guard);
240
241 rc = ddf_fun_bind(dev->kbd_fun);
242 if (rc != EOK) {
243 ddf_msg(LVL_ERROR, "Failed to bind keyboard function: %s.",
244 ddf_fun_get_name(dev->kbd_fun));
245 goto error;
246 }
247 kbd_bound = true;
248
249 rc = ddf_fun_bind(dev->aux_fun);
250 if (rc != EOK) {
251 ddf_msg(LVL_ERROR, "Failed to bind aux function: %s.",
252 ddf_fun_get_name(dev->aux_fun));
253 goto error;
254 }
255 aux_bound = true;
256
257 /* Disable kbd and aux */
258 wait_ready(dev);
259 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
260 wait_ready(dev);
261 pio_write_8(&dev->regs->data, i8042_KBD_DISABLE | i8042_AUX_DISABLE);
262
263 /* Flush all current IO */
264 while (pio_read_8(&dev->regs->status) & i8042_OUTPUT_FULL)
265 (void) pio_read_8(&dev->regs->data);
266
267 memcpy(ranges, i8042_ranges, sizeof(i8042_ranges));
268 ranges[0].base = (uintptr_t) regs;
269
270 memcpy(cmds, i8042_cmds, sizeof(i8042_cmds));
271 cmds[0].addr = (void *) &(((i8042_regs_t *) regs)->status);
272 cmds[3].addr = (void *) &(((i8042_regs_t *) regs)->data);
273
274 irq_code_t irq_code = {
275 .rangecount = range_count,
276 .ranges = ranges,
277 .cmdcount = cmd_count,
278 .cmds = cmds
279 };
280
281 rc = register_interrupt_handler(ddf_dev, irq_kbd, i8042_irq_handler,
282 &irq_code);
283 if (rc != EOK) {
284 ddf_msg(LVL_ERROR, "Failed set handler for kbd: %s.",
285 ddf_dev_get_name(ddf_dev));
286 goto error;
287 }
288
289 rc = register_interrupt_handler(ddf_dev, irq_mouse, i8042_irq_handler,
290 &irq_code);
291 if (rc != EOK) {
292 ddf_msg(LVL_ERROR, "Failed set handler for mouse: %s.",
293 ddf_dev_get_name(ddf_dev));
294 goto error;
295 }
296
297 /* Enable interrupts */
298 async_sess_t *parent_sess = ddf_dev_parent_sess_get(ddf_dev);
299 assert(parent_sess != NULL);
300
301 const bool enabled = hw_res_enable_interrupt(parent_sess);
302 if (!enabled) {
303 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable interrupts: %s.",
304 ddf_dev_get_name(ddf_dev));
305 rc = EIO;
306 goto error;
307 }
308
309 /* Enable port interrupts. */
310 wait_ready(dev);
311 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
312 wait_ready(dev);
313 pio_write_8(&dev->regs->data, i8042_KBD_IE | i8042_KBD_TRANSLATE |
314 i8042_AUX_IE);
315
316 return EOK;
317error:
318 if (kbd_bound)
319 ddf_fun_unbind(dev->kbd_fun);
320 if (aux_bound)
321 ddf_fun_unbind(dev->aux_fun);
322 if (dev->kbd_fun != NULL)
323 ddf_fun_destroy(dev->kbd_fun);
324 if (dev->aux_fun != NULL)
325 ddf_fun_destroy(dev->aux_fun);
326
327 return rc;
328}
329
330// FIXME TODO use shared instead this
331enum {
332 IPC_CHAR_READ = DEV_FIRST_CUSTOM_METHOD,
333 IPC_CHAR_WRITE,
334};
335
336/** Write data to i8042 port.
337 *
338 * @param fun DDF function.
339 * @param buffer Data source.
340 * @param size Data size.
341 *
342 * @return Bytes written.
343 *
344 */
345static int i8042_write(ddf_fun_t *fun, char *buffer, size_t size)
346{
347 i8042_t *controller = dev_i8042(ddf_fun_get_dev(fun));
348 fibril_mutex_lock(&controller->write_guard);
349
350 for (size_t i = 0; i < size; ++i) {
351 if (controller->aux_fun == fun) {
352 wait_ready(controller);
353 pio_write_8(&controller->regs->status,
354 i8042_CMD_WRITE_AUX);
355 }
356
357 wait_ready(controller);
358 pio_write_8(&controller->regs->data, buffer[i]);
359 }
360
361 fibril_mutex_unlock(&controller->write_guard);
362 return size;
363}
364
365/** Read data from i8042 port.
366 *
367 * @param fun DDF function.
368 * @param buffer Data place.
369 * @param size Data place size.
370 *
371 * @return Bytes read.
372 *
373 */
374static int i8042_read(ddf_fun_t *fun, char *data, size_t size)
375{
376 i8042_t *controller = dev_i8042(ddf_fun_get_dev(fun));
377 buffer_t *buffer = (fun == controller->aux_fun) ?
378 &controller->aux_buffer : &controller->kbd_buffer;
379
380 for (size_t i = 0; i < size; ++i)
381 *data++ = buffer_read(buffer);
382
383 return size;
384}
385
386/** Handle data requests.
387 *
388 * @param fun ddf_fun_t function.
389 * @param id callid
390 * @param call IPC request.
391 *
392 */
393void default_handler(ddf_fun_t *fun, ipc_callid_t id, ipc_call_t *call)
394{
395 const sysarg_t method = IPC_GET_IMETHOD(*call);
396 const size_t size = IPC_GET_ARG1(*call);
397
398 switch (method) {
399 case IPC_CHAR_READ:
400 if (size <= 4 * sizeof(sysarg_t)) {
401 sysarg_t message[4] = {};
402
403 i8042_read(fun, (char *) message, size);
404 async_answer_4(id, size, message[0], message[1],
405 message[2], message[3]);
406 } else
407 async_answer_0(id, ELIMIT);
408 break;
409
410 case IPC_CHAR_WRITE:
411 if (size <= 3 * sizeof(sysarg_t)) {
412 const sysarg_t message[3] = {
413 IPC_GET_ARG2(*call),
414 IPC_GET_ARG3(*call),
415 IPC_GET_ARG4(*call)
416 };
417
418 i8042_write(fun, (char *) message, size);
419 async_answer_0(id, size);
420 } else
421 async_answer_0(id, ELIMIT);
422
423 default:
424 async_answer_0(id, EINVAL);
425 }
426}
427
428/**
429 * @}
430 */
Note: See TracBrowser for help on using the repository browser.