diff options
author | emax <emax@FreeBSD.org> | 2004-11-16 16:59:23 +0000 |
---|---|---|
committer | emax <emax@FreeBSD.org> | 2004-11-16 16:59:23 +0000 |
commit | 0beb137e9e187f0760eefe7cd7bce29578d7eb29 (patch) | |
tree | 3161eb597693d6e2404a878423f5eccba944ab7e /sys/dev | |
parent | 9933c3bdef5c40de3dd15bd8c8e456f64ad93de9 (diff) | |
download | FreeBSD-src-0beb137e9e187f0760eefe7cd7bce29578d7eb29.zip FreeBSD-src-0beb137e9e187f0760eefe7cd7bce29578d7eb29.tar.gz |
Add virtual AT keyboard driver vkbd(4).
Not yet connected to the build.
Diffstat (limited to 'sys/dev')
-rw-r--r-- | sys/dev/vkbd/vkbd.c | 1321 | ||||
-rw-r--r-- | sys/dev/vkbd/vkbd_var.h | 51 |
2 files changed, 1372 insertions, 0 deletions
diff --git a/sys/dev/vkbd/vkbd.c b/sys/dev/vkbd/vkbd.c new file mode 100644 index 0000000..d6ceb21 --- /dev/null +++ b/sys/dev/vkbd/vkbd.c @@ -0,0 +1,1321 @@ +/* + * vkbd.c + * + * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: vkbd.c,v 1.20 2004/11/15 23:53:30 max Exp $ + * $FreeBSD$ + */ + +#include "opt_kbd.h" + +#include <sys/param.h> +#include <sys/conf.h> +#include <sys/kbio.h> +#include <sys/kernel.h> +#include <sys/limits.h> +#include <sys/lock.h> +#include <sys/malloc.h> +#include <sys/module.h> +#include <sys/mutex.h> +#include <sys/poll.h> +#include <sys/proc.h> +#include <sys/queue.h> +#include <sys/systm.h> +#include <sys/taskqueue.h> +#include <sys/uio.h> +#include <sys/vnode.h> +#include <dev/kbd/kbdreg.h> +#include <dev/kbd/kbdtables.h> +#include <dev/vkbd/vkbd_var.h> + +#define DEVICE_NAME "vkbdctl" +#define KEYBOARD_NAME "vkbd" + +MALLOC_DECLARE(M_VKBD); +MALLOC_DEFINE(M_VKBD, KEYBOARD_NAME, "Virtual AT keyboard"); + +/***************************************************************************** + ***************************************************************************** + ** Keyboard state + ***************************************************************************** + *****************************************************************************/ + +#define VKBD_LOCK_DECL struct mtx ks_lock +#define VKBD_LOCK_INIT(s) mtx_init(&(s)->ks_lock, NULL, NULL, MTX_DEF) +#define VKBD_LOCK_DESTROY(s) mtx_destroy(&(s)->ks_lock) +#define VKBD_LOCK(s) mtx_lock(&(s)->ks_lock) +#define VKBD_UNLOCK(s) mtx_unlock(&(s)->ks_lock) +#define VKBD_LOCK_ASSERT(s, w) mtx_assert(&(s)->ks_lock, w) +#define VKBD_SLEEP(s, f, d, t) \ + msleep(&(s)->f, &(s)->ks_lock, PCATCH | (PZERO + 1), d, t) + +#define VKBD_KEYBOARD(d) \ + kbd_get_keyboard(kbd_find_keyboard(KEYBOARD_NAME, dev2unit(d))) + +/* vkbd queue */ +struct vkbd_queue +{ + int q[VKBD_Q_SIZE]; /* queue */ + int head; /* index of the first code */ + int tail; /* index of the last code */ + int cc; /* number of codes in queue */ +}; + +typedef struct vkbd_queue vkbd_queue_t; + +/* vkbd state */ +struct vkbd_state +{ + struct cdev *ks_dev; /* control device */ + + struct selinfo ks_rsel; /* select(2) */ + struct selinfo ks_wsel; + + vkbd_queue_t ks_inq; /* input key codes queue */ + struct task ks_task; /* interrupt task */ + + int ks_flags; /* flags */ +#define OPEN (1 << 0) /* control device is open */ +#define COMPOSE (1 << 1) /* compose flag */ +#define STATUS (1 << 2) /* status has changed */ +#define TASK (1 << 3) /* interrupt task queued */ +#define READ (1 << 4) /* read pending */ +#define WRITE (1 << 5) /* write pending */ + + int ks_mode; /* K_XLATE, K_RAW, K_CODE */ + int ks_polling; /* polling flag */ + int ks_state; /* shift/lock key state */ + int ks_accents; /* accent key index (> 0) */ + u_int ks_composed_char; /* composed char code */ + u_char ks_prefix; /* AT scan code prefix */ + + VKBD_LOCK_DECL; +}; + +typedef struct vkbd_state vkbd_state_t; + +/***************************************************************************** + ***************************************************************************** + ** Character device + ***************************************************************************** + *****************************************************************************/ + +static void vkbd_dev_clone(void *, char *, int, struct cdev **); +static d_open_t vkbd_dev_open; +static d_close_t vkbd_dev_close; +static d_read_t vkbd_dev_read; +static d_write_t vkbd_dev_write; +static d_ioctl_t vkbd_dev_ioctl; +static d_poll_t vkbd_dev_poll; +static void vkbd_dev_intr(void *, int); +static void vkbd_status_changed(vkbd_state_t *); +static int vkbd_data_ready(vkbd_state_t *); +static int vkbd_data_read(vkbd_state_t *, int); + +static struct cdevsw vkbd_dev_cdevsw = { + .d_version = D_VERSION, + .d_flags = D_PSEUDO | D_NEEDGIANT, + .d_open = vkbd_dev_open, + .d_close = vkbd_dev_close, + .d_read = vkbd_dev_read, + .d_write = vkbd_dev_write, + .d_ioctl = vkbd_dev_ioctl, + .d_poll = vkbd_dev_poll, + .d_name = DEVICE_NAME, +}; + +static struct clonedevs *vkbd_dev_clones = NULL; + +/* Clone device */ +static void +vkbd_dev_clone(void *arg, char *name, int namelen, struct cdev **dev) +{ + int unit; + + if (*dev != NULL) + return; + + if (strcmp(name, DEVICE_NAME) == 0) + unit = -1; + else if (dev_stdclone(name, NULL, DEVICE_NAME, &unit) != 1) + return; /* don't recognize the name */ + + /* find any existing device, or allocate new unit number */ + if (clone_create(&vkbd_dev_clones, &vkbd_dev_cdevsw, &unit, dev, 0)) { + *dev = make_dev(&vkbd_dev_cdevsw, unit2minor(unit), + UID_ROOT, GID_WHEEL, 0600, DEVICE_NAME "%d", unit); + if (*dev != NULL) + (*dev)->si_flags |= SI_CHEAPCLONE; + } +} + +/* Open device */ +static int +vkbd_dev_open(struct cdev *dev, int flag, int mode, struct thread *td) +{ + int unit = dev2unit(dev), error; + keyboard_switch_t *sw = NULL; + keyboard_t *kbd = NULL; + vkbd_state_t *state = (vkbd_state_t *) dev->si_drv1; + + /* XXX FIXME: dev->si_drv1 locking */ + if (state == NULL) { + if ((sw = kbd_get_switch(KEYBOARD_NAME)) == NULL) + return (ENXIO); + + if ((error = (*sw->probe)(unit, NULL, 0)) != 0 || + (error = (*sw->init)(unit, &kbd, NULL, 0)) != 0) + return (error); + + state = (vkbd_state_t *) kbd->kb_data; + + if ((error = (*sw->enable)(kbd)) != 0) { + (*sw->term)(kbd); + return (error); + } + +#ifdef KBD_INSTALL_CDEV + if ((error = kbd_attach(kbd)) != 0) { + (*sw->disable)(kbd); + (*sw->term)(kbd); + return (error); + } +#endif /* def KBD_INSTALL_CDEV */ + + dev->si_drv1 = kbd->kb_data; + } + + VKBD_LOCK(state); + + if (state->ks_flags & OPEN) { + VKBD_UNLOCK(state); + return (EBUSY); + } + + state->ks_flags |= OPEN; + state->ks_dev = dev; + + VKBD_UNLOCK(state); + + return (0); +} + +/* Close device */ +static int +vkbd_dev_close(struct cdev *dev, int foo, int bar, struct thread *td) +{ + keyboard_t *kbd = VKBD_KEYBOARD(dev); + vkbd_state_t *state = NULL; + + if (kbd == NULL) + return (ENXIO); + + if (kbd->kb_data == NULL || kbd->kb_data != dev->si_drv1) + panic("%s: kbd->kb_data != dev->si_drv1\n", __func__); + + state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + + /* wait for interrupt task */ + while (state->ks_flags & TASK) + VKBD_SLEEP(state, ks_task, "vkbdc", 0); + + /* wakeup poll()ers */ + selwakeuppri(&state->ks_rsel, PZERO + 1); + selwakeuppri(&state->ks_wsel, PZERO + 1); + + state->ks_flags &= ~OPEN; + state->ks_dev = NULL; + state->ks_inq.head = state->ks_inq.tail = state->ks_inq.cc = 0; + + VKBD_UNLOCK(state); + + (*kbdsw[kbd->kb_index]->disable)(kbd); +#ifdef KBD_INSTALL_CDEV + kbd_detach(kbd); +#endif /* def KBD_INSTALL_CDEV */ + (*kbdsw[kbd->kb_index]->term)(kbd); + + /* XXX FIXME: dev->si_drv1 locking */ + dev->si_drv1 = NULL; + + return (0); +} + +/* Read status */ +static int +vkbd_dev_read(struct cdev *dev, struct uio *uio, int flag) +{ + keyboard_t *kbd = VKBD_KEYBOARD(dev); + vkbd_state_t *state = NULL; + vkbd_status_t status; + int error; + + if (kbd == NULL) + return (ENXIO); + + if (uio->uio_resid != sizeof(status)) + return (EINVAL); + + if (kbd->kb_data == NULL || kbd->kb_data != dev->si_drv1) + panic("%s: kbd->kb_data != dev->si_drv1\n", __func__); + + state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + + if (state->ks_flags & READ) { + VKBD_UNLOCK(state); + return (EALREADY); + } + + state->ks_flags |= READ; +again: + if (state->ks_flags & STATUS) { + state->ks_flags &= ~STATUS; + + status.mode = state->ks_mode; + status.leds = KBD_LED_VAL(kbd); + status.lock = state->ks_state & LOCK_MASK; + status.delay = kbd->kb_delay1; + status.rate = kbd->kb_delay2; + bzero(status.reserved, sizeof(status.reserved)); + + error = uiomove(&status, sizeof(status), uio); + } else { + if (flag & IO_NDELAY) { + error = EWOULDBLOCK; + goto done; + } + + error = VKBD_SLEEP(state, ks_flags, "vkbdr", 0); + if (error != 0) + goto done; + + goto again; + } +done: + state->ks_flags &= ~READ; + + VKBD_UNLOCK(state); + + return (error); +} + +/* Write scancodes */ +static int +vkbd_dev_write(struct cdev *dev, struct uio *uio, int flag) +{ + keyboard_t *kbd = VKBD_KEYBOARD(dev); + vkbd_state_t *state = NULL; + vkbd_queue_t *q = NULL; + int error, avail, bytes; + + if (kbd == NULL) + return (ENXIO); + + if (uio->uio_resid <= 0) + return (EINVAL); + + if (kbd->kb_data == NULL || kbd->kb_data != dev->si_drv1) + panic("%s: kbd->kb_data != dev->si_drv1\n", __func__); + + state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + + if (state->ks_flags & WRITE) { + VKBD_UNLOCK(state); + return (EALREADY); + } + + state->ks_flags |= WRITE; + error = 0; + q = &state->ks_inq; + + while (uio->uio_resid >= sizeof(q->q[0])) { + if (q->head == q->tail) { + if (q->cc == 0) + avail = sizeof(q->q)/sizeof(q->q[0]) - q->head; + else + avail = 0; /* queue must be full */ + } else if (q->head < q->tail) + avail = sizeof(q->q)/sizeof(q->q[0]) - q->tail; + else + avail = q->head - q->tail; + + if (avail == 0) { + if (flag & IO_NDELAY) { + error = EWOULDBLOCK; + break; + } + + error = VKBD_SLEEP(state, ks_inq, "vkbdw", 0); + if (error != 0) + break; + } else { + bytes = avail * sizeof(q->q[0]); + if (bytes > uio->uio_resid) { + avail = uio->uio_resid / sizeof(q->q[0]); + bytes = avail * sizeof(q->q[0]); + } + + error = uiomove((void *) &q->q[q->tail], bytes, uio); + if (error != 0) + break; + + q->cc += avail; + q->tail += avail; + if (q->tail == sizeof(q->q)/sizeof(q->q[0])) + q->tail = 0; + + /* queue interrupt task if needed */ + if (!(state->ks_flags & TASK) && + taskqueue_enqueue(taskqueue_swi_giant, &state->ks_task) == 0) + state->ks_flags |= TASK; + } + } + + state->ks_flags &= ~WRITE; + + VKBD_UNLOCK(state); + + return (error); +} + +/* Process ioctl */ +static int +vkbd_dev_ioctl(struct cdev *dev, u_long cmd, caddr_t data, int flag, struct thread *td) +{ + keyboard_t *kbd = VKBD_KEYBOARD(dev); + + return ((kbd == NULL)? ENXIO : + (*kbdsw[kbd->kb_index]->ioctl)(kbd, cmd, data)); +} + +/* Poll device */ +static int +vkbd_dev_poll(struct cdev *dev, int events, struct thread *td) +{ + vkbd_state_t *state = (vkbd_state_t *) dev->si_drv1; + vkbd_queue_t *q = NULL; + int revents = 0; + + if (state == NULL) + return (ENXIO); + + VKBD_LOCK(state); + + q = &state->ks_inq; + + if (events & (POLLIN | POLLRDNORM)) { + if (state->ks_flags & STATUS) + revents |= events & (POLLIN | POLLRDNORM); + else + selrecord(td, &state->ks_rsel); + } + + if (events & (POLLOUT | POLLWRNORM)) { + if (q->cc < sizeof(q->q)/sizeof(q->q[0])) + revents |= events & (POLLOUT | POLLWRNORM); + else + selrecord(td, &state->ks_wsel); + } + + VKBD_UNLOCK(state); + + return (revents); +} + +/* Interrupt handler */ +void +vkbd_dev_intr(void *xkbd, int pending) +{ + keyboard_t *kbd = (keyboard_t *) xkbd; + vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data; + + (*kbdsw[kbd->kb_index]->intr)(kbd, NULL); + + VKBD_LOCK(state); + + state->ks_flags &= ~TASK; + wakeup(&state->ks_task); + + VKBD_UNLOCK(state); +} + +/* Set status change flags */ +static void +vkbd_status_changed(vkbd_state_t *state) +{ + VKBD_LOCK_ASSERT(state, MA_OWNED); + + if (!(state->ks_flags & STATUS)) { + state->ks_flags |= STATUS; + selwakeuppri(&state->ks_rsel, PZERO + 1); + wakeup(&state->ks_flags); + } +} + +/* Check if we have data in the input queue */ +static int +vkbd_data_ready(vkbd_state_t *state) +{ + VKBD_LOCK_ASSERT(state, MA_OWNED); + + return (state->ks_inq.cc > 0); +} + +/* Read one code from the input queue */ +static int +vkbd_data_read(vkbd_state_t *state, int wait) +{ + vkbd_queue_t *q = &state->ks_inq; + int c; + + VKBD_LOCK_ASSERT(state, MA_OWNED); + + if (q->cc == 0) + return (-1); + + /* get first code from the queue */ + q->cc --; + c = q->q[q->head ++]; + if (q->head == sizeof(q->q)/sizeof(q->q[0])) + q->head = 0; + + /* wakeup ks_inq writers/poll()ers */ + selwakeuppri(&state->ks_wsel, PZERO + 1); + wakeup(q); + + return (c); +} + +/**************************************************************************** + **************************************************************************** + ** Keyboard driver + **************************************************************************** + ****************************************************************************/ + +static int vkbd_configure(int flags); +static kbd_probe_t vkbd_probe; +static kbd_init_t vkbd_init; +static kbd_term_t vkbd_term; +static kbd_intr_t vkbd_intr; +static kbd_test_if_t vkbd_test_if; +static kbd_enable_t vkbd_enable; +static kbd_disable_t vkbd_disable; +static kbd_read_t vkbd_read; +static kbd_check_t vkbd_check; +static kbd_read_char_t vkbd_read_char; +static kbd_check_char_t vkbd_check_char; +static kbd_ioctl_t vkbd_ioctl; +static kbd_lock_t vkbd_lock; +static void vkbd_clear_state_locked(vkbd_state_t *state); +static kbd_clear_state_t vkbd_clear_state; +static kbd_get_state_t vkbd_get_state; +static kbd_set_state_t vkbd_set_state; +static kbd_poll_mode_t vkbd_poll; + +static keyboard_switch_t vkbdsw = { + .probe = vkbd_probe, + .init = vkbd_init, + .term = vkbd_term, + .intr = vkbd_intr, + .test_if = vkbd_test_if, + .enable = vkbd_enable, + .disable = vkbd_disable, + .read = vkbd_read, + .check = vkbd_check, + .read_char = vkbd_read_char, + .check_char = vkbd_check_char, + .ioctl = vkbd_ioctl, + .lock = vkbd_lock, + .clear_state = vkbd_clear_state, + .get_state = vkbd_get_state, + .set_state = vkbd_set_state, + .get_fkeystr = genkbd_get_fkeystr, + .poll = vkbd_poll, + .diag = genkbd_diag, +}; + +static int typematic(int delay, int rate); +static int typematic_delay(int delay); +static int typematic_rate(int rate); + +/* Return the number of found keyboards */ +static int +vkbd_configure(int flags) +{ + return (1); +} + +/* Detect a keyboard */ +static int +vkbd_probe(int unit, void *arg, int flags) +{ + return (0); +} + +/* Reset and initialize the keyboard (stolen from atkbd.c) */ +static int +vkbd_init(int unit, keyboard_t **kbdp, void *arg, int flags) +{ + keyboard_t *kbd = NULL; + vkbd_state_t *state = NULL; + keymap_t *keymap = NULL; + accentmap_t *accmap = NULL; + fkeytab_t *fkeymap = NULL; + int fkeymap_size, delay[2]; + + if (*kbdp == NULL) { + *kbdp = kbd = malloc(sizeof(*kbd), M_VKBD, M_NOWAIT | M_ZERO); + state = malloc(sizeof(*state), M_VKBD, M_NOWAIT | M_ZERO); + keymap = malloc(sizeof(key_map), M_VKBD, M_NOWAIT); + accmap = malloc(sizeof(accent_map), M_VKBD, M_NOWAIT); + fkeymap = malloc(sizeof(fkey_tab), M_VKBD, M_NOWAIT); + fkeymap_size = sizeof(fkey_tab)/sizeof(fkey_tab[0]); + if ((kbd == NULL) || (state == NULL) || (keymap == NULL) || + (accmap == NULL) || (fkeymap == NULL)) { + if (state != NULL) + free(state, M_VKBD); + if (keymap != NULL) + free(keymap, M_VKBD); + if (accmap != NULL) + free(accmap, M_VKBD); + if (fkeymap != NULL) + free(fkeymap, M_VKBD); + if (kbd != NULL) + free(kbd, M_VKBD); + return (ENOMEM); + } + + VKBD_LOCK_INIT(state); + state->ks_inq.head = state->ks_inq.tail = state->ks_inq.cc = 0; + TASK_INIT(&state->ks_task, 0, vkbd_dev_intr, (void *) kbd); + } else if (KBD_IS_INITIALIZED(*kbdp) && KBD_IS_CONFIGURED(*kbdp)) { + return (0); + } else { + kbd = *kbdp; + state = (vkbd_state_t *) kbd->kb_data; + keymap = kbd->kb_keymap; + accmap = kbd->kb_accentmap; + fkeymap = kbd->kb_fkeytab; + fkeymap_size = kbd->kb_fkeytab_size; + } + + if (!KBD_IS_PROBED(kbd)) { + kbd_init_struct(kbd, KEYBOARD_NAME, KB_OTHER, unit, flags, 0, 0); + bcopy(&key_map, keymap, sizeof(key_map)); + bcopy(&accent_map, accmap, sizeof(accent_map)); + bcopy(fkey_tab, fkeymap, + imin(fkeymap_size*sizeof(fkeymap[0]), sizeof(fkey_tab))); + kbd_set_maps(kbd, keymap, accmap, fkeymap, fkeymap_size); + kbd->kb_data = (void *)state; + + KBD_FOUND_DEVICE(kbd); + KBD_PROBE_DONE(kbd); + + VKBD_LOCK(state); + vkbd_clear_state_locked(state); + state->ks_mode = K_XLATE; + /* FIXME: set the initial value for lock keys in ks_state */ + VKBD_UNLOCK(state); + } + if (!KBD_IS_INITIALIZED(kbd) && !(flags & KB_CONF_PROBE_ONLY)) { + kbd->kb_config = flags & ~KB_CONF_PROBE_ONLY; + + vkbd_ioctl(kbd, KDSETLED, (caddr_t)&state->ks_state); + delay[0] = kbd->kb_delay1; + delay[1] = kbd->kb_delay2; + vkbd_ioctl(kbd, KDSETREPEAT, (caddr_t)delay); + + KBD_INIT_DONE(kbd); + } + if (!KBD_IS_CONFIGURED(kbd)) { + if (kbd_register(kbd) < 0) + return (ENXIO); + KBD_CONFIG_DONE(kbd); + } + + return (0); +} + +/* Finish using this keyboard */ +static int +vkbd_term(keyboard_t *kbd) +{ + vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data; + + kbd_unregister(kbd); + + VKBD_LOCK_DESTROY(state); + bzero(state, sizeof(*state)); + free(state, M_VKBD); + + free(kbd->kb_keymap, M_VKBD); + free(kbd->kb_accentmap, M_VKBD); + free(kbd->kb_fkeytab, M_VKBD); + free(kbd, M_VKBD); + + return (0); +} + +/* Keyboard interrupt routine */ +static int +vkbd_intr(keyboard_t *kbd, void *arg) +{ + int c; + + if (KBD_IS_ACTIVE(kbd) && KBD_IS_BUSY(kbd)) { + /* let the callback function to process the input */ + (*kbd->kb_callback.kc_func)(kbd, KBDIO_KEYINPUT, + kbd->kb_callback.kc_arg); + } else { + /* read and discard the input; no one is waiting for input */ + do { + c = vkbd_read_char(kbd, FALSE); + } while (c != NOKEY); + } + + return (0); +} + +/* Test the interface to the device */ +static int +vkbd_test_if(keyboard_t *kbd) +{ + return (0); +} + +/* + * Enable the access to the device; until this function is called, + * the client cannot read from the keyboard. + */ + +static int +vkbd_enable(keyboard_t *kbd) +{ + KBD_ACTIVATE(kbd); + return (0); +} + +/* Disallow the access to the device */ +static int +vkbd_disable(keyboard_t *kbd) +{ + KBD_DEACTIVATE(kbd); + return (0); +} + +/* Read one byte from the keyboard if it's allowed */ +static int +vkbd_read(keyboard_t *kbd, int wait) +{ + vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data; + int c; + + VKBD_LOCK(state); + c = vkbd_data_read(state, wait); + VKBD_UNLOCK(state); + + if (c != -1) + kbd->kb_count ++; + + return (KBD_IS_ACTIVE(kbd)? c : -1); +} + +/* Check if data is waiting */ +static int +vkbd_check(keyboard_t *kbd) +{ + vkbd_state_t *state = NULL; + int ready; + + if (!KBD_IS_ACTIVE(kbd)) + return (FALSE); + + state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + ready = vkbd_data_ready(state); + VKBD_UNLOCK(state); + + return (ready); +} + +/* Read char from the keyboard (stolen from atkbd.c) */ +static u_int +vkbd_read_char(keyboard_t *kbd, int wait) +{ + vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data; + u_int action; + int scancode, keycode; + + VKBD_LOCK(state); + +next_code: + + /* do we have a composed char to return? */ + if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) { + action = state->ks_composed_char; + state->ks_composed_char = 0; + if (action > UCHAR_MAX) { + VKBD_UNLOCK(state); + return (ERRKEY); + } + + VKBD_UNLOCK(state); + return (action); + } + + /* see if there is something in the keyboard port */ + scancode = vkbd_data_read(state, wait); + if (scancode == -1) { + VKBD_UNLOCK(state); + return (NOKEY); + } + /* XXX FIXME: check for -1 if wait == 1! */ + + kbd->kb_count ++; + + /* return the byte as is for the K_RAW mode */ + if (state->ks_mode == K_RAW) { + VKBD_UNLOCK(state); + return (scancode); + } + + /* translate the scan code into a keycode */ + keycode = scancode & 0x7F; + switch (state->ks_prefix) { + case 0x00: /* normal scancode */ + switch(scancode) { + case 0xB8: /* left alt (compose key) released */ + if (state->ks_flags & COMPOSE) { + state->ks_flags &= ~COMPOSE; + if (state->ks_composed_char > UCHAR_MAX) + state->ks_composed_char = 0; + } + break; + case 0x38: /* left alt (compose key) pressed */ + if (!(state->ks_flags & COMPOSE)) { + state->ks_flags |= COMPOSE; + state->ks_composed_char = 0; + } + break; + case 0xE0: + case 0xE1: + state->ks_prefix = scancode; + goto next_code; + } + break; + case 0xE0: /* 0xE0 prefix */ + state->ks_prefix = 0; + switch (keycode) { + case 0x1C: /* right enter key */ + keycode = 0x59; + break; + case 0x1D: /* right ctrl key */ + keycode = 0x5A; + break; + case 0x35: /* keypad divide key */ + keycode = 0x5B; + break; + case 0x37: /* print scrn key */ + keycode = 0x5C; + break; + case 0x38: /* right alt key (alt gr) */ + keycode = 0x5D; + break; + case 0x46: /* ctrl-pause/break on AT 101 (see below) */ + keycode = 0x68; + break; + case 0x47: /* grey home key */ + keycode = 0x5E; + break; + case 0x48: /* grey up arrow key */ + keycode = 0x5F; + break; + case 0x49: /* grey page up key */ + keycode = 0x60; + break; + case 0x4B: /* grey left arrow key */ + keycode = 0x61; + break; + case 0x4D: /* grey right arrow key */ + keycode = 0x62; + break; + case 0x4F: /* grey end key */ + keycode = 0x63; + break; + case 0x50: /* grey down arrow key */ + keycode = 0x64; + break; + case 0x51: /* grey page down key */ + keycode = 0x65; + break; + case 0x52: /* grey insert key */ + keycode = 0x66; + break; + case 0x53: /* grey delete key */ + keycode = 0x67; + break; + /* the following 3 are only used on the MS "Natural" keyboard */ + case 0x5b: /* left Window key */ + keycode = 0x69; + break; + case 0x5c: /* right Window key */ + keycode = 0x6a; + break; + case 0x5d: /* menu key */ + keycode = 0x6b; + break; + case 0x5e: /* power key */ + keycode = 0x6d; + break; + case 0x5f: /* sleep key */ + keycode = 0x6e; + break; + case 0x63: /* wake key */ + keycode = 0x6f; + break; + default: /* ignore everything else */ + goto next_code; + } + break; + case 0xE1: /* 0xE1 prefix */ + /* + * The pause/break key on the 101 keyboard produces: + * E1-1D-45 E1-9D-C5 + * Ctrl-pause/break produces: + * E0-46 E0-C6 (See above.) + */ + state->ks_prefix = 0; + if (keycode == 0x1D) + state->ks_prefix = 0x1D; + goto next_code; + /* NOT REACHED */ + case 0x1D: /* pause / break */ + state->ks_prefix = 0; + if (keycode != 0x45) + goto next_code; + keycode = 0x68; + break; + } + + if (kbd->kb_type == KB_84) { + switch (keycode) { + case 0x37: /* *(numpad)/print screen */ + if (state->ks_flags & SHIFTS) + keycode = 0x5c; /* print screen */ + break; + case 0x45: /* num lock/pause */ + if (state->ks_flags & CTLS) + keycode = 0x68; /* pause */ + break; + case 0x46: /* scroll lock/break */ + if (state->ks_flags & CTLS) + keycode = 0x6c; /* break */ + break; + } + } else if (kbd->kb_type == KB_101) { + switch (keycode) { + case 0x5c: /* print screen */ + if (state->ks_flags & ALTS) + keycode = 0x54; /* sysrq */ + break; + case 0x68: /* pause/break */ + if (state->ks_flags & CTLS) + keycode = 0x6c; /* break */ + break; + } + } + + /* return the key code in the K_CODE mode */ + if (state->ks_mode == K_CODE) { + VKBD_UNLOCK(state); + return (keycode | (scancode & 0x80)); + } + + /* compose a character code */ + if (state->ks_flags & COMPOSE) { + switch (keycode | (scancode & 0x80)) { + /* key pressed, process it */ + case 0x47: case 0x48: case 0x49: /* keypad 7,8,9 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x40; + if (state->ks_composed_char > UCHAR_MAX) { + VKBD_UNLOCK(state); + return (ERRKEY); + } + goto next_code; + case 0x4B: case 0x4C: case 0x4D: /* keypad 4,5,6 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x47; + if (state->ks_composed_char > UCHAR_MAX) { + VKBD_UNLOCK(state); + return (ERRKEY); + } + goto next_code; + case 0x4F: case 0x50: case 0x51: /* keypad 1,2,3 */ + state->ks_composed_char *= 10; + state->ks_composed_char += keycode - 0x4E; + if (state->ks_composed_char > UCHAR_MAX) { + VKBD_UNLOCK(state); + return (ERRKEY); + } + goto next_code; + case 0x52: /* keypad 0 */ + state->ks_composed_char *= 10; + if (state->ks_composed_char > UCHAR_MAX) { + VKBD_UNLOCK(state); + return (ERRKEY); + } + goto next_code; + + /* key released, no interest here */ + case 0xC7: case 0xC8: case 0xC9: /* keypad 7,8,9 */ + case 0xCB: case 0xCC: case 0xCD: /* keypad 4,5,6 */ + case 0xCF: case 0xD0: case 0xD1: /* keypad 1,2,3 */ + case 0xD2: /* keypad 0 */ + goto next_code; + + case 0x38: /* left alt key */ + break; + + default: + if (state->ks_composed_char > 0) { + state->ks_flags &= ~COMPOSE; + state->ks_composed_char = 0; + VKBD_UNLOCK(state); + return (ERRKEY); + } + break; + } + } + + /* keycode to key action */ + action = genkbd_keyaction(kbd, keycode, scancode & 0x80, + &state->ks_state, &state->ks_accents); + if (action == NOKEY) + goto next_code; + + VKBD_UNLOCK(state); + + return (action); +} + +/* Check if char is waiting */ +static int +vkbd_check_char(keyboard_t *kbd) +{ + vkbd_state_t *state = NULL; + int ready; + + if (!KBD_IS_ACTIVE(kbd)) + return (FALSE); + + state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char > 0)) + ready = TRUE; + else + ready = vkbd_data_ready(state); + VKBD_UNLOCK(state); + + return (ready); +} + +/* Some useful control functions (stolen from atkbd.c) */ +static int +vkbd_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg) +{ + vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data; + int i; + + VKBD_LOCK(state); + + switch (cmd) { + case KDGKBMODE: /* get keyboard mode */ + *(int *)arg = state->ks_mode; + break; + + case KDSKBMODE: /* set keyboard mode */ + switch (*(int *)arg) { + case K_XLATE: + if (state->ks_mode != K_XLATE) { + /* make lock key state and LED state match */ + state->ks_state &= ~LOCK_MASK; + state->ks_state |= KBD_LED_VAL(kbd); + vkbd_status_changed(state); + } + /* FALLTHROUGH */ + + case K_RAW: + case K_CODE: + if (state->ks_mode != *(int *)arg) { + vkbd_clear_state_locked(state); + state->ks_mode = *(int *)arg; + vkbd_status_changed(state); + } + break; + + default: + VKBD_UNLOCK(state); + return (EINVAL); + } + break; + + case KDGETLED: /* get keyboard LED */ + *(int *)arg = KBD_LED_VAL(kbd); + break; + + case KDSETLED: /* set keyboard LED */ + /* NOTE: lock key state in ks_state won't be changed */ + if (*(int *)arg & ~LOCK_MASK) { + VKBD_UNLOCK(state); + return (EINVAL); + } + + i = *(int *)arg; + /* replace CAPS LED with ALTGR LED for ALTGR keyboards */ + if (state->ks_mode == K_XLATE && + kbd->kb_keymap->n_keys > ALTGR_OFFSET) { + if (i & ALKED) + i |= CLKED; + else + i &= ~CLKED; + } + + KBD_LED_VAL(kbd) = *(int *)arg; + vkbd_status_changed(state); + break; + + case KDGKBSTATE: /* get lock key state */ + *(int *)arg = state->ks_state & LOCK_MASK; + break; + + case KDSKBSTATE: /* set lock key state */ + if (*(int *)arg & ~LOCK_MASK) { + VKBD_UNLOCK(state); + return (EINVAL); + } + state->ks_state &= ~LOCK_MASK; + state->ks_state |= *(int *)arg; + vkbd_status_changed(state); + VKBD_UNLOCK(state); + /* set LEDs and quit */ + return (vkbd_ioctl(kbd, KDSETLED, arg)); + + case KDSETREPEAT: /* set keyboard repeat rate (new interface) */ + i = typematic(((int *)arg)[0], ((int *)arg)[1]); + kbd->kb_delay1 = typematic_delay(i); + kbd->kb_delay2 = typematic_rate(i); + vkbd_status_changed(state); + break; + + case KDSETRAD: /* set keyboard repeat rate (old interface) */ + kbd->kb_delay1 = typematic_delay(*(int *)arg); + kbd->kb_delay2 = typematic_rate(*(int *)arg); + vkbd_status_changed(state); + break; + + case PIO_KEYMAP: /* set keyboard translation table */ + case PIO_KEYMAPENT: /* set keyboard translation table entry */ + case PIO_DEADKEYMAP: /* set accent key translation table */ + state->ks_accents = 0; + /* FALLTHROUGH */ + + default: + VKBD_UNLOCK(state); + return (genkbd_commonioctl(kbd, cmd, arg)); + } + + VKBD_UNLOCK(state); + + return (0); +} + +/* Lock the access to the keyboard */ +static int +vkbd_lock(keyboard_t *kbd, int lock) +{ + return (1); /* XXX */ +} + +/* Clear the internal state of the keyboard */ +static void +vkbd_clear_state_locked(vkbd_state_t *state) +{ + VKBD_LOCK_ASSERT(state, MA_OWNED); + + state->ks_flags = 0; + state->ks_polling = 0; + state->ks_state &= LOCK_MASK; /* preserve locking key state */ + state->ks_accents = 0; + state->ks_composed_char = 0; +/* state->ks_prefix = 0; XXX */ + + /* flush ks_inq and wakeup writers/poll()ers */ + state->ks_inq.head = state->ks_inq.tail = state->ks_inq.cc = 0; + selwakeuppri(&state->ks_wsel, PZERO + 1); + wakeup(&state->ks_inq); +} + +static void +vkbd_clear_state(keyboard_t *kbd) +{ + vkbd_state_t *state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + vkbd_clear_state_locked(state); + VKBD_UNLOCK(state); +} + +/* Save the internal state */ +static int +vkbd_get_state(keyboard_t *kbd, void *buf, size_t len) +{ + if (len == 0) + return (sizeof(vkbd_state_t)); + if (len < sizeof(vkbd_state_t)) + return (-1); + bcopy(kbd->kb_data, buf, sizeof(vkbd_state_t)); /* XXX locking? */ + return (0); +} + +/* Set the internal state */ +static int +vkbd_set_state(keyboard_t *kbd, void *buf, size_t len) +{ + if (len < sizeof(vkbd_state_t)) + return (ENOMEM); + bcopy(buf, kbd->kb_data, sizeof(vkbd_state_t)); /* XXX locking? */ + return (0); +} + +/* Set polling */ +static int +vkbd_poll(keyboard_t *kbd, int on) +{ + vkbd_state_t *state = NULL; + + state = (vkbd_state_t *) kbd->kb_data; + + VKBD_LOCK(state); + + if (on) + state->ks_polling ++; + else + state->ks_polling --; + + VKBD_UNLOCK(state); + + return (0); +} + +/* + * Local functions + */ + +static int delays[] = { 250, 500, 750, 1000 }; +static int rates[] = { 34, 38, 42, 46, 50, 55, 59, 63, + 68, 76, 84, 92, 100, 110, 118, 126, + 136, 152, 168, 184, 200, 220, 236, 252, + 272, 304, 336, 368, 400, 440, 472, 504 }; + +static int +typematic_delay(int i) +{ + return (delays[(i >> 5) & 3]); +} + +static int +typematic_rate(int i) +{ + return (rates[i & 0x1f]); +} + +static int +typematic(int delay, int rate) +{ + int value; + int i; + + for (i = sizeof(delays)/sizeof(delays[0]) - 1; i > 0; i --) { + if (delay >= delays[i]) + break; + } + value = i << 5; + for (i = sizeof(rates)/sizeof(rates[0]) - 1; i > 0; i --) { + if (rate >= rates[i]) + break; + } + value |= i; + return (value); +} + +/***************************************************************************** + ***************************************************************************** + ** Module + ***************************************************************************** + *****************************************************************************/ + +KEYBOARD_DRIVER(vkbd, vkbdsw, vkbd_configure); + +static int +vkbd_modevent(module_t mod, int type, void *data) +{ + static eventhandler_tag tag; + + switch (type) { + case MOD_LOAD: + clone_setup(&vkbd_dev_clones); + tag = EVENTHANDLER_REGISTER(dev_clone, vkbd_dev_clone, 0, 1000); + if (tag == NULL) { + clone_cleanup(&vkbd_dev_clones); + return (ENOMEM); + } + kbd_add_driver(&vkbd_kbd_driver); + break; + + case MOD_UNLOAD: + kbd_delete_driver(&vkbd_kbd_driver); + EVENTHANDLER_DEREGISTER(dev_clone, tag); + clone_cleanup(&vkbd_dev_clones); + break; + + default: + return (EOPNOTSUPP); + } + + return (0); +} + +DEV_MODULE(vkbd, vkbd_modevent, NULL); + diff --git a/sys/dev/vkbd/vkbd_var.h b/sys/dev/vkbd/vkbd_var.h new file mode 100644 index 0000000..20c5100 --- /dev/null +++ b/sys/dev/vkbd/vkbd_var.h @@ -0,0 +1,51 @@ +/* + * vkbd_var.h + * + * Copyright (c) 2004 Maksim Yevmenkin <m_evmenkin@yahoo.com> + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * $Id: vkbd_var.h,v 1.4 2004/08/17 17:43:14 max Exp $ + * $FreeBSD$ + */ + +#ifndef _VKBD_VAR_H_ +#define _VKBD_VAR_H_ + +#define VKBD_Q_SIZE 64 /* vkbd input queue size */ + +struct vkbd_status +{ + int mode; /* keyboard mode */ + int leds; /* keyboard LEDs */ + int lock; /* keyboard lock key state */ + int delay; /* keyboard delay */ + int rate; /* keyboard rate */ + int reserved[3]; +}; + +typedef struct vkbd_status vkbd_status_t; +typedef struct vkbd_status * vkbd_status_p; + +#endif /* ndef _VKBD_VAR_H_ */ + |