source: mainline/uspace/drv/char/i8042/i8042.c@ 46c7668

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 46c7668 was 984a9ba, checked in by Martin Decky <martin@…>, 7 years ago

do not expose the call capability handler from the async framework

Keep the call capability handler encapsulated within the async framework
and do not expose it explicitly via its API. Use the pointer to
ipc_call_t as the sole object identifying an IPC call in the code that
uses the async framework.

This plugs a major leak in the abstraction and also simplifies both the
async framework (slightly) and all IPC servers.

  • Property mode set to 100644
File size: 10.7 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_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
121/** Interrupt handler routine.
122 *
123 * Write new data to the corresponding buffer.
124 *
125 * @param call pointerr to call data.
126 * @param dev Device that caued the interrupt.
127 *
128 */
129static void i8042_irq_handler(ipc_call_t *call, ddf_dev_t *dev)
130{
131 i8042_t *controller = ddf_dev_data_get(dev);
132 errno_t rc;
133
134 const uint8_t status = IPC_GET_ARG1(*call);
135 const uint8_t data = IPC_GET_ARG2(*call);
136
137 i8042_port_t *port = (status & i8042_AUX_DATA) ?
138 controller->aux : controller->kbd;
139
140 fibril_mutex_lock(&port->buf_lock);
141
142 rc = circ_buf_push(&port->cbuf, &data);
143 if (rc != EOK)
144 ddf_msg(LVL_ERROR, "Buffer overrun");
145
146 fibril_mutex_unlock(&port->buf_lock);
147 fibril_condvar_broadcast(&port->buf_cv);
148}
149
150/** Initialize i8042 driver structure.
151 *
152 * @param dev Driver structure to initialize.
153 * @param regs I/O range of registers.
154 * @param irq_kbd IRQ for primary port.
155 * @param irq_mouse IRQ for aux port.
156 * @param ddf_dev DDF device structure of the device.
157 *
158 * @return Error code.
159 *
160 */
161errno_t i8042_init(i8042_t *dev, addr_range_t *regs, int irq_kbd,
162 int irq_mouse, ddf_dev_t *ddf_dev)
163{
164 const size_t range_count = sizeof(i8042_ranges) /
165 sizeof(irq_pio_range_t);
166 irq_pio_range_t ranges[range_count];
167 const size_t cmd_count = sizeof(i8042_cmds) / sizeof(irq_cmd_t);
168 irq_cmd_t cmds[cmd_count];
169 ddf_fun_t *kbd_fun;
170 ddf_fun_t *aux_fun;
171 i8042_regs_t *ar;
172
173 errno_t rc;
174 bool kbd_bound = false;
175 bool aux_bound = false;
176
177 if (regs->size < sizeof(i8042_regs_t)) {
178 rc = EINVAL;
179 goto error;
180 }
181
182 if (pio_enable_range(regs, (void **) &dev->regs) != 0) {
183 rc = EIO;
184 goto error;
185 }
186
187 kbd_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2a");
188 if (kbd_fun == NULL) {
189 rc = ENOMEM;
190 goto error;
191 }
192
193 dev->kbd = ddf_fun_data_alloc(kbd_fun, sizeof(i8042_port_t));
194 if (dev->kbd == NULL) {
195 rc = ENOMEM;
196 goto error;
197 }
198
199 dev->kbd->fun = kbd_fun;
200 dev->kbd->ctl = dev;
201 chardev_srvs_init(&dev->kbd->cds);
202 dev->kbd->cds.ops = &i8042_chardev_ops;
203 dev->kbd->cds.sarg = dev->kbd;
204 fibril_mutex_initialize(&dev->kbd->buf_lock);
205 fibril_condvar_initialize(&dev->kbd->buf_cv);
206
207 rc = ddf_fun_add_match_id(dev->kbd->fun, "char/xtkbd", 90);
208 if (rc != EOK)
209 goto error;
210
211 aux_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2b");
212 if (aux_fun == NULL) {
213 rc = ENOMEM;
214 goto error;
215 }
216
217 dev->aux = ddf_fun_data_alloc(aux_fun, sizeof(i8042_port_t));
218 if (dev->aux == NULL) {
219 rc = ENOMEM;
220 goto error;
221 }
222
223 dev->aux->fun = aux_fun;
224 dev->aux->ctl = dev;
225 chardev_srvs_init(&dev->aux->cds);
226 dev->aux->cds.ops = &i8042_chardev_ops;
227 dev->aux->cds.sarg = dev->aux;
228 fibril_mutex_initialize(&dev->aux->buf_lock);
229 fibril_condvar_initialize(&dev->aux->buf_cv);
230
231 rc = ddf_fun_add_match_id(dev->aux->fun, "char/ps2mouse", 90);
232 if (rc != EOK)
233 goto error;
234
235 ddf_fun_set_conn_handler(dev->kbd->fun, i8042_char_conn);
236 ddf_fun_set_conn_handler(dev->aux->fun, i8042_char_conn);
237
238 circ_buf_init(&dev->kbd->cbuf, dev->kbd->buf_data, BUFFER_SIZE, 1);
239 circ_buf_init(&dev->aux->cbuf, dev->aux->buf_data, BUFFER_SIZE, 1);
240 fibril_mutex_initialize(&dev->write_guard);
241
242 rc = ddf_fun_bind(dev->kbd->fun);
243 if (rc != EOK) {
244 ddf_msg(LVL_ERROR, "Failed to bind keyboard function: %s.",
245 ddf_fun_get_name(dev->kbd->fun));
246 goto error;
247 }
248 kbd_bound = true;
249
250 rc = ddf_fun_bind(dev->aux->fun);
251 if (rc != EOK) {
252 ddf_msg(LVL_ERROR, "Failed to bind aux function: %s.",
253 ddf_fun_get_name(dev->aux->fun));
254 goto error;
255 }
256 aux_bound = true;
257
258 /* Disable kbd and aux */
259 wait_ready(dev);
260 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
261 wait_ready(dev);
262 pio_write_8(&dev->regs->data, i8042_KBD_DISABLE | i8042_AUX_DISABLE);
263
264 /* Flush all current IO */
265 while (pio_read_8(&dev->regs->status) & i8042_OUTPUT_FULL)
266 (void) pio_read_8(&dev->regs->data);
267
268 memcpy(ranges, i8042_ranges, sizeof(i8042_ranges));
269 ranges[0].base = RNGABS(*regs);
270
271
272 ar = RNGABSPTR(*regs);
273 memcpy(cmds, i8042_cmds, sizeof(i8042_cmds));
274 cmds[0].addr = (void *) &ar->status;
275 cmds[3].addr = (void *) &ar->data;
276
277 irq_code_t irq_code = {
278 .rangecount = range_count,
279 .ranges = ranges,
280 .cmdcount = cmd_count,
281 .cmds = cmds
282 };
283
284 cap_irq_handle_t kbd_ihandle;
285 rc = register_interrupt_handler(ddf_dev, irq_kbd,
286 i8042_irq_handler, &irq_code, &kbd_ihandle);
287 if (rc != EOK) {
288 ddf_msg(LVL_ERROR, "Failed set handler for kbd: %s.",
289 ddf_dev_get_name(ddf_dev));
290 goto error;
291 }
292
293 cap_irq_handle_t mouse_ihandle;
294 rc = register_interrupt_handler(ddf_dev, irq_mouse,
295 i8042_irq_handler, &irq_code, &mouse_ihandle);
296 if (rc != EOK) {
297 ddf_msg(LVL_ERROR, "Failed set handler for mouse: %s.",
298 ddf_dev_get_name(ddf_dev));
299 goto error;
300 }
301
302 /* Enable interrupts */
303 async_sess_t *parent_sess = ddf_dev_parent_sess_get(ddf_dev);
304 assert(parent_sess != NULL);
305
306 rc = hw_res_enable_interrupt(parent_sess, irq_kbd);
307 if (rc != EOK) {
308 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable keyboard interrupt: %s.",
309 ddf_dev_get_name(ddf_dev));
310 rc = EIO;
311 goto error;
312 }
313
314 rc = hw_res_enable_interrupt(parent_sess, irq_mouse);
315 if (rc != EOK) {
316 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable mouse interrupt: %s.",
317 ddf_dev_get_name(ddf_dev));
318 rc = EIO;
319 goto error;
320 }
321
322 /* Enable port interrupts. */
323 wait_ready(dev);
324 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
325 wait_ready(dev);
326 pio_write_8(&dev->regs->data, i8042_KBD_IE | i8042_KBD_TRANSLATE |
327 i8042_AUX_IE);
328
329 return EOK;
330error:
331 if (kbd_bound)
332 ddf_fun_unbind(dev->kbd->fun);
333 if (aux_bound)
334 ddf_fun_unbind(dev->aux->fun);
335 if (dev->kbd->fun != NULL)
336 ddf_fun_destroy(dev->kbd->fun);
337 if (dev->aux->fun != NULL)
338 ddf_fun_destroy(dev->aux->fun);
339
340 return rc;
341}
342
343/** Write data to i8042 port.
344 *
345 * @param srv Connection-specific data
346 * @param buffer Data source
347 * @param size Data size
348 * @param nwr Place to store number of bytes successfully written
349 *
350 * @return EOK on success or non-zero error code
351 *
352 */
353static errno_t i8042_write(chardev_srv_t *srv, const void *data, size_t size,
354 size_t *nwr)
355{
356 i8042_port_t *port = (i8042_port_t *)srv->srvs->sarg;
357 i8042_t *i8042 = port->ctl;
358 const char *dp = (const char *)data;
359
360 fibril_mutex_lock(&i8042->write_guard);
361
362 for (size_t i = 0; i < size; ++i) {
363 if (port == i8042->aux) {
364 wait_ready(i8042);
365 pio_write_8(&i8042->regs->status,
366 i8042_CMD_WRITE_AUX);
367 }
368
369 wait_ready(i8042);
370 pio_write_8(&i8042->regs->data, dp[i]);
371 }
372
373 fibril_mutex_unlock(&i8042->write_guard);
374 *nwr = size;
375 return EOK;
376}
377
378/** Read data from i8042 port.
379 *
380 * @param srv Connection-specific data
381 * @param buffer Data place
382 * @param size Data place size
383 * @param nread Place to store number of bytes successfully read
384 *
385 * @return EOK on success or non-zero error code
386 *
387 */
388static errno_t i8042_read(chardev_srv_t *srv, void *dest, size_t size,
389 size_t *nread)
390{
391 i8042_port_t *port = (i8042_port_t *)srv->srvs->sarg;
392 size_t p;
393 uint8_t *destp = (uint8_t *)dest;
394 errno_t rc;
395
396 fibril_mutex_lock(&port->buf_lock);
397
398 while (circ_buf_nused(&port->cbuf) == 0)
399 fibril_condvar_wait(&port->buf_cv, &port->buf_lock);
400
401 p = 0;
402 while (p < size) {
403 rc = circ_buf_pop(&port->cbuf, &destp[p]);
404 if (rc != EOK)
405 break;
406 ++p;
407 }
408
409 fibril_mutex_unlock(&port->buf_lock);
410
411 *nread = p;
412 return EOK;
413}
414
415/** Handle data requests.
416 *
417 * @param call IPC request.
418 * @param arg ddf_fun_t function.
419 *
420 */
421void i8042_char_conn(ipc_call_t *icall, void *arg)
422{
423 i8042_port_t *port = ddf_fun_data_get((ddf_fun_t *)arg);
424
425 chardev_conn(icall, &port->cds);
426}
427
428/**
429 * @}
430 */
Note: See TracBrowser for help on using the repository browser.