Ticket #858: i8042.c

File i8042.c, 11.3 KB (added by Colin Parker, 22 months ago)

uspace/drv/char/i8042/i8042.c

Line 
1/*
2 * Copyright (c) 2001-2004 Jakub Jermar
3 * Copyright (c) 2006 Josef Cejka
4 * Copyright (c) 2021 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 i8042
33 * @{
34 */
35
36/** @file
37 * @brief i8042 PS/2 port driver.
38 */
39
40#include <adt/circ_buf.h>
41#include <ddf/log.h>
42#include <ddf/interrupt.h>
43#include <ddi.h>
44#include <device/hw_res.h>
45#include <errno.h>
46#include <str_error.h>
47#include <inttypes.h>
48#include <io/chardev_srv.h>
49#include <time.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 *,
71 chardev_flags_t);
72static errno_t i8042_write(chardev_srv_t *, const void *, size_t, size_t *);
73
74static chardev_ops_t i8042_chardev_ops = {
75 .read = i8042_read,
76 .write = i8042_write
77};
78
79static const irq_pio_range_t i8042_ranges[] = {
80 {
81 .base = 0,
82 .size = sizeof(i8042_regs_t)
83 }
84};
85
86/** i8042 Interrupt pseudo-code. */
87static const irq_cmd_t i8042_cmds[] = {
88 {
89 .cmd = CMD_PIO_READ_8,
90 .addr = NULL, /* will be patched in run-time */
91 .dstarg = 1
92 },
93 {
94 .cmd = CMD_AND,
95 .value = i8042_OUTPUT_FULL,
96 .srcarg = 1,
97 .dstarg = 3
98 },
99 {
100 .cmd = CMD_PREDICATE,
101 .value = 2,
102 .srcarg = 3
103 },
104 {
105 .cmd = CMD_PIO_READ_8,
106 .addr = NULL, /* will be patched in run-time */
107 .dstarg = 2
108 },
109 {
110 .cmd = CMD_ACCEPT
111 }
112};
113
114/** Wait until it is safe to write to the device. */
115static void wait_ready(i8042_t *dev)
116{
117 static bool first_time = true;
118 assert(dev);
119
120 struct timespec cur_time, start_time;
121 if(first_time) getuptime(&start_time);
122 while (pio_read_8(&dev->regs->status) & i8042_INPUT_FULL) {
123 if (first_time) {
124 nsec_t time_diff;
125
126 getuptime(&cur_time);
127 time_diff = ts_sub_diff(&cur_time, &start_time);
128 if (time_diff > 100*1000*((nsec_t)1000)) {
129 log_msg(LOG_DEFAULT, LVL_ERROR,
130 "Timed out waiting for device ready.");
131 abort();
132 }
133 }
134 }
135 first_time = false;
136}
137
138/** Interrupt handler routine.
139 *
140 * Write new data to the corresponding buffer.
141 *
142 * @param call pointerr to call data.
143 * @param dev Device that caued the interrupt.
144 *
145 */
146static void i8042_irq_handler(ipc_call_t *call, ddf_dev_t *dev)
147{
148 i8042_t *controller = ddf_dev_data_get(dev);
149 errno_t rc;
150
151 const uint8_t status = ipc_get_arg1(call);
152 const uint8_t data = ipc_get_arg2(call);
153
154 i8042_port_t *port = (status & i8042_AUX_DATA) ?
155 controller->aux : controller->kbd;
156
157 fibril_mutex_lock(&port->buf_lock);
158
159 rc = circ_buf_push(&port->cbuf, &data);
160 if (rc != EOK)
161 ddf_msg(LVL_ERROR, "Buffer overrun");
162
163 fibril_mutex_unlock(&port->buf_lock);
164 fibril_condvar_broadcast(&port->buf_cv);
165
166 async_sess_t *parent_sess = ddf_dev_parent_sess_get(dev);
167 hw_res_clear_interrupt(parent_sess, port->irq);
168}
169
170/** Initialize i8042 driver structure.
171 *
172 * @param dev Driver structure to initialize.
173 * @param regs I/O range of registers.
174 * @param irq_kbd IRQ for primary port.
175 * @param irq_mouse IRQ for aux port.
176 * @param ddf_dev DDF device structure of the device.
177 *
178 * @return Error code.
179 *
180 */
181errno_t i8042_init(i8042_t *dev, addr_range_t *regs, int irq_kbd,
182 int irq_mouse, ddf_dev_t *ddf_dev)
183{
184 const size_t range_count = sizeof(i8042_ranges) /
185 sizeof(irq_pio_range_t);
186 irq_pio_range_t ranges[range_count];
187 const size_t cmd_count = sizeof(i8042_cmds) / sizeof(irq_cmd_t);
188 irq_cmd_t cmds[cmd_count];
189 ddf_fun_t *kbd_fun;
190 ddf_fun_t *aux_fun;
191 i8042_regs_t *ar;
192
193 errno_t rc;
194 bool kbd_bound = false;
195 bool aux_bound = false;
196
197 if (regs->size < sizeof(i8042_regs_t)) {
198 rc = EINVAL;
199 goto error;
200 }
201
202 if (pio_enable_range(regs, (void **) &dev->regs) != 0) {
203 rc = EIO;
204 goto error;
205 }
206
207 kbd_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2a");
208 if (kbd_fun == NULL) {
209 rc = ENOMEM;
210 goto error;
211 }
212
213 dev->kbd = ddf_fun_data_alloc(kbd_fun, sizeof(i8042_port_t));
214 if (dev->kbd == NULL) {
215 rc = ENOMEM;
216 goto error;
217 }
218
219 dev->kbd->fun = kbd_fun;
220 dev->kbd->ctl = dev;
221 chardev_srvs_init(&dev->kbd->cds);
222 dev->kbd->cds.ops = &i8042_chardev_ops;
223 dev->kbd->cds.sarg = dev->kbd;
224 dev->kbd->irq = irq_kbd;
225 fibril_mutex_initialize(&dev->kbd->buf_lock);
226 fibril_condvar_initialize(&dev->kbd->buf_cv);
227
228 rc = ddf_fun_add_match_id(dev->kbd->fun, "char/xtkbd", 90);
229 if (rc != EOK)
230 goto error;
231
232 aux_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2b");
233 if (aux_fun == NULL) {
234 rc = ENOMEM;
235 goto error;
236 }
237
238 dev->aux = ddf_fun_data_alloc(aux_fun, sizeof(i8042_port_t));
239 if (dev->aux == NULL) {
240 rc = ENOMEM;
241 goto error;
242 }
243
244 dev->aux->fun = aux_fun;
245 dev->aux->ctl = dev;
246 chardev_srvs_init(&dev->aux->cds);
247 dev->aux->cds.ops = &i8042_chardev_ops;
248 dev->aux->cds.sarg = dev->aux;
249 dev->aux->irq = irq_mouse;
250 fibril_mutex_initialize(&dev->aux->buf_lock);
251 fibril_condvar_initialize(&dev->aux->buf_cv);
252
253 rc = ddf_fun_add_match_id(dev->aux->fun, "char/ps2mouse", 90);
254 if (rc != EOK)
255 goto error;
256
257 ddf_fun_set_conn_handler(dev->kbd->fun, i8042_char_conn);
258 ddf_fun_set_conn_handler(dev->aux->fun, i8042_char_conn);
259
260 circ_buf_init(&dev->kbd->cbuf, dev->kbd->buf_data, BUFFER_SIZE, 1);
261 circ_buf_init(&dev->aux->cbuf, dev->aux->buf_data, BUFFER_SIZE, 1);
262 fibril_mutex_initialize(&dev->write_guard);
263
264 rc = ddf_fun_bind(dev->kbd->fun);
265 if (rc != EOK) {
266 ddf_msg(LVL_ERROR, "Failed to bind keyboard function: %s.",
267 ddf_fun_get_name(dev->kbd->fun));
268 goto error;
269 }
270 kbd_bound = true;
271
272 rc = ddf_fun_bind(dev->aux->fun);
273 if (rc != EOK) {
274 ddf_msg(LVL_ERROR, "Failed to bind aux function: %s.",
275 ddf_fun_get_name(dev->aux->fun));
276 goto error;
277 }
278 aux_bound = true;
279
280 /* Disable kbd and aux */
281 wait_ready(dev);
282 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
283 wait_ready(dev);
284 pio_write_8(&dev->regs->data, i8042_KBD_DISABLE | i8042_AUX_DISABLE);
285
286 /* Flush all current IO */
287 while (pio_read_8(&dev->regs->status) & i8042_OUTPUT_FULL)
288 (void) pio_read_8(&dev->regs->data);
289
290 memcpy(ranges, i8042_ranges, sizeof(i8042_ranges));
291 ranges[0].base = RNGABS(*regs);
292
293 ar = RNGABSPTR(*regs);
294 memcpy(cmds, i8042_cmds, sizeof(i8042_cmds));
295 cmds[0].addr = (void *) &ar->status;
296 cmds[3].addr = (void *) &ar->data;
297
298 irq_code_t irq_code = {
299 .rangecount = range_count,
300 .ranges = ranges,
301 .cmdcount = cmd_count,
302 .cmds = cmds
303 };
304
305 cap_irq_handle_t kbd_ihandle;
306 rc = register_interrupt_handler(ddf_dev, irq_kbd,
307 i8042_irq_handler, &irq_code, &kbd_ihandle);
308 if (rc != EOK) {
309 ddf_msg(LVL_ERROR, "Failed set handler for kbd: %s.",
310 ddf_dev_get_name(ddf_dev));
311 goto error;
312 }
313
314 cap_irq_handle_t mouse_ihandle;
315 rc = register_interrupt_handler(ddf_dev, irq_mouse,
316 i8042_irq_handler, &irq_code, &mouse_ihandle);
317 if (rc != EOK) {
318 ddf_msg(LVL_ERROR, "Failed set handler for mouse: %s.",
319 ddf_dev_get_name(ddf_dev));
320 goto error;
321 }
322
323 /* Enable interrupts */
324 async_sess_t *parent_sess = ddf_dev_parent_sess_get(ddf_dev);
325 assert(parent_sess != NULL);
326
327 rc = hw_res_enable_interrupt(parent_sess, irq_kbd);
328 if (rc != EOK) {
329 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable keyboard interrupt: %s.",
330 ddf_dev_get_name(ddf_dev));
331 rc = EIO;
332 goto error;
333 }
334
335 rc = hw_res_enable_interrupt(parent_sess, irq_mouse);
336 if (rc != EOK) {
337 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable mouse interrupt: %s.",
338 ddf_dev_get_name(ddf_dev));
339 rc = EIO;
340 goto error;
341 }
342
343 /* Enable port interrupts. */
344 wait_ready(dev);
345 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
346 wait_ready(dev);
347 pio_write_8(&dev->regs->data, i8042_KBD_IE | i8042_KBD_TRANSLATE |
348 i8042_AUX_IE);
349
350 return EOK;
351error:
352 if (kbd_bound)
353 ddf_fun_unbind(dev->kbd->fun);
354 if (aux_bound)
355 ddf_fun_unbind(dev->aux->fun);
356 if (dev->kbd->fun != NULL)
357 ddf_fun_destroy(dev->kbd->fun);
358 if (dev->aux->fun != NULL)
359 ddf_fun_destroy(dev->aux->fun);
360
361 return rc;
362}
363
364/** Write data to i8042 port.
365 *
366 * @param srv Connection-specific data
367 * @param buffer Data source
368 * @param size Data size
369 * @param nwr Place to store number of bytes successfully written
370 *
371 * @return EOK on success or non-zero error code
372 *
373 */
374static errno_t i8042_write(chardev_srv_t *srv, const void *data, size_t size,
375 size_t *nwr)
376{
377 i8042_port_t *port = (i8042_port_t *)srv->srvs->sarg;
378 i8042_t *i8042 = port->ctl;
379 const char *dp = (const char *)data;
380
381 fibril_mutex_lock(&i8042->write_guard);
382
383 for (size_t i = 0; i < size; ++i) {
384 if (port == i8042->aux) {
385 wait_ready(i8042);
386 pio_write_8(&i8042->regs->status,
387 i8042_CMD_WRITE_AUX);
388 }
389
390 wait_ready(i8042);
391 pio_write_8(&i8042->regs->data, dp[i]);
392 }
393
394 fibril_mutex_unlock(&i8042->write_guard);
395 *nwr = size;
396 return EOK;
397}
398
399/** Read data from i8042 port.
400 *
401 * @param srv Connection-specific data
402 * @param buffer Data place
403 * @param size Data place size
404 * @param nread Place to store number of bytes successfully read
405 *
406 * @return EOK on success or non-zero error code
407 *
408 */
409static errno_t i8042_read(chardev_srv_t *srv, void *dest, size_t size,
410 size_t *nread, chardev_flags_t flags)
411{
412 i8042_port_t *port = (i8042_port_t *)srv->srvs->sarg;
413 size_t p;
414 uint8_t *destp = (uint8_t *)dest;
415 errno_t rc;
416
417 fibril_mutex_lock(&port->buf_lock);
418
419 while ((flags & chardev_f_nonblock) == 0 &&
420 circ_buf_nused(&port->cbuf) == 0)
421 fibril_condvar_wait(&port->buf_cv, &port->buf_lock);
422
423 p = 0;
424 while (p < size) {
425 rc = circ_buf_pop(&port->cbuf, &destp[p]);
426 if (rc != EOK)
427 break;
428 ++p;
429 }
430
431 fibril_mutex_unlock(&port->buf_lock);
432
433 *nread = p;
434 return EOK;
435}
436
437/** Handle data requests.
438 *
439 * @param call IPC request.
440 * @param arg ddf_fun_t function.
441 *
442 */
443void i8042_char_conn(ipc_call_t *icall, void *arg)
444{
445 i8042_port_t *port = ddf_fun_data_get((ddf_fun_t *)arg);
446
447 chardev_conn(icall, &port->cds);
448}
449
450/**
451 * @}
452 */