freebsd-dev/sys/dev/kbdmux/kbdmux.c
Maksim Yevmenkin fd4df69975 Fix hard lockup caused by kbdmux(4) when kbdmux(4), PS/2 keyboard
(atkbd(4)) and PS/2 mouse (psm(4)) are used together.

Turns out that atkbd(4) check_char() method may return "true" while
read_char() method returns NOKEY. When this happens kbdmux(4) was
simply stuck in the dead loop. Avoid dead loop in kbdmux(4) by breaking
out of the loop if read_char() method returns NOKEY.

It almost seems like a bug in atkkbd(4), atkbd_check_char() calls
kbdc_data_ready(), and, the later will return "true" if there are
pending data in either kbd or aux queue. However, because both aux
and kbd are on the same controller, I'm not sure if this is a bug
or feature.

Tested by:	markus
MFC after:	1 day
2006-02-25 21:59:29 +00:00

1322 lines
30 KiB
C

/*
* kbdmux.c
*/
/*-
* Copyright (c) 2005 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: kbdmux.c,v 1.4 2005/07/14 17:38:35 max Exp $
* $FreeBSD$
*/
#include "opt_kbd.h"
#include <sys/param.h>
#include <sys/conf.h>
#include <sys/consio.h>
#include <sys/fcntl.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/selinfo.h>
#include <sys/systm.h>
#include <sys/taskqueue.h>
#include <sys/tty.h>
#include <sys/uio.h>
#include <dev/kbd/kbdreg.h>
#include <dev/kbd/kbdtables.h>
#define KEYBOARD_NAME "kbdmux"
MALLOC_DECLARE(M_KBDMUX);
MALLOC_DEFINE(M_KBDMUX, KEYBOARD_NAME, "Keyboard multiplexor");
/*****************************************************************************
*****************************************************************************
** Keyboard state
*****************************************************************************
*****************************************************************************/
#define KBDMUX_Q_SIZE 512 /* input queue size */
/*
* XXX
* For now rely on Giant mutex to protect our data structures.
* Just like the rest of keyboard drivers and syscons(4) do.
* Note that callout is initialized as not MP-safe to make sure
* Giant is held.
*/
#if 0 /* not yet */
#define KBDMUX_LOCK_DECL_GLOBAL \
struct mtx ks_lock
#define KBDMUX_LOCK_INIT(s) \
mtx_init(&(s)->ks_lock, "kbdmux", NULL, MTX_DEF|MTX_RECURSE)
#define KBDMUX_LOCK_DESTROY(s) \
mtx_destroy(&(s)->ks_lock)
#define KBDMUX_LOCK(s) \
mtx_lock(&(s)->ks_lock)
#define KBDMUX_UNLOCK(s) \
mtx_unlock(&(s)->ks_lock)
#define KBDMUX_LOCK_ASSERT(s, w) \
mtx_assert(&(s)->ks_lock, (w))
#define KBDMUX_SLEEP(s, f, d, t) \
msleep(&(s)->f, &(s)->ks_lock, PCATCH | (PZERO + 1), (d), (t))
#define KBDMUX_CALLOUT_INIT(s) \
callout_init_mtx(&(s)->ks_timo, &(s)->ks_lock, 0)
#define KBDMUX_QUEUE_INTR(s) \
taskqueue_enqueue(taskqueue_swi_giant, &(s)->ks_task)
#else
#define KBDMUX_LOCK_DECL_GLOBAL
#define KBDMUX_LOCK_INIT(s)
#define KBDMUX_LOCK_DESTROY(s)
#define KBDMUX_LOCK(s)
#define KBDMUX_UNLOCK(s)
#define KBDMUX_LOCK_ASSERT(s, w)
#define KBDMUX_SLEEP(s, f, d, t) \
tsleep(&(s)->f, PCATCH | (PZERO + 1), (d), (t))
#define KBDMUX_CALLOUT_INIT(s) \
callout_init(&(s)->ks_timo, 0)
#define KBDMUX_QUEUE_INTR(s) \
taskqueue_enqueue(taskqueue_swi_giant, &(s)->ks_task)
#endif /* not yet */
#define KBDMUX_INTR(kbd, arg) \
(*kbdsw[(kbd)->kb_index]->intr)((kbd), (arg))
#define KBDMUX_IOCTL(kbd, cmd, arg) \
(*kbdsw[(kbd)->kb_index]->ioctl)((kbd), (cmd), (caddr_t) (arg))
#define KBDMUX_CHECK_CHAR(kbd) \
(*kbdsw[(kbd)->kb_index]->check_char)((kbd))
#define KBDMUX_READ_CHAR(kbd, wait) \
(*kbdsw[(kbd)->kb_index]->read_char)((kbd), (wait))
#define KBDMUX_ENABLE(kbd) \
(*kbdsw[(kbd)->kb_index]->enable)((kbd))
#define KBDMUX_POLL(kbd, on) \
(*kbdsw[(kbd)->kb_index]->poll)((kbd), (on))
#define KBDMUX_CLEAR_STATE(kbd) \
(*kbdsw[(kbd)->kb_index]->clear_state)((kbd))
/*
* kbdmux keyboard
*/
struct kbdmux_kbd
{
keyboard_t *kbd; /* keyboard */
SLIST_ENTRY(kbdmux_kbd) next; /* link to next */
};
typedef struct kbdmux_kbd kbdmux_kbd_t;
/*
* kbdmux state
*/
struct kbdmux_state
{
struct clist ks_inq; /* input chars queue */
struct task ks_task; /* interrupt task */
struct callout ks_timo; /* timeout handler */
#define TICKS (hz) /* rate */
int ks_flags; /* flags */
#define COMPOSE (1 << 0) /* compose char flag */
#define POLLING (1 << 1) /* polling */
#define TASK (1 << 2) /* interrupt task queued */
int ks_mode; /* K_XLATE, K_RAW, K_CODE */
int ks_state; /* state */
int ks_accents; /* accent key index (> 0) */
u_int ks_composed_char; /* composed char code */
u_char ks_prefix; /* AT scan code prefix */
SLIST_HEAD(, kbdmux_kbd) ks_kbds; /* keyboards */
KBDMUX_LOCK_DECL_GLOBAL;
};
typedef struct kbdmux_state kbdmux_state_t;
/*****************************************************************************
*****************************************************************************
** Helper functions
*****************************************************************************
*****************************************************************************/
static task_fn_t kbdmux_kbd_intr;
static timeout_t kbdmux_kbd_intr_timo;
static kbd_callback_func_t kbdmux_kbd_event;
/*
* Interrupt handler task
*/
void
kbdmux_kbd_intr(void *xkbd, int pending)
{
keyboard_t *kbd = (keyboard_t *) xkbd;
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
KBDMUX_INTR(kbd, NULL);
KBDMUX_LOCK(state);
state->ks_flags &= ~TASK;
wakeup(&state->ks_task);
KBDMUX_UNLOCK(state);
}
/*
* Schedule interrupt handler on timeout. Called with locked state.
*/
void
kbdmux_kbd_intr_timo(void *xstate)
{
kbdmux_state_t *state = (kbdmux_state_t *) xstate;
KBDMUX_LOCK_ASSERT(state, MA_OWNED);
if (callout_pending(&state->ks_timo))
return; /* callout was reset */
if (!callout_active(&state->ks_timo))
return; /* callout was stopped */
callout_deactivate(&state->ks_timo);
/* queue interrupt task if needed */
if (state->ks_inq.c_cc > 0 && !(state->ks_flags & TASK) &&
KBDMUX_QUEUE_INTR(state) == 0)
state->ks_flags |= TASK;
/* re-schedule timeout */
callout_reset(&state->ks_timo, TICKS, kbdmux_kbd_intr_timo, state);
}
/*
* Process event from one of our keyboards
*/
static int
kbdmux_kbd_event(keyboard_t *kbd, int event, void *arg)
{
kbdmux_state_t *state = (kbdmux_state_t *) arg;
switch (event) {
case KBDIO_KEYINPUT: {
int c;
KBDMUX_LOCK(state);
/* read all chars from the keyboard */
while (KBDMUX_CHECK_CHAR(kbd)) {
c = KBDMUX_READ_CHAR(kbd, 0);
if (c == NOKEY)
break;
if (c == ERRKEY)
continue; /* XXX ring bell */
if (!KBD_IS_BUSY(kbd))
continue; /* not open - discard the input */
putc(c, &state->ks_inq);
}
/* queue interrupt task if needed */
if (state->ks_inq.c_cc > 0 && !(state->ks_flags & TASK) &&
KBDMUX_QUEUE_INTR(state) == 0)
state->ks_flags |= TASK;
KBDMUX_UNLOCK(state);
} break;
case KBDIO_UNLOADING: {
kbdmux_kbd_t *k;
KBDMUX_LOCK(state);
SLIST_FOREACH(k, &state->ks_kbds, next)
if (k->kbd == kbd)
break;
if (k != NULL) {
kbd_release(k->kbd, &k->kbd);
SLIST_REMOVE(&state->ks_kbds, k, kbdmux_kbd, next);
k->kbd = NULL;
free(k, M_KBDMUX);
}
KBDMUX_UNLOCK(state);
} break;
default:
return (EINVAL);
/* NOT REACHED */
}
return (0);
}
/****************************************************************************
****************************************************************************
** Keyboard driver
****************************************************************************
****************************************************************************/
static int kbdmux_configure(int flags);
static kbd_probe_t kbdmux_probe;
static kbd_init_t kbdmux_init;
static kbd_term_t kbdmux_term;
static kbd_intr_t kbdmux_intr;
static kbd_test_if_t kbdmux_test_if;
static kbd_enable_t kbdmux_enable;
static kbd_disable_t kbdmux_disable;
static kbd_read_t kbdmux_read;
static kbd_check_t kbdmux_check;
static kbd_read_char_t kbdmux_read_char;
static kbd_check_char_t kbdmux_check_char;
static kbd_ioctl_t kbdmux_ioctl;
static kbd_lock_t kbdmux_lock;
static void kbdmux_clear_state_locked(kbdmux_state_t *state);
static kbd_clear_state_t kbdmux_clear_state;
static kbd_get_state_t kbdmux_get_state;
static kbd_set_state_t kbdmux_set_state;
static kbd_poll_mode_t kbdmux_poll;
static keyboard_switch_t kbdmuxsw = {
.probe = kbdmux_probe,
.init = kbdmux_init,
.term = kbdmux_term,
.intr = kbdmux_intr,
.test_if = kbdmux_test_if,
.enable = kbdmux_enable,
.disable = kbdmux_disable,
.read = kbdmux_read,
.check = kbdmux_check,
.read_char = kbdmux_read_char,
.check_char = kbdmux_check_char,
.ioctl = kbdmux_ioctl,
.lock = kbdmux_lock,
.clear_state = kbdmux_clear_state,
.get_state = kbdmux_get_state,
.set_state = kbdmux_set_state,
.get_fkeystr = genkbd_get_fkeystr,
.poll = kbdmux_poll,
.diag = genkbd_diag,
};
/*
* Return the number of found keyboards
*/
static int
kbdmux_configure(int flags)
{
return (1);
}
/*
* Detect a keyboard
*/
static int
kbdmux_probe(int unit, void *arg, int flags)
{
return (0);
}
/*
* Reset and initialize the keyboard (stolen from atkbd.c)
*/
static int
kbdmux_init(int unit, keyboard_t **kbdp, void *arg, int flags)
{
keyboard_t *kbd = NULL;
kbdmux_state_t *state = NULL;
keymap_t *keymap = NULL;
accentmap_t *accmap = NULL;
fkeytab_t *fkeymap = NULL;
int error, needfree, fkeymap_size, delay[2];
if (*kbdp == NULL) {
*kbdp = kbd = malloc(sizeof(*kbd), M_KBDMUX, M_NOWAIT | M_ZERO);
state = malloc(sizeof(*state), M_KBDMUX, M_NOWAIT | M_ZERO);
keymap = malloc(sizeof(key_map), M_KBDMUX, M_NOWAIT);
accmap = malloc(sizeof(accent_map), M_KBDMUX, M_NOWAIT);
fkeymap = malloc(sizeof(fkey_tab), M_KBDMUX, M_NOWAIT);
fkeymap_size = sizeof(fkey_tab)/sizeof(fkey_tab[0]);
needfree = 1;
if ((kbd == NULL) || (state == NULL) || (keymap == NULL) ||
(accmap == NULL) || (fkeymap == NULL)) {
error = ENOMEM;
goto bad;
}
KBDMUX_LOCK_INIT(state);
clist_alloc_cblocks(&state->ks_inq,
KBDMUX_Q_SIZE, KBDMUX_Q_SIZE / 2);
TASK_INIT(&state->ks_task, 0, kbdmux_kbd_intr, (void *) kbd);
KBDMUX_CALLOUT_INIT(state);
SLIST_INIT(&state->ks_kbds);
} else if (KBD_IS_INITIALIZED(*kbdp) && KBD_IS_CONFIGURED(*kbdp)) {
return (0);
} else {
kbd = *kbdp;
state = (kbdmux_state_t *) kbd->kb_data;
keymap = kbd->kb_keymap;
accmap = kbd->kb_accentmap;
fkeymap = kbd->kb_fkeytab;
fkeymap_size = kbd->kb_fkeytab_size;
needfree = 0;
}
if (!KBD_IS_PROBED(kbd)) {
/* XXX assume 101/102 keys keyboard */
kbd_init_struct(kbd, KEYBOARD_NAME, KB_101, 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);
KBDMUX_LOCK(state);
kbdmux_clear_state_locked(state);
state->ks_mode = K_XLATE;
KBDMUX_UNLOCK(state);
}
if (!KBD_IS_INITIALIZED(kbd) && !(flags & KB_CONF_PROBE_ONLY)) {
kbd->kb_config = flags & ~KB_CONF_PROBE_ONLY;
kbdmux_ioctl(kbd, KDSETLED, (caddr_t)&state->ks_state);
delay[0] = kbd->kb_delay1;
delay[1] = kbd->kb_delay2;
kbdmux_ioctl(kbd, KDSETREPEAT, (caddr_t)delay);
KBD_INIT_DONE(kbd);
}
if (!KBD_IS_CONFIGURED(kbd)) {
if (kbd_register(kbd) < 0) {
error = ENXIO;
goto bad;
}
KBD_CONFIG_DONE(kbd);
KBDMUX_LOCK(state);
callout_reset(&state->ks_timo, TICKS, kbdmux_kbd_intr_timo, state);
KBDMUX_UNLOCK(state);
}
return (0);
bad:
if (needfree) {
if (state != NULL) {
clist_free_cblocks(&state->ks_inq);
free(state, M_KBDMUX);
}
if (keymap != NULL)
free(keymap, M_KBDMUX);
if (accmap != NULL)
free(accmap, M_KBDMUX);
if (fkeymap != NULL)
free(fkeymap, M_KBDMUX);
if (kbd != NULL) {
free(kbd, M_KBDMUX);
*kbdp = NULL; /* insure ref doesn't leak to caller */
}
}
return (error);
}
/*
* Finish using this keyboard
*/
static int
kbdmux_term(keyboard_t *kbd)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
kbdmux_kbd_t *k;
KBDMUX_LOCK(state);
/* kill callout */
callout_stop(&state->ks_timo);
/* wait for interrupt task */
while (state->ks_flags & TASK)
KBDMUX_SLEEP(state, ks_task, "kbdmuxc", 0);
/* release all keyboards from the mux */
while ((k = SLIST_FIRST(&state->ks_kbds)) != NULL) {
kbd_release(k->kbd, &k->kbd);
SLIST_REMOVE_HEAD(&state->ks_kbds, next);
k->kbd = NULL;
free(k, M_KBDMUX);
}
/* flush input queue */
ndflush(&state->ks_inq, state->ks_inq.c_cc);
clist_free_cblocks(&state->ks_inq);
KBDMUX_UNLOCK(state);
kbd_unregister(kbd);
KBDMUX_LOCK_DESTROY(state);
bzero(state, sizeof(*state));
free(state, M_KBDMUX);
free(kbd, M_KBDMUX);
return (0);
}
/*
* Keyboard interrupt routine
*/
static int
kbdmux_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 = kbdmux_read_char(kbd, FALSE);
} while (c != NOKEY);
}
return (0);
}
/*
* Test the interface to the device
*/
static int
kbdmux_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
kbdmux_enable(keyboard_t *kbd)
{
KBD_ACTIVATE(kbd);
return (0);
}
/*
* Disallow the access to the device
*/
static int
kbdmux_disable(keyboard_t *kbd)
{
KBD_DEACTIVATE(kbd);
return (0);
}
/*
* Read one byte from the keyboard if it's allowed
*/
static int
kbdmux_read(keyboard_t *kbd, int wait)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
int c;
KBDMUX_LOCK(state);
c = getc(&state->ks_inq);
KBDMUX_UNLOCK(state);
if (c != -1)
kbd->kb_count ++;
return (KBD_IS_ACTIVE(kbd)? c : -1);
}
/*
* Check if data is waiting
*/
static int
kbdmux_check(keyboard_t *kbd)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
int ready;
if (!KBD_IS_ACTIVE(kbd))
return (FALSE);
KBDMUX_LOCK(state);
ready = (state->ks_inq.c_cc > 0)? TRUE : FALSE;
KBDMUX_UNLOCK(state);
return (ready);
}
/*
* Read char from the keyboard (stolen from atkbd.c)
*/
static u_int
kbdmux_read_char(keyboard_t *kbd, int wait)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
u_int action;
int scancode, keycode;
KBDMUX_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) {
KBDMUX_UNLOCK(state);
return (ERRKEY);
}
KBDMUX_UNLOCK(state);
return (action);
}
/* see if there is something in the keyboard queue */
scancode = getc(&state->ks_inq);
if (scancode == -1) {
KBDMUX_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) {
KBDMUX_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;
}
/* XXX assume 101/102 keys AT keyboard */
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) {
KBDMUX_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) {
KBDMUX_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) {
KBDMUX_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) {
KBDMUX_UNLOCK(state);
return (ERRKEY);
}
goto next_code;
case 0x52: /* keypad 0 */
state->ks_composed_char *= 10;
if (state->ks_composed_char > UCHAR_MAX) {
KBDMUX_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;
KBDMUX_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;
KBDMUX_UNLOCK(state);
return (action);
}
/*
* Check if char is waiting
*/
static int
kbdmux_check_char(keyboard_t *kbd)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
int ready;
if (!KBD_IS_ACTIVE(kbd))
return (FALSE);
KBDMUX_LOCK(state);
if (!(state->ks_flags & COMPOSE) && (state->ks_composed_char != 0))
ready = TRUE;
else
ready = (state->ks_inq.c_cc > 0)? TRUE : FALSE;
KBDMUX_UNLOCK(state);
return (ready);
}
/*
* Keyboard ioctl's
*/
static int
kbdmux_ioctl(keyboard_t *kbd, u_long cmd, caddr_t arg)
{
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
};
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
kbdmux_kbd_t *k;
keyboard_info_t *ki;
int error = 0, mode;
if (state == NULL)
return (ENXIO);
switch (cmd) {
case KBADDKBD: /* add keyboard to the mux */
ki = (keyboard_info_t *) arg;
if (ki == NULL || ki->kb_unit < 0 || ki->kb_name[0] == '\0' ||
strcmp(ki->kb_name, "*") == 0)
return (EINVAL); /* bad input */
KBDMUX_LOCK(state);
SLIST_FOREACH(k, &state->ks_kbds, next)
if (k->kbd->kb_unit == ki->kb_unit &&
strcmp(k->kbd->kb_name, ki->kb_name) == 0)
break;
if (k != NULL) {
KBDMUX_UNLOCK(state);
return (0); /* keyboard already in the mux */
}
k = malloc(sizeof(*k), M_KBDMUX, M_NOWAIT | M_ZERO);
if (k == NULL) {
KBDMUX_UNLOCK(state);
return (ENOMEM); /* out of memory */
}
k->kbd = kbd_get_keyboard(
kbd_allocate(
ki->kb_name,
ki->kb_unit,
(void *) &k->kbd,
kbdmux_kbd_event, (void *) state));
if (k->kbd == NULL) {
KBDMUX_UNLOCK(state);
free(k, M_KBDMUX);
return (EINVAL); /* bad keyboard */
}
KBDMUX_ENABLE(k->kbd);
KBDMUX_CLEAR_STATE(k->kbd);
/* set K_RAW mode on slave keyboard */
mode = K_RAW;
error = KBDMUX_IOCTL(k->kbd, KDSKBMODE, &mode);
if (error == 0) {
/* set lock keys state on slave keyboard */
mode = state->ks_state & LOCK_MASK;
error = KBDMUX_IOCTL(k->kbd, KDSKBSTATE, &mode);
}
if (error != 0) {
KBDMUX_UNLOCK(state);
kbd_release(k->kbd, &k->kbd);
k->kbd = NULL;
free(k, M_KBDMUX);
return (error); /* could not set mode */
}
SLIST_INSERT_HEAD(&state->ks_kbds, k, next);
KBDMUX_UNLOCK(state);
break;
case KBRELKBD: /* release keyboard from the mux */
ki = (keyboard_info_t *) arg;
if (ki == NULL || ki->kb_unit < 0 || ki->kb_name[0] == '\0' ||
strcmp(ki->kb_name, "*") == 0)
return (EINVAL); /* bad input */
KBDMUX_LOCK(state);
SLIST_FOREACH(k, &state->ks_kbds, next)
if (k->kbd->kb_unit == ki->kb_unit &&
strcmp(k->kbd->kb_name, ki->kb_name) == 0)
break;
if (k != NULL) {
error = kbd_release(k->kbd, &k->kbd);
if (error == 0) {
SLIST_REMOVE(&state->ks_kbds, k, kbdmux_kbd, next);
k->kbd = NULL;
free(k, M_KBDMUX);
}
} else
error = ENXIO; /* keyboard is not in the mux */
KBDMUX_UNLOCK(state);
break;
case KDGKBMODE: /* get kyboard mode */
KBDMUX_LOCK(state);
*((int *) arg) = state->ks_mode;
KBDMUX_UNLOCK(state);
break;
case KDSKBMODE: /* set keyboard mode */
KBDMUX_LOCK(state);
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);
}
/* FALLTHROUGH */
case K_RAW:
case K_CODE:
if (state->ks_mode != *((int *) arg)) {
kbdmux_clear_state_locked(state);
state->ks_mode = *((int *) arg);
}
break;
default:
error = EINVAL;
break;
}
KBDMUX_UNLOCK(state);
break;
case KDGETLED: /* get keyboard LED */
KBDMUX_LOCK(state);
*((int *) arg) = KBD_LED_VAL(kbd);
KBDMUX_UNLOCK(state);
break;
case KDSETLED: /* set keyboard LED */
KBDMUX_LOCK(state);
/* NOTE: lock key state in ks_state won't be changed */
if (*((int *) arg) & ~LOCK_MASK) {
KBDMUX_UNLOCK(state);
return (EINVAL);
}
KBD_LED_VAL(kbd) = *((int *) arg);
/* KDSETLED on all slave keyboards */
SLIST_FOREACH(k, &state->ks_kbds, next)
KBDMUX_IOCTL(k->kbd, KDSETLED, arg);
KBDMUX_UNLOCK(state);
break;
case KDGKBSTATE: /* get lock key state */
KBDMUX_LOCK(state);
*((int *) arg) = state->ks_state & LOCK_MASK;
KBDMUX_UNLOCK(state);
break;
case KDSKBSTATE: /* set lock key state */
KBDMUX_LOCK(state);
if (*((int *) arg) & ~LOCK_MASK) {
KBDMUX_UNLOCK(state);
return (EINVAL);
}
state->ks_state &= ~LOCK_MASK;
state->ks_state |= *((int *) arg);
/* KDSKBSTATE on all slave keyboards */
SLIST_FOREACH(k, &state->ks_kbds, next)
KBDMUX_IOCTL(k->kbd, KDSKBSTATE, arg);
KBDMUX_UNLOCK(state);
return (kbdmux_ioctl(kbd, KDSETLED, arg));
/* NOT REACHED */
case KDSETREPEAT: /* set keyboard repeat rate (new interface) */
case KDSETRAD: /* set keyboard repeat rate (old interface) */
KBDMUX_LOCK(state);
if (cmd == KDSETREPEAT) {
int i;
/* lookup delay */
for (i = sizeof(delays)/sizeof(delays[0]) - 1; i > 0; i --)
if (((int *) arg)[0] >= delays[i])
break;
mode = i << 5;
/* lookup rate */
for (i = sizeof(rates)/sizeof(rates[0]) - 1; i > 0; i --)
if (((int *) arg)[1] >= rates[i])
break;
mode |= i;
} else
mode = *((int *) arg);
if (mode & ~0x7f) {
KBDMUX_UNLOCK(state);
return (EINVAL);
}
kbd->kb_delay1 = delays[(mode >> 5) & 3];
kbd->kb_delay2 = rates[mode & 0x1f];
/* perform command on all slave keyboards */
SLIST_FOREACH(k, &state->ks_kbds, next)
KBDMUX_IOCTL(k->kbd, cmd, arg);
KBDMUX_UNLOCK(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 */
KBDMUX_LOCK(state);
state->ks_accents = 0;
/* perform command on all slave keyboards */
SLIST_FOREACH(k, &state->ks_kbds, next)
KBDMUX_IOCTL(k->kbd, cmd, arg);
KBDMUX_UNLOCK(state);
/* FALLTHROUGH */
default:
error = genkbd_commonioctl(kbd, cmd, arg);
break;
}
return (error);
}
/*
* Lock the access to the keyboard
*/
static int
kbdmux_lock(keyboard_t *kbd, int lock)
{
return (1); /* XXX */
}
/*
* Clear the internal state of the keyboard
*/
static void
kbdmux_clear_state_locked(kbdmux_state_t *state)
{
KBDMUX_LOCK_ASSERT(state, MA_OWNED);
state->ks_flags &= ~(COMPOSE|POLLING);
state->ks_state &= LOCK_MASK; /* preserve locking key state */
state->ks_accents = 0;
state->ks_composed_char = 0;
/* state->ks_prefix = 0; XXX */
ndflush(&state->ks_inq, state->ks_inq.c_cc);
}
static void
kbdmux_clear_state(keyboard_t *kbd)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
KBDMUX_LOCK(state);
kbdmux_clear_state_locked(state);
KBDMUX_UNLOCK(state);
}
/*
* Save the internal state
*/
static int
kbdmux_get_state(keyboard_t *kbd, void *buf, size_t len)
{
if (len == 0)
return (sizeof(kbdmux_state_t));
if (len < sizeof(kbdmux_state_t))
return (-1);
bcopy(kbd->kb_data, buf, sizeof(kbdmux_state_t)); /* XXX locking? */
return (0);
}
/*
* Set the internal state
*/
static int
kbdmux_set_state(keyboard_t *kbd, void *buf, size_t len)
{
if (len < sizeof(kbdmux_state_t))
return (ENOMEM);
bcopy(buf, kbd->kb_data, sizeof(kbdmux_state_t)); /* XXX locking? */
return (0);
}
/*
* Set polling
*/
static int
kbdmux_poll(keyboard_t *kbd, int on)
{
kbdmux_state_t *state = (kbdmux_state_t *) kbd->kb_data;
kbdmux_kbd_t *k;
KBDMUX_LOCK(state);
if (on)
state->ks_flags |= POLLING;
else
state->ks_flags &= ~POLLING;
/* set poll on slave keyboards */
SLIST_FOREACH(k, &state->ks_kbds, next)
KBDMUX_POLL(k->kbd, on);
KBDMUX_UNLOCK(state);
return (0);
}
/*****************************************************************************
*****************************************************************************
** Module
*****************************************************************************
*****************************************************************************/
KEYBOARD_DRIVER(kbdmux, kbdmuxsw, kbdmux_configure);
static int
kbdmux_modevent(module_t mod, int type, void *data)
{
keyboard_switch_t *sw;
keyboard_t *kbd;
int error;
switch (type) {
case MOD_LOAD:
if ((error = kbd_add_driver(&kbdmux_kbd_driver)) != 0)
break;
if ((sw = kbd_get_switch(KEYBOARD_NAME)) == NULL) {
kbd_delete_driver(&kbdmux_kbd_driver);
error = ENXIO;
break;
}
kbd = NULL;
if ((error = (*sw->probe)(0, NULL, 0)) != 0 ||
(error = (*sw->init)(0, &kbd, NULL, 0)) != 0) {
kbd_delete_driver(&kbdmux_kbd_driver);
break;
}
#ifdef KBD_INSTALL_CDEV
if ((error = kbd_attach(kbd)) != 0) {
(*sw->term)(kbd);
kbd_delete_driver(&kbdmux_kbd_driver);
break;
}
#endif
if ((error = (*sw->enable)(kbd)) != 0) {
(*sw->disable)(kbd);
#ifdef KBD_INSTALL_CDEV
kbd_detach(kbd);
#endif
(*sw->term)(kbd);
kbd_delete_driver(&kbdmux_kbd_driver);
break;
}
break;
case MOD_UNLOAD:
if ((sw = kbd_get_switch(KEYBOARD_NAME)) == NULL)
panic("kbd_get_switch(" KEYBOARD_NAME ") == NULL");
kbd = kbd_get_keyboard(kbd_find_keyboard(KEYBOARD_NAME, 0));
if (kbd == NULL)
panic("kbd_get_keyboard(kbd_find_keyboard(" KEYBOARD_NAME ", 0)) == NULL");
(*sw->disable)(kbd);
#ifdef KBD_INSTALL_CDEV
kbd_detach(kbd);
#endif
(*sw->term)(kbd);
kbd_delete_driver(&kbdmux_kbd_driver);
error = 0;
break;
default:
error = EOPNOTSUPP;
break;
}
return (0);
}
DEV_MODULE(kbdmux, kbdmux_modevent, NULL);