source: mainline/uspace/drv/char/i8042/i8042.c@ eadaeae8

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since eadaeae8 was eadaeae8, checked in by Jakub Jermar <jakub@…>, 7 years ago

Make capability handles type-safe

Define distinct pointer types for the handles of the supported
capability types and use them instead of integer handles. This makes it
virtually impossible to pass a non-handle or a handle of different type
instead of the proper handle. Also turn cap_handle_t into an "untyped"
capability handle that can be assigned to and from the "typed" handles.

This commit also fixes a bug in msim-con driver, which wrongly used the
IRQ number instead of the IRQ capability handle to unregister the IRQ.

This commit also fixes the wrong use of the capability handle instead
of error code in libusbhost.

  • Property mode set to 100644
File size: 10.8 KB
Line 
1/*
2 * Copyright (c) 2001-2004 Jakub Jermar
3 * Copyright (c) 2006 Josef Cejka
4 * Copyright (c) 2017 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 <adt/circ_buf.h>
42#include <ddf/log.h>
43#include <ddf/interrupt.h>
44#include <ddi.h>
45#include <device/hw_res.h>
46#include <errno.h>
47#include <str_error.h>
48#include <inttypes.h>
49#include <io/chardev_srv.h>
50
51#include "i8042.h"
52
53/* Interesting bits for status register */
54#define i8042_OUTPUT_FULL 0x01
55#define i8042_INPUT_FULL 0x02
56#define i8042_AUX_DATA 0x20
57
58/* Command constants */
59#define i8042_CMD_WRITE_CMDB 0x60 /**< Write command byte */
60#define i8042_CMD_WRITE_AUX 0xd4 /**< Write aux device */
61
62/* Command byte fields */
63#define i8042_KBD_IE 0x01
64#define i8042_AUX_IE 0x02
65#define i8042_KBD_DISABLE 0x10
66#define i8042_AUX_DISABLE 0x20
67#define i8042_KBD_TRANSLATE 0x40 /* Use this to switch to XT scancodes */
68
69static void i8042_char_conn(ipc_callid_t, ipc_call_t *, void *);
70static errno_t i8042_read(chardev_srv_t *, void *, size_t, size_t *);
71static errno_t i8042_write(chardev_srv_t *, const void *, size_t, size_t *);
72
73static chardev_ops_t i8042_chardev_ops = {
74 .read = i8042_read,
75 .write = i8042_write
76};
77
78static const irq_pio_range_t i8042_ranges[] = {
79 {
80 .base = 0,
81 .size = sizeof(i8042_regs_t)
82 }
83};
84
85/** i8042 Interrupt pseudo-code. */
86static const irq_cmd_t i8042_cmds[] = {
87 {
88 .cmd = CMD_PIO_READ_8,
89 .addr = NULL, /* will be patched in run-time */
90 .dstarg = 1
91 },
92 {
93 .cmd = CMD_AND,
94 .value = i8042_OUTPUT_FULL,
95 .srcarg = 1,
96 .dstarg = 3
97 },
98 {
99 .cmd = CMD_PREDICATE,
100 .value = 2,
101 .srcarg = 3
102 },
103 {
104 .cmd = CMD_PIO_READ_8,
105 .addr = NULL, /* will be patched in run-time */
106 .dstarg = 2
107 },
108 {
109 .cmd = CMD_ACCEPT
110 }
111};
112
113/** Wait until it is safe to write to the device. */
114static void wait_ready(i8042_t *dev)
115{
116 assert(dev);
117 while (pio_read_8(&dev->regs->status) & i8042_INPUT_FULL);
118}
119
120/** Interrupt handler routine.
121 *
122 * Write new data to the corresponding buffer.
123 *
124 * @param call pointerr to call data.
125 * @param dev Device that caued the interrupt.
126 *
127 */
128static void i8042_irq_handler(ipc_call_t *call, ddf_dev_t *dev)
129{
130 i8042_t *controller = ddf_dev_data_get(dev);
131 errno_t rc;
132
133 const uint8_t status = IPC_GET_ARG1(*call);
134 const uint8_t data = IPC_GET_ARG2(*call);
135
136 i8042_port_t *port = (status & i8042_AUX_DATA) ?
137 controller->aux : controller->kbd;
138
139 fibril_mutex_lock(&port->buf_lock);
140
141 rc = circ_buf_push(&port->cbuf, &data);
142 if (rc != EOK)
143 ddf_msg(LVL_ERROR, "Buffer overrun");
144
145 fibril_mutex_unlock(&port->buf_lock);
146 fibril_condvar_broadcast(&port->buf_cv);
147}
148
149/** Initialize i8042 driver structure.
150 *
151 * @param dev Driver structure to initialize.
152 * @param regs I/O range of registers.
153 * @param irq_kbd IRQ for primary port.
154 * @param irq_mouse IRQ for aux port.
155 * @param ddf_dev DDF device structure of the device.
156 *
157 * @return Error code.
158 *
159 */
160errno_t i8042_init(i8042_t *dev, addr_range_t *regs, int irq_kbd,
161 int irq_mouse, ddf_dev_t *ddf_dev)
162{
163 const size_t range_count = sizeof(i8042_ranges) /
164 sizeof(irq_pio_range_t);
165 irq_pio_range_t ranges[range_count];
166 const size_t cmd_count = sizeof(i8042_cmds) / sizeof(irq_cmd_t);
167 irq_cmd_t cmds[cmd_count];
168 ddf_fun_t *kbd_fun;
169 ddf_fun_t *aux_fun;
170 i8042_regs_t *ar;
171
172 errno_t rc;
173 bool kbd_bound = false;
174 bool aux_bound = false;
175
176 if (regs->size < sizeof(i8042_regs_t)) {
177 rc = EINVAL;
178 goto error;
179 }
180
181 if (pio_enable_range(regs, (void **) &dev->regs) != 0) {
182 rc = EIO;
183 goto error;
184 }
185
186 kbd_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2a");
187 if (kbd_fun == NULL) {
188 rc = ENOMEM;
189 goto error;
190 }
191
192 dev->kbd = ddf_fun_data_alloc(kbd_fun, sizeof(i8042_port_t));
193 if (dev->kbd == NULL) {
194 rc = ENOMEM;
195 goto error;
196 }
197
198 dev->kbd->fun = kbd_fun;
199 dev->kbd->ctl = dev;
200 chardev_srvs_init(&dev->kbd->cds);
201 dev->kbd->cds.ops = &i8042_chardev_ops;
202 dev->kbd->cds.sarg = dev->kbd;
203 fibril_mutex_initialize(&dev->kbd->buf_lock);
204 fibril_condvar_initialize(&dev->kbd->buf_cv);
205
206 rc = ddf_fun_add_match_id(dev->kbd->fun, "char/xtkbd", 90);
207 if (rc != EOK)
208 goto error;
209
210 aux_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2b");
211 if (aux_fun == NULL) {
212 rc = ENOMEM;
213 goto error;
214 }
215
216 dev->aux = ddf_fun_data_alloc(aux_fun, sizeof(i8042_port_t));
217 if (dev->aux == NULL) {
218 rc = ENOMEM;
219 goto error;
220 }
221
222 dev->aux->fun = aux_fun;
223 dev->aux->ctl = dev;
224 chardev_srvs_init(&dev->aux->cds);
225 dev->aux->cds.ops = &i8042_chardev_ops;
226 dev->aux->cds.sarg = dev->aux;
227 fibril_mutex_initialize(&dev->aux->buf_lock);
228 fibril_condvar_initialize(&dev->aux->buf_cv);
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_conn_handler(dev->kbd->fun, i8042_char_conn);
235 ddf_fun_set_conn_handler(dev->aux->fun, i8042_char_conn);
236
237 circ_buf_init(&dev->kbd->cbuf, dev->kbd->buf_data, BUFFER_SIZE, 1);
238 circ_buf_init(&dev->aux->cbuf, dev->aux->buf_data, BUFFER_SIZE, 1);
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 = RNGABS(*regs);
269
270
271 ar = RNGABSPTR(*regs);
272 memcpy(cmds, i8042_cmds, sizeof(i8042_cmds));
273 cmds[0].addr = (void *) &ar->status;
274 cmds[3].addr = (void *) &ar->data;
275
276 irq_code_t irq_code = {
277 .rangecount = range_count,
278 .ranges = ranges,
279 .cmdcount = cmd_count,
280 .cmds = cmds
281 };
282
283 cap_irq_handle_t kbd_ihandle;
284 rc = register_interrupt_handler(ddf_dev, irq_kbd,
285 i8042_irq_handler, &irq_code, &kbd_ihandle);
286 if (rc != EOK) {
287 ddf_msg(LVL_ERROR, "Failed set handler for kbd: %s.",
288 ddf_dev_get_name(ddf_dev));
289 goto error;
290 }
291
292 cap_irq_handle_t mouse_ihandle;
293 rc = register_interrupt_handler(ddf_dev, irq_mouse,
294 i8042_irq_handler, &irq_code, &mouse_ihandle);
295 if (rc != EOK) {
296 ddf_msg(LVL_ERROR, "Failed set handler for mouse: %s.",
297 ddf_dev_get_name(ddf_dev));
298 goto error;
299 }
300
301 /* Enable interrupts */
302 async_sess_t *parent_sess = ddf_dev_parent_sess_get(ddf_dev);
303 assert(parent_sess != NULL);
304
305 rc = hw_res_enable_interrupt(parent_sess, irq_kbd);
306 if (rc != EOK) {
307 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable keyboard interrupt: %s.",
308 ddf_dev_get_name(ddf_dev));
309 rc = EIO;
310 goto error;
311 }
312
313 rc = hw_res_enable_interrupt(parent_sess, irq_mouse);
314 if (rc != EOK) {
315 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable mouse interrupt: %s.",
316 ddf_dev_get_name(ddf_dev));
317 rc = EIO;
318 goto error;
319 }
320
321 /* Enable port interrupts. */
322 wait_ready(dev);
323 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
324 wait_ready(dev);
325 pio_write_8(&dev->regs->data, i8042_KBD_IE | i8042_KBD_TRANSLATE |
326 i8042_AUX_IE);
327
328 return EOK;
329error:
330 if (kbd_bound)
331 ddf_fun_unbind(dev->kbd->fun);
332 if (aux_bound)
333 ddf_fun_unbind(dev->aux->fun);
334 if (dev->kbd->fun != NULL)
335 ddf_fun_destroy(dev->kbd->fun);
336 if (dev->aux->fun != NULL)
337 ddf_fun_destroy(dev->aux->fun);
338
339 return rc;
340}
341
342/** Write data to i8042 port.
343 *
344 * @param srv Connection-specific data
345 * @param buffer Data source
346 * @param size Data size
347 * @param nwr Place to store number of bytes successfully written
348 *
349 * @return EOK on success or non-zero error code
350 *
351 */
352static errno_t i8042_write(chardev_srv_t *srv, const void *data, size_t size,
353 size_t *nwr)
354{
355 i8042_port_t *port = (i8042_port_t *)srv->srvs->sarg;
356 i8042_t *i8042 = port->ctl;
357 const char *dp = (const char *)data;
358
359 fibril_mutex_lock(&i8042->write_guard);
360
361 for (size_t i = 0; i < size; ++i) {
362 if (port == i8042->aux) {
363 wait_ready(i8042);
364 pio_write_8(&i8042->regs->status,
365 i8042_CMD_WRITE_AUX);
366 }
367
368 wait_ready(i8042);
369 pio_write_8(&i8042->regs->data, dp[i]);
370 }
371
372 fibril_mutex_unlock(&i8042->write_guard);
373 *nwr = size;
374 return EOK;
375}
376
377/** Read data from i8042 port.
378 *
379 * @param srv Connection-specific data
380 * @param buffer Data place
381 * @param size Data place size
382 * @param nread Place to store number of bytes successfully read
383 *
384 * @return EOK on success or non-zero error code
385 *
386 */
387static errno_t i8042_read(chardev_srv_t *srv, void *dest, size_t size,
388 size_t *nread)
389{
390 i8042_port_t *port = (i8042_port_t *)srv->srvs->sarg;
391 size_t p;
392 uint8_t *destp = (uint8_t *)dest;
393 errno_t rc;
394
395 fibril_mutex_lock(&port->buf_lock);
396
397 while (circ_buf_nused(&port->cbuf) == 0)
398 fibril_condvar_wait(&port->buf_cv, &port->buf_lock);
399
400 p = 0;
401 while (p < size) {
402 rc = circ_buf_pop(&port->cbuf, &destp[p]);
403 if (rc != EOK)
404 break;
405 ++p;
406 }
407
408 fibril_mutex_unlock(&port->buf_lock);
409
410 *nread = p;
411 return EOK;
412}
413
414/** Handle data requests.
415 *
416 * @param id callid
417 * @param call IPC request.
418 * @param arg ddf_fun_t function.
419 */
420void i8042_char_conn(ipc_callid_t iid, ipc_call_t *icall, void *arg)
421{
422 i8042_port_t *port = ddf_fun_data_get((ddf_fun_t *)arg);
423
424 chardev_conn(iid, icall, &port->cds);
425}
426
427/**
428 * @}
429 */
Note: See TracBrowser for help on using the repository browser.