source: mainline/uspace/drv/char/i8042/i8042.c@ 48fa501

lfn serial ticket/834-toolchain-update topic/msim-upgrade topic/simplify-dev-export
Last change on this file since 48fa501 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
RevLine 
[a2bd204f]1/*
2 * Copyright (c) 2001-2004 Jakub Jermar
3 * Copyright (c) 2006 Josef Cejka
4 * Copyright (c) 2009 Jiri Svoboda
[7cb0cb4]5 * Copyright (c) 2011 Jan Vesely
[a2bd204f]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 */
[2df6f6fe]31
[a2bd204f]32/** @addtogroup kbd_port
33 * @ingroup kbd
34 * @{
35 */
[2df6f6fe]36
[a2bd204f]37/** @file
38 * @brief i8042 PS/2 port driver.
39 */
40
[b1f44b4]41#include <device/hw_res.h>
[9ff60d1]42#include <ddi.h>
[a2bd204f]43#include <errno.h>
[ee163b3]44#include <str_error.h>
[a2bd204f]45#include <inttypes.h>
[ee163b3]46#include <ddf/log.h>
47#include <ddf/interrupt.h>
[a2bd204f]48#include "i8042.h"
49
[2df6f6fe]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)
[a2bd204f]97
[a455321]98void default_handler(ddf_fun_t *, ipc_callid_t, ipc_call_t *);
[8bb9540]99
[bd87ae0]100/** Port function operations. */
[a455321]101static ddf_dev_ops_t ops = {
102 .default_handler = default_handler,
[a8f7029]103};
104
[2507d1fc]105static const irq_pio_range_t i8042_ranges[] = {
106 {
107 .base = 0,
108 .size = sizeof(i8042_regs_t)
109 }
110};
111
[336f03b]112/** i8042 Interrupt pseudo-code. */
[ee163b3]113static const irq_cmd_t i8042_cmds[] = {
[a2bd204f]114 {
115 .cmd = CMD_PIO_READ_8,
[2df6f6fe]116 .addr = NULL, /* will be patched in run-time */
[a2bd204f]117 .dstarg = 1
118 },
119 {
[8486c07]120 .cmd = CMD_AND,
[a2bd204f]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,
[2df6f6fe]132 .addr = NULL, /* will be patched in run-time */
[a2bd204f]133 .dstarg = 2
134 },
135 {
136 .cmd = CMD_ACCEPT
137 }
138};
[8bb9540]139
[56fd7cf]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
[336f03b]146/** Wait until it is safe to write to the device. */
[ee163b3]147static void wait_ready(i8042_t *dev)
[a2bd204f]148{
[dd28c1a]149 assert(dev);
150 while (pio_read_8(&dev->regs->status) & i8042_INPUT_FULL);
[a2bd204f]151}
[8bb9540]152
[336f03b]153/** Interrupt handler routine.
[2df6f6fe]154 *
155 * Write new data to the corresponding buffer.
156 *
157 * @param dev Device that caued the interrupt.
158 * @param iid Call id.
[336f03b]159 * @param call pointerr to call data.
[2df6f6fe]160 *
[336f03b]161 */
[2df6f6fe]162static void i8042_irq_handler(ddf_dev_t *dev, ipc_callid_t iid,
163 ipc_call_t *call)
[b1f44b4]164{
[56fd7cf]165 i8042_t *controller = dev_i8042(dev);
[2df6f6fe]166
[a8f7029]167 const uint8_t status = IPC_GET_ARG1(*call);
168 const uint8_t data = IPC_GET_ARG2(*call);
[2df6f6fe]169
[9f97ffe]170 buffer_t *buffer = (status & i8042_AUX_DATA) ?
171 &controller->aux_buffer : &controller->kbd_buffer;
[2df6f6fe]172
[9f97ffe]173 buffer_write(buffer, data);
[b1f44b4]174}
[8bb9540]175
[336f03b]176/** Initialize i8042 driver structure.
[2df6f6fe]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.
[336f03b]182 * @param irq_mouse IRQ for aux port.
[2df6f6fe]183 * @param ddf_dev DDF device structure of the device.
184 *
[336f03b]185 * @return Error code.
[2df6f6fe]186 *
[336f03b]187 */
[ee163b3]188int i8042_init(i8042_t *dev, void *regs, size_t reg_size, int irq_kbd,
189 int irq_mouse, ddf_dev_t *ddf_dev)
[a2bd204f]190{
[56fd7cf]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;
[2df6f6fe]203
[56fd7cf]204 if (reg_size < sizeof(i8042_regs_t)) {
205 rc = EINVAL;
206 goto error;
207 }
[2df6f6fe]208
[56fd7cf]209 if (pio_enable(regs, sizeof(i8042_regs_t), (void **) &dev->regs) != 0) {
210 rc = EIO;
211 goto error;
212 }
[2df6f6fe]213
[e747303]214 dev->kbd_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2a");
[56fd7cf]215 if (dev->kbd_fun == NULL) {
216 rc = ENOMEM;
217 goto error;
218 };
[2df6f6fe]219
[56fd7cf]220 rc = ddf_fun_add_match_id(dev->kbd_fun, "char/xtkbd", 90);
221 if (rc != EOK)
222 goto error;
[2df6f6fe]223
[a455321]224 dev->aux_fun = ddf_fun_create(ddf_dev, fun_inner, "ps2b");
[56fd7cf]225 if (dev->aux_fun == NULL) {
226 rc = ENOMEM;
227 goto error;
[a2bd204f]228 }
[2df6f6fe]229
[56fd7cf]230 rc = ddf_fun_add_match_id(dev->aux_fun, "char/ps2mouse", 90);
231 if (rc != EOK)
232 goto error;
[2df6f6fe]233
[56fd7cf]234 ddf_fun_set_ops(dev->kbd_fun, &ops);
235 ddf_fun_set_ops(dev->aux_fun, &ops);
[2df6f6fe]236
[9f97ffe]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);
[2df6f6fe]240
[56fd7cf]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;
[2df6f6fe]248
[56fd7cf]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;
[2df6f6fe]256
[a2bd204f]257 /* Disable kbd and aux */
[dd28c1a]258 wait_ready(dev);
[78aa0ab]259 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
[dd28c1a]260 wait_ready(dev);
[78aa0ab]261 pio_write_8(&dev->regs->data, i8042_KBD_DISABLE | i8042_AUX_DISABLE);
[2df6f6fe]262
[a2bd204f]263 /* Flush all current IO */
[78aa0ab]264 while (pio_read_8(&dev->regs->status) & i8042_OUTPUT_FULL)
265 (void) pio_read_8(&dev->regs->data);
[a2bd204f]266
[2507d1fc]267 memcpy(ranges, i8042_ranges, sizeof(i8042_ranges));
268 ranges[0].base = (uintptr_t) regs;
[cccdb8b7]269
[ee163b3]270 memcpy(cmds, i8042_cmds, sizeof(i8042_cmds));
[cccdb8b7]271 cmds[0].addr = (void *) &(((i8042_regs_t *) regs)->status);
272 cmds[3].addr = (void *) &(((i8042_regs_t *) regs)->data);
[ee163b3]273
[2507d1fc]274 irq_code_t irq_code = {
275 .rangecount = range_count,
276 .ranges = ranges,
277 .cmdcount = cmd_count,
278 .cmds = cmds
279 };
[2df6f6fe]280
[56fd7cf]281 rc = register_interrupt_handler(ddf_dev, irq_kbd, i8042_irq_handler,
[ee163b3]282 &irq_code);
[56fd7cf]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 }
[2df6f6fe]288
[56fd7cf]289 rc = register_interrupt_handler(ddf_dev, irq_mouse, i8042_irq_handler,
[ee163b3]290 &irq_code);
[56fd7cf]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 }
[2df6f6fe]296
[b1f44b4]297 /* Enable interrupts */
[56fd7cf]298 async_sess_t *parent_sess = ddf_dev_parent_sess_get(ddf_dev);
299 assert(parent_sess != NULL);
[2df6f6fe]300
[b1f44b4]301 const bool enabled = hw_res_enable_interrupt(parent_sess);
[56fd7cf]302 if (!enabled) {
[a1a101d]303 log_msg(LOG_DEFAULT, LVL_ERROR, "Failed to enable interrupts: %s.",
[56fd7cf]304 ddf_dev_get_name(ddf_dev));
305 rc = EIO;
306 goto error;
307 }
[2df6f6fe]308
[7cb0cb4]309 /* Enable port interrupts. */
[dd28c1a]310 wait_ready(dev);
[78aa0ab]311 pio_write_8(&dev->regs->status, i8042_CMD_WRITE_CMDB);
[dd28c1a]312 wait_ready(dev);
[78aa0ab]313 pio_write_8(&dev->regs->data, i8042_KBD_IE | i8042_KBD_TRANSLATE |
[a2bd204f]314 i8042_AUX_IE);
[2df6f6fe]315
[b1f44b4]316 return EOK;
[56fd7cf]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;
[a2bd204f]328}
[8bb9540]329
[2df6f6fe]330// FIXME TODO use shared instead this
[a455321]331enum {
332 IPC_CHAR_READ = DEV_FIRST_CUSTOM_METHOD,
333 IPC_CHAR_WRITE,
334};
335
336/** Write data to i8042 port.
[2df6f6fe]337 *
338 * @param fun DDF function.
[336f03b]339 * @param buffer Data source.
[2df6f6fe]340 * @param size Data size.
341 *
[336f03b]342 * @return Bytes written.
[2df6f6fe]343 *
[336f03b]344 */
[a455321]345static int i8042_write(ddf_fun_t *fun, char *buffer, size_t size)
[a8f7029]346{
[56fd7cf]347 i8042_t *controller = dev_i8042(ddf_fun_get_dev(fun));
[9f97ffe]348 fibril_mutex_lock(&controller->write_guard);
[2df6f6fe]349
[a8f7029]350 for (size_t i = 0; i < size; ++i) {
[876f6463]351 if (controller->aux_fun == fun) {
352 wait_ready(controller);
[2df6f6fe]353 pio_write_8(&controller->regs->status,
354 i8042_CMD_WRITE_AUX);
[876f6463]355 }
[2df6f6fe]356
[336f03b]357 wait_ready(controller);
[a8f7029]358 pio_write_8(&controller->regs->data, buffer[i]);
359 }
[2df6f6fe]360
[9f97ffe]361 fibril_mutex_unlock(&controller->write_guard);
[e747303]362 return size;
[a8f7029]363}
[8bb9540]364
[a455321]365/** Read data from i8042 port.
[2df6f6fe]366 *
367 * @param fun DDF function.
[336f03b]368 * @param buffer Data place.
[2df6f6fe]369 * @param size Data place size.
370 *
[336f03b]371 * @return Bytes read.
[2df6f6fe]372 *
[336f03b]373 */
[a455321]374static int i8042_read(ddf_fun_t *fun, char *data, size_t size)
[a8f7029]375{
[56fd7cf]376 i8042_t *controller = dev_i8042(ddf_fun_get_dev(fun));
[a455321]377 buffer_t *buffer = (fun == controller->aux_fun) ?
378 &controller->aux_buffer : &controller->kbd_buffer;
[2df6f6fe]379
380 for (size_t i = 0; i < size; ++i)
[a455321]381 *data++ = buffer_read(buffer);
[2df6f6fe]382
[e747303]383 return size;
[a8f7029]384}
[a455321]385
[bd87ae0]386/** Handle data requests.
[2df6f6fe]387 *
388 * @param fun ddf_fun_t function.
389 * @param id callid
[bd87ae0]390 * @param call IPC request.
[2df6f6fe]391 *
[bd87ae0]392 */
[a455321]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);
[2df6f6fe]397
[a455321]398 switch (method) {
399 case IPC_CHAR_READ:
400 if (size <= 4 * sizeof(sysarg_t)) {
[22e4e9b]401 sysarg_t message[4] = {};
[2df6f6fe]402
403 i8042_read(fun, (char *) message, size);
[a455321]404 async_answer_4(id, size, message[0], message[1],
405 message[2], message[3]);
[2df6f6fe]406 } else
[a455321]407 async_answer_0(id, ELIMIT);
408 break;
[2df6f6fe]409
[a455321]410 case IPC_CHAR_WRITE:
411 if (size <= 3 * sizeof(sysarg_t)) {
412 const sysarg_t message[3] = {
[2df6f6fe]413 IPC_GET_ARG2(*call),
414 IPC_GET_ARG3(*call),
415 IPC_GET_ARG4(*call)
416 };
417
418 i8042_write(fun, (char *) message, size);
[a455321]419 async_answer_0(id, size);
[2df6f6fe]420 } else
[a455321]421 async_answer_0(id, ELIMIT);
[2df6f6fe]422
[a455321]423 default:
424 async_answer_0(id, EINVAL);
425 }
426}
[2df6f6fe]427
[a2bd204f]428/**
429 * @}
430 */
Note: See TracBrowser for help on using the repository browser.