/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2007 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/* SunOS-4.0 1.60 */
/* From: SunOS4.0 sundev/kbd.c */
/*
* Keyboard input streams module - handles conversion of up/down codes to
* ASCII or event format.
*/
#include <sys/types.h>
#include <sys/param.h>
#include <sys/sysmacros.h>
#include <sys/signal.h>
#include <sys/termios.h>
#include <sys/termio.h>
#include <sys/stream.h>
#include <sys/stropts.h>
#include <sys/strsun.h>
#include <sys/kmem.h>
#include <sys/file.h>
#include <sys/uio.h>
#include <sys/errno.h>
#include <sys/time.h>
#include <sys/consdev.h>
#include <sys/kbd.h>
#include <sys/kbio.h>
#include <sys/kbdreg.h>
#include <sys/vuid_event.h>
#include <sys/debug.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/policy.h>
#include <sys/modctl.h>
#include <sys/beep.h>
#include <sys/int_limits.h>
static struct streamtab kbd_info;
static struct fmodsw fsw = {
"kb",
&kbd_info,
D_MP | D_MTPERMOD
};
/*
* Module linkage information for the kernel.
*/
static struct modlstrmod modlstrmod = {
&mod_strmodops, "streams module for keyboard", &fsw
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modlstrmod, NULL
};
int
_init(void)
{
return (mod_install(&modlinkage));
}
int
_fini(void)
{
return (mod_remove(&modlinkage));
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* For now these are shared.
* These data structures are static (defined in keytables.c) thus
* there is no need to perform any locking.
*/
extern struct keyboards keytables[];
extern char keystringtab[16][KTAB_STRLEN];
extern struct compose_sequence_t kb_compose_table[];
extern signed char kb_compose_map[];
extern struct fltaccent_sequence_t kb_fltaccent_table[];
extern uchar_t kb_numlock_table[];
/*
* This value corresponds approximately to max 10 fingers
*/
static int kbd_downs_size = 15;
typedef struct key_event {
uchar_t key_station; /* Physical key station associated with event */
Firm_event event; /* Event that sent out on down */
} Key_event;
struct kbddata {
queue_t *kbdd_readq;
queue_t *kbdd_writeq;
mblk_t *kbdd_iocpending; /* "ioctl" awaiting buffer */
mblk_t *kbdd_replypending; /* "ioctl" reply awaiting result */
int kbdd_flags; /* random flags */
bufcall_id_t kbdd_bufcallid; /* bufcall id */
timeout_id_t kbdd_rptid; /* timeout id for kbdrpt() */
timeout_id_t kbdd_layoutid; /* timeout id for kbdlayout() */
int kbdd_iocid; /* ID of "ioctl" being waited for */
int kbdd_iocerror; /* error return from "ioctl" */
struct keyboardstate kbdd_state;
/*
* State of keyboard & keyboard
* specific settings, e.g., tables
*/
int kbdd_translate; /* Translate keycodes? */
int kbdd_translatable; /* Keyboard is translatable? */
int kbdd_compat; /* Generating pre-4.1 events? */
short kbdd_ascii_addr; /* Vuid_id_addr for ascii events */
short kbdd_top_addr; /* Vuid_id_addr for top events */
short kbdd_vkey_addr; /* Vuid_id_addr for vkey events */
struct key_event *kbdd_downs;
/*
* Table of key stations currently down
* that have firm events that need
* to be matched with up transitions
* when kbdd_translate is TR_*EVENT
*/
int kbdd_downs_entries; /* # of possible entries in kbdd_downs */
uint_t kbdd_downs_bytes; /* # of bytes allocated for kbdd_downs */
ushort_t compose_key; /* first compose key */
ushort_t fltaccent_entry; /* floating accent keymap entry */
char led_state; /* current state of LEDs */
unsigned char shiftkey; /* used for the new abort keys */
};
#define KBD_OPEN 0x00000001 /* keyboard is open for business */
#define KBD_IOCWAIT 0x00000002 /* "open" waiting for "ioctl" to finish */
#define NO_HARD_RESET 0 /* don't do hard reset */
#define HARD_RESET 1 /* do hard reset */
/*
* Constants setup during the first open of a kbd (so that they can be patched
* for debugging purposes).
*/
static int kbd_repeatrate;
static int kbd_repeatdelay;
static int kbd_overflow_cnt; /* Number of times kbd overflowed input q */
static int kbd_overflow_msg = 1; /* Whether to print message on q overflow */
#ifdef KBD_DEBUG
int kbd_debug = 0;
int kbd_ra_debug = 0;
int kbd_raw_debug = 0;
int kbd_rpt_debug = 0;
int kbd_input_debug = 0;
#endif /* KBD_DEBUG */
static int kbdopen(queue_t *, dev_t *, int, int, cred_t *);
static int kbdclose(queue_t *, int, cred_t *);
static void kbdwput(queue_t *, mblk_t *);
static void kbdrput(queue_t *, mblk_t *);
static struct module_info kbdmiinfo = {
0,
"kb",
0,
INFPSZ,
2048,
128
};
static struct qinit kbdrinit = {
(int (*)())kbdrput,
(int (*)())NULL,
kbdopen,
kbdclose,
(int (*)())NULL,
&kbdmiinfo
};
static struct module_info kbdmoinfo = {
0,
"kb",
0,
INFPSZ,
2048,
128
};
static struct qinit kbdwinit = {
(int (*)())kbdwput,
(int (*)())NULL,
kbdopen,
kbdclose,
(int (*)())NULL,
&kbdmoinfo
};
static struct streamtab kbd_info = {
&kbdrinit,
&kbdwinit,
NULL,
NULL,
};
static void kbdreioctl(void *);
static void kbdioctl(queue_t *, mblk_t *);
static void kbdflush(struct kbddata *);
static void kbduse(struct kbddata *, unsigned);
static void kbdsetled(struct kbddata *);
static void kbd_beep_off(void *arg);
static void kbd_beep_on(void *arg);
static void kbdcmd(queue_t *, char);
static void kbdreset(struct kbddata *, uint_t);
static int kbdsetkey(struct kbddata *, struct kiockey *, cred_t *);
static int kbdgetkey(struct kbddata *, struct kiockey *);
static int kbdskey(struct kbddata *, struct kiockeymap *, cred_t *);
static int kbdgkey(struct kbddata *, struct kiockeymap *);
static void kbdlayouttimeout(void *);
static void kbdinput(struct kbddata *, unsigned);
static void kbdid(struct kbddata *, int);
static struct keymap *settable(struct kbddata *, uint_t);
static void kbdrpt(void *);
static void kbdcancelrpt(struct kbddata *);
static void kbdtranslate(struct kbddata *, unsigned, queue_t *);
static int kbd_do_compose(ushort_t, ushort_t, ushort_t *);
static void kbd_send_esc_event(char, struct kbddata *);
char *strsetwithdecimal(char *, uint_t, uint_t);
static void kbdkeypressed(struct kbddata *, uchar_t, Firm_event *,
ushort_t);
static void kbdqueuepress(struct kbddata *, uchar_t, Firm_event *);
static void kbdkeyreleased(struct kbddata *, uchar_t);
static void kbdreleaseall(struct kbddata *);
static void kbdputcode(uint_t, queue_t *);
static void kbdputbuf(char *, queue_t *);
static void kbdqueueevent(struct kbddata *, Firm_event *);
/*
* Dummy qbufcall callback routine used by open and close.
* The framework will wake up qwait_sig when we return from
* this routine (as part of leaving the perimeters.)
* (The framework enters the perimeters before calling the qbufcall() callback
* and leaves the perimeters after the callback routine has executed. The
* framework performs an implicit wakeup of any thread in qwait/qwait_sig
* when it leaves the perimeter. See qwait(9E).)
*/
/* ARGSUSED */
static void dummy_callback(void *arg)
{}
/*
* Open a keyboard.
* Ttyopen sets line characteristics
*/
/* ARGSUSED */
static int
kbdopen(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *crp)
{
register int error;
register struct kbddata *kbdd;
mblk_t *mp;
mblk_t *datap;
register struct iocblk *iocb;
register struct termios *cb;
/* Set these up only once so that they could be changed from adb */
if (!kbd_repeatrate) {
kbd_repeatrate = (hz+29)/30;
kbd_repeatdelay = hz/2;
}
if (q->q_ptr != NULL)
return (0); /* already attached */
/*
* Only allow open requests to succeed for privileged users. This
* necessary to prevent users from pushing the "kb" module again
* on the stream associated with /dev/kbd.
*/
if (secpolicy_console(crp) != 0) {
return (EPERM);
}
switch (sflag) {
case MODOPEN:
break;
case CLONEOPEN:
return (EINVAL); /* No Bozos! */
}
/* allocate keyboard */
kbdd = kmem_zalloc(sizeof (struct kbddata), KM_SLEEP);
/*
* Set up queue pointers, so that the "put" procedure will accept
* the reply to the "ioctl" message we send down.
*/
q->q_ptr = kbdd;
WR(q)->q_ptr = kbdd;
qprocson(q);
/*
* Setup tty modes.
*/
while ((mp = mkiocb(TCSETSF)) == NULL) {
timeout_id_t id = qbufcall(q, sizeof (struct iocblk), BPRI_HI,
dummy_callback, NULL);
if (!qwait_sig(q)) {
qunbufcall(q, id);
kmem_free(kbdd, sizeof (struct kbddata));
qprocsoff(q);
return (EINTR);
}
}
while ((datap = allocb(sizeof (struct termios), BPRI_HI)) ==
NULL) {
timeout_id_t id = qbufcall(q, sizeof (struct termios), BPRI_HI,
dummy_callback, NULL);
if (!qwait_sig(q)) {
qunbufcall(q, id);
freemsg(mp);
kmem_free(kbdd, sizeof (struct kbddata));
qprocsoff(q);
return (EINTR);
}
}
iocb = (struct iocblk *)mp->b_rptr;
iocb->ioc_count = sizeof (struct termios);
cb = (struct termios *)datap->b_rptr;
cb->c_iflag = 0;
cb->c_oflag = 0;
cb->c_cflag = CREAD|CS8|B1200;
cb->c_lflag = 0;
bzero(cb->c_cc, NCCS);
datap->b_wptr += sizeof (struct termios);
mp->b_cont = datap;
kbdd->kbdd_flags |= KBD_IOCWAIT; /* indicate that we're */
kbdd->kbdd_iocid = iocb->ioc_id; /* waiting for this response */
putnext(WR(q), mp);
/*
* Now wait for it. Let our read queue put routine wake us up
* when it arrives.
*/
while (kbdd->kbdd_flags & KBD_IOCWAIT) {
if (!qwait_sig(q)) {
error = EINTR;
goto error;
}
}
if ((error = kbdd->kbdd_iocerror) != 0)
goto error;
/*
* Set up private data.
*/
kbdd->kbdd_readq = q;
kbdd->kbdd_writeq = WR(q);
kbdd->kbdd_iocpending = NULL;
kbdd->kbdd_translatable = TR_CAN;
kbdd->kbdd_translate = TR_ASCII;
kbdd->kbdd_compat = 1;
kbdd->kbdd_ascii_addr = ASCII_FIRST;
kbdd->kbdd_top_addr = TOP_FIRST;
kbdd->kbdd_vkey_addr = VKEY_FIRST;
/* Allocate dynamic memory for downs table */
kbdd->kbdd_downs_entries = kbd_downs_size;
kbdd->kbdd_downs_bytes = kbd_downs_size * sizeof (Key_event);
kbdd->kbdd_downs = kmem_alloc(kbdd->kbdd_downs_bytes, KM_SLEEP);
kbdd->kbdd_flags = KBD_OPEN;
kbdd->led_state = 0;
/*
* Reset kbd.
*/
kbdreset(kbdd, HARD_RESET);
(void) beep_init((void *)WR(q), kbd_beep_on, kbd_beep_off, NULL);
return (0);
error:
qprocsoff(q);
kmem_free(kbdd, sizeof (struct kbddata));
return (error);
}
/*
* Close a keyboard.
*/
/* ARGSUSED1 */
static int
kbdclose(register queue_t *q, int flag, cred_t *crp)
{
register struct kbddata *kbdd = (struct kbddata *)q->q_ptr;
register mblk_t *mp;
qprocsoff(q);
(void) beep_fini();
/*
* Since we're about to destroy our private data, turn off
* our open flag first, so we don't accept any more input
* and try to use that data.
*/
kbdd->kbdd_flags = 0;
if ((mp = kbdd->kbdd_replypending) != NULL) {
/*
* There was a KIOCLAYOUT pending; presumably, it timed out.
* Throw the reply away.
*/
kbdd->kbdd_replypending = NULL;
freemsg(mp);
}
/* clear all timeouts */
if (kbdd->kbdd_bufcallid)
qunbufcall(q, kbdd->kbdd_bufcallid);
if (kbdd->kbdd_rptid)
(void) quntimeout(q, kbdd->kbdd_rptid);
if (kbdd->kbdd_layoutid)
(void) quntimeout(q, kbdd->kbdd_layoutid);
kmem_free(kbdd->kbdd_downs, kbdd->kbdd_downs_bytes);
kmem_free(kbdd, sizeof (struct kbddata));
return (0);
}
/*
* Line discipline output queue put procedure: handles M_IOCTL
* messages.
*/
static void
kbdwput(register queue_t *q, register mblk_t *mp)
{
/*
* Process M_FLUSH, and some M_IOCTL, messages here; pass
* everything else down.
*/
switch (mp->b_datap->db_type) {
case M_FLUSH:
if (*mp->b_rptr & FLUSHW)
flushq(q, FLUSHDATA);
if (*mp->b_rptr & FLUSHR)
flushq(RD(q), FLUSHDATA);
default:
putnext(q, mp); /* pass it down the line */
break;
case M_IOCTL:
kbdioctl(q, mp);
break;
}
}
static void
kbdreioctl(void *kbdd_addr)
{
struct kbddata *kbdd = kbdd_addr;
queue_t *q;
mblk_t *mp;
kbdd->kbdd_bufcallid = 0;
q = kbdd->kbdd_writeq;
if ((mp = kbdd->kbdd_iocpending) != NULL) {
kbdd->kbdd_iocpending = NULL; /* not pending any more */
kbdioctl(q, mp);
}
}
static void
kbdioctl(register queue_t *q, register mblk_t *mp)
{
register struct kbddata *kbdd = (struct kbddata *)q->q_ptr;
register struct iocblk *iocp;
register short new_translate;
register Vuid_addr_probe *addr_probe;
register short *addr_ptr;
mblk_t *datap;
size_t ioctlrespsize;
int err = 0;
int tmp;
int cycles;
int frequency;
int msecs;
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
case VUIDSFORMAT:
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
new_translate = (*(int *)mp->b_cont->b_rptr == VUID_NATIVE) ?
TR_ASCII : TR_EVENT;
if (new_translate == kbdd->kbdd_translate)
break;
kbdd->kbdd_translate = new_translate;
goto output_format_change;
case KIOCTRANS:
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
new_translate = *(int *)mp->b_cont->b_rptr;
if (new_translate == kbdd->kbdd_translate)
break;
kbdd->kbdd_translate = new_translate;
goto output_format_change;
case KIOCCMD:
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
tmp = (char)(*(int *)mp->b_cont->b_rptr);
if (tmp == KBD_CMD_BELL)
(void) beeper_on(BEEP_TYPE4);
else if (tmp == KBD_CMD_NOBELL)
(void) beeper_off();
else
kbdcmd(q, tmp);
break;
case KIOCMKTONE:
if (iocp->ioc_count != TRANSPARENT) {
/*
* We don't support non-transparent ioctls,
* i.e. I_STR ioctls
*/
err = EINVAL;
break;
}
tmp = (int)(*(intptr_t *)mp->b_cont->b_rptr);
cycles = tmp & 0xffff;
msecs = (tmp >> 16) & 0xffff;
if (cycles == 0)
frequency = UINT16_MAX;
else if (cycles == UINT16_MAX)
frequency = 0;
else {
frequency = (PIT_HZ + cycles / 2) / cycles;
if (frequency > UINT16_MAX)
frequency = UINT16_MAX;
}
err = beep_mktone(frequency, msecs);
break;
case KIOCSLED:
err = miocpullup(mp, sizeof (uchar_t));
if (err != 0)
break;
kbdd->led_state = *(uchar_t *)mp->b_cont->b_rptr;
kbdsetled(kbdd);
break;
case KIOCGLED:
if ((datap = allocb(sizeof (uchar_t), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(uchar_t *)datap->b_wptr = kbdd->led_state;
datap->b_wptr += sizeof (uchar_t);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (uchar_t);
break;
case VUIDGFORMAT:
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr =
(kbdd->kbdd_translate == TR_EVENT ||
kbdd->kbdd_translate == TR_UNTRANS_EVENT) ?
VUID_FIRM_EVENT: VUID_NATIVE;
datap->b_wptr += sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCGTRANS:
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = kbdd->kbdd_translate;
datap->b_wptr += sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case VUIDSADDR:
err = miocpullup(mp, sizeof (Vuid_addr_probe));
if (err != 0)
break;
addr_probe = (Vuid_addr_probe *)mp->b_cont->b_rptr;
switch (addr_probe->base) {
case ASCII_FIRST:
addr_ptr = &kbdd->kbdd_ascii_addr;
break;
case TOP_FIRST:
addr_ptr = &kbdd->kbdd_top_addr;
break;
case VKEY_FIRST:
addr_ptr = &kbdd->kbdd_vkey_addr;
break;
default:
err = ENODEV;
}
if ((err == 0) && (*addr_ptr != addr_probe->data.next)) {
*addr_ptr = addr_probe->data.next;
goto output_format_change;
}
break;
case VUIDGADDR:
err = miocpullup(mp, sizeof (Vuid_addr_probe));
if (err != 0)
break;
addr_probe = (Vuid_addr_probe *)mp->b_cont->b_rptr;
switch (addr_probe->base) {
case ASCII_FIRST:
addr_probe->data.current = kbdd->kbdd_ascii_addr;
break;
case TOP_FIRST:
addr_probe->data.current = kbdd->kbdd_top_addr;
break;
case VKEY_FIRST:
addr_probe->data.current = kbdd->kbdd_vkey_addr;
break;
default:
err = ENODEV;
}
break;
case KIOCTRANSABLE:
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
if (kbdd->kbdd_translatable != *(int *)mp->b_cont->b_rptr) {
kbdd->kbdd_translatable = *(int *)mp->b_cont->b_rptr;
kbdreset(kbdd, HARD_RESET);
goto output_format_change;
}
break;
case KIOCGTRANSABLE:
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = kbdd->kbdd_translatable;
datap->b_wptr += sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCSCOMPAT:
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
kbdd->kbdd_compat = *(int *)mp->b_cont->b_rptr;
break;
case KIOCGCOMPAT:
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = kbdd->kbdd_compat;
datap->b_wptr += sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCSETKEY:
err = miocpullup(mp, sizeof (struct kiockey));
if (err != 0)
break;
err = kbdsetkey(kbdd, (struct kiockey *)mp->b_cont->b_rptr,
iocp->ioc_cr);
/*
* Since this only affects any subsequent key presses,
* don't goto output_format_change. One might want to
* toggle the keytable entries dynamically.
*/
break;
case KIOCGETKEY:
err = miocpullup(mp, sizeof (struct kiockey));
if (err != 0)
break;
err = kbdgetkey(kbdd, (struct kiockey *)mp->b_cont->b_rptr);
break;
case KIOCSKEY:
err = miocpullup(mp, sizeof (struct kiockeymap));
if (err != 0)
break;
err = kbdskey(kbdd, (struct kiockeymap *)mp->b_cont->b_rptr,
iocp->ioc_cr);
/*
* Since this only affects any subsequent key presses,
* don't goto output_format_change. One might want to
* toggle the keytable entries dynamically.
*/
break;
case KIOCGKEY:
err = miocpullup(mp, sizeof (struct kiockeymap));
if (err != 0)
break;
err = kbdgkey(kbdd, (struct kiockeymap *)mp->b_cont->b_rptr);
break;
case KIOCSDIRECT:
goto output_format_change;
case KIOCGDIRECT:
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = 1; /* always direct */
datap->b_wptr += sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCTYPE:
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = kbdd->kbdd_state.k_id;
datap->b_wptr += sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCLAYOUT:
if ((datap = kbdd->kbdd_replypending) != NULL) {
/*
* There was an earlier KIOCLAYOUT pending; presumably,
* it timed out. Throw the reply away.
*/
kbdd->kbdd_replypending = NULL;
freemsg(datap);
}
if (kbdd->kbdd_state.k_id == KB_SUN4 ||
kbdd->kbdd_state.k_id == KB_PC) {
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
iocp->ioc_rval = 0;
iocp->ioc_error = 0; /* brain rot */
iocp->ioc_count = sizeof (int);
if (mp->b_cont) /* free msg to prevent memory leak */
freemsg(mp->b_cont);
mp->b_cont = datap;
mp->b_datap->db_type = M_IOCACK;
kbdd->kbdd_replypending = mp;
kbdcmd(q, (char)KBD_CMD_GETLAYOUT);
if (kbdd->kbdd_layoutid)
(void) quntimeout(q, kbdd->kbdd_layoutid);
kbdd->kbdd_layoutid = qtimeout(q, kbdlayouttimeout,
kbdd, hz / 5);
return; /* wait for reply from keyboard */
} else {
/*
* Not a Type 4 keyboard; return an immediate error.
*/
err = EINVAL;
break;
}
case KIOCGRPTDELAY:
/*
* Report the autorepeat delay, unit in millisecond
*/
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = TICK_TO_MSEC(kbd_repeatdelay);
datap->b_wptr += sizeof (int);
/* free msg to prevent memory leak */
if (mp->b_cont != NULL)
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCSRPTDELAY:
/*
* Set the autorepeat delay
*/
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
/* validate the input */
if (*(int *)mp->b_cont->b_rptr < KIOCRPTDELAY_MIN) {
err = EINVAL;
break;
}
kbd_repeatdelay = MSEC_TO_TICK(*(int *)mp->b_cont->b_rptr);
if (kbd_repeatdelay <= 0)
kbd_repeatdelay = 1;
break;
case KIOCGRPTRATE:
/*
* Report the autorepeat rate
*/
if ((datap = allocb(sizeof (int), BPRI_HI)) == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = TICK_TO_MSEC(kbd_repeatrate);
datap->b_wptr += sizeof (int);
/* free msg to prevent memory leak */
if (mp->b_cont != NULL)
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCSRPTRATE:
/*
* Set the autorepeat rate
*/
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
/* validate the input */
if (*(int *)mp->b_cont->b_rptr < KIOCRPTRATE_MIN) {
err = EINVAL;
break;
}
kbd_repeatrate = MSEC_TO_TICK(*(int *)mp->b_cont->b_rptr);
if (kbd_repeatrate <= 0)
kbd_repeatrate = 1;
break;
default:
putnext(q, mp); /* pass it down the line */
return;
}
goto done;
output_format_change:
kbdflush(kbdd);
done:
if (err != 0) {
iocp->ioc_rval = 0;
iocp->ioc_error = err;
mp->b_datap->db_type = M_IOCNAK;
} else {
iocp->ioc_rval = 0;
iocp->ioc_error = 0; /* brain rot */
mp->b_datap->db_type = M_IOCACK;
}
qreply(q, mp);
return;
allocfailure:
/*
* We needed to allocate something to handle this "ioctl", but
* couldn't; save this "ioctl" and arrange to get called back when
* it's more likely that we can get what we need.
* If there's already one being saved, throw it out, since it
* must have timed out.
*/
if (kbdd->kbdd_iocpending != NULL)
freemsg(kbdd->kbdd_iocpending);
kbdd->kbdd_iocpending = mp;
if (kbdd->kbdd_bufcallid)
qunbufcall(q, kbdd->kbdd_bufcallid);
kbdd->kbdd_bufcallid = qbufcall(q, ioctlrespsize, BPRI_HI,
kbdreioctl, kbdd);
}
static void
kbdflush(register struct kbddata *kbdd)
{
register queue_t *q;
/* Flush pending data already sent upstream */
if ((q = kbdd->kbdd_readq) != NULL && q->q_next != NULL)
(void) putnextctl1(q, M_FLUSH, FLUSHR);
/* Flush pending ups */
bzero(kbdd->kbdd_downs, kbdd->kbdd_downs_bytes);
kbdcancelrpt(kbdd);
}
/*
* Pass keycode upstream, either translated or untranslated.
*/
static void
kbduse(register struct kbddata *kbdd, unsigned keycode)
{
register queue_t *readq;
#ifdef KBD_DEBUG
if (kbd_input_debug) printf("KBD USE key=%d\n", keycode);
#endif
if ((readq = kbdd->kbdd_readq) == NULL)
return;
if (!kbdd->kbdd_translatable ||
kbdd->kbdd_translate == TR_NONE)
kbdputcode(keycode, readq);
else
kbdtranslate(kbdd, keycode, readq);
}
static void
kbd_beep_on(void *arg)
{
kbdcmd((queue_t *)arg, KBD_CMD_BELL);
}
static void
kbd_beep_off(void *arg)
{
kbdcmd((queue_t *)arg, KBD_CMD_NOBELL);
}
/*
* kbdclick is used to remember the current click value of the
* Sun-3 keyboard. This brain damaged keyboard will reset the
* clicking to the "default" value after a reset command and
* there is no way to read out the current click value. We
* cannot send a click command immediately after the reset
* command or the keyboard gets screwed up. So we wait until
* we get the ID byte before we send back the click command.
* Unfortunately, this means that there is a small window
* where the keyboard can click when it really shouldn't be.
* A value of -1 means that kbdclick has not been initialized yet.
*/
static int kbdclick = -1;
/*
* Send command byte to keyboard, if you can.
*/
static void
kbdcmd(register queue_t *q, char cmd)
{
register mblk_t *bp;
if (canput(q)) {
if ((bp = allocb(1, BPRI_MED)) == NULL)
cmn_err(CE_WARN,
"kbdcmd: Can't allocate block for command");
else {
*bp->b_wptr++ = cmd;
putnext(q, bp);
if (cmd == KBD_CMD_NOCLICK)
kbdclick = 0;
else if (cmd == KBD_CMD_CLICK)
kbdclick = 1;
}
}
}
/*
* Update the keyboard LEDs to match the current keyboard state.
* Do this only on Type 4 keyboards; other keyboards don't support the
* KBD_CMD_SETLED command (nor, for that matter, the appropriate LEDs).
*/
static void
kbdsetled(register struct kbddata *kbdd)
{
if (kbdd->kbdd_state.k_id == KB_SUN4 ||
kbdd->kbdd_state.k_id == KB_PC) {
kbdcmd(kbdd->kbdd_writeq, KBD_CMD_SETLED);
kbdcmd(kbdd->kbdd_writeq, kbdd->led_state);
}
}
/*
* Reset the keyboard
*/
static void
kbdreset(register struct kbddata *kbdd, uint_t hard_reset)
{
register struct keyboardstate *k;
k = &kbdd->kbdd_state;
if (kbdd->kbdd_translatable) {
k->k_idstate = KID_NONE;
k->k_id = -1;
k->k_state = NORMAL;
if (hard_reset)
kbdcmd(kbdd->kbdd_writeq, KBD_CMD_RESET);
} else {
bzero(k, sizeof (struct keyboardstate));
k->k_id = KB_ASCII;
k->k_idstate = KID_OK;
}
}
/*
* Old special codes.
*/
#define OLD_SHIFTKEYS 0x80
#define OLD_BUCKYBITS 0x90
#define OLD_FUNNY 0xA0
#define OLD_FA_UMLAUT 0xA9
#define OLD_FA_CFLEX 0xAA
#define OLD_FA_TILDE 0xAB
#define OLD_FA_CEDILLA 0xAC
#define OLD_FA_ACUTE 0xAD
#define OLD_FA_GRAVE 0xAE
#define OLD_ISOCHAR 0xAF
#define OLD_STRING 0xB0
#define OLD_LEFTFUNC 0xC0
#define OLD_RIGHTFUNC 0xD0
#define OLD_TOPFUNC 0xE0
#define OLD_BOTTOMFUNC 0xF0
/*
* Map old special codes to new ones.
* Indexed by ((old special code) >> 4) & 0x07; add (old special code) & 0x0F.
*/
static ushort_t special_old_to_new[] = {
SHIFTKEYS,
BUCKYBITS,
FUNNY,
STRING,
LEFTFUNC,
RIGHTFUNC,
TOPFUNC,
BOTTOMFUNC,
};
/*
* Set individual keystation translation from old-style entry.
* TODO: Have each keyboard own own translation tables.
*/
static int
kbdsetkey(register struct kbddata *kbdd, struct kiockey *key, cred_t *cr)
{
int strtabindex, i;
struct keymap *km;
register int tablemask;
register ushort_t entry;
if (key->kio_station >= KEYMAP_SIZE)
return (EINVAL);
if (kbdd->kbdd_state.k_curkeyboard == NULL)
return (EINVAL);
tablemask = key->kio_tablemask;
if (tablemask == KIOCABORT1) {
if (secpolicy_console(cr) != 0)
return (EPERM);
kbdd->kbdd_state.k_curkeyboard->k_abort1 = key->kio_station;
return (0);
}
if (tablemask == KIOCABORT2) {
if (secpolicy_console(cr) != 0)
return (EPERM);
kbdd->kbdd_state.k_curkeyboard->k_abort2 = key->kio_station;
return (0);
}
if ((tablemask & ALTGRAPHMASK) ||
(km = settable(kbdd, (uint_t)tablemask)) == NULL)
return (EINVAL);
if (key->kio_entry >= (uchar_t)OLD_STRING &&
key->kio_entry <= (uchar_t)(OLD_STRING + 15)) {
strtabindex = key->kio_entry - OLD_STRING;
for (i = 0; i < KTAB_STRLEN; i++)
keystringtab[strtabindex][i] = key->kio_string[i];
keystringtab[strtabindex][KTAB_STRLEN-1] = '\0';
}
entry = key->kio_entry;
/*
* There's nothing we need do with OLD_ISOCHAR.
*/
if (entry != OLD_ISOCHAR) {
if (entry & 0x80) {
if (entry >= OLD_FA_UMLAUT && entry <= OLD_FA_GRAVE)
entry = FA_CLASS + (entry & 0x0F) - 9;
else
entry =
special_old_to_new[entry >> 4 & 0x07]
+ (entry & 0x0F);
}
}
km->keymap[key->kio_station] = entry;
return (0);
}
/*
* Map new special codes to old ones.
* Indexed by (new special code) >> 8; add (new special code) & 0xFF.
*/
static uchar_t special_new_to_old[] = {
0, /* normal */
OLD_SHIFTKEYS, /* SHIFTKEYS */
OLD_BUCKYBITS, /* BUCKYBITS */
OLD_FUNNY, /* FUNNY */
OLD_FA_UMLAUT, /* FA_CLASS */
OLD_STRING, /* STRING */
OLD_LEFTFUNC, /* FUNCKEYS */
};
/*
* Get individual keystation translation as old-style entry.
*/
static int
kbdgetkey(register struct kbddata *kbdd, struct kiockey *key)
{
int strtabindex, i;
struct keymap *km;
register ushort_t entry;
if (key->kio_station >= KEYMAP_SIZE)
return (EINVAL);
if (kbdd->kbdd_state.k_curkeyboard == NULL)
return (EINVAL);
if (key->kio_tablemask == KIOCABORT1) {
key->kio_station = kbdd->kbdd_state.k_curkeyboard->k_abort1;
return (0);
}
if (key->kio_tablemask == KIOCABORT2) {
key->kio_station = kbdd->kbdd_state.k_curkeyboard->k_abort2;
return (0);
}
if ((km = settable(kbdd, (uint_t)key->kio_tablemask)) == NULL)
return (EINVAL);
entry = km->keymap[key->kio_station];
if (entry & 0xFF00)
key->kio_entry =
special_new_to_old[(ushort_t)(entry & 0xFF00) >> 8]
+ (entry & 0x00FF);
else {
if (entry & 0x80)
key->kio_entry = (ushort_t)OLD_ISOCHAR; /* you lose */
else
key->kio_entry = (ushort_t)entry;
}
if (entry >= STRING && entry <= (uchar_t)(STRING + 15)) {
strtabindex = entry - STRING;
for (i = 0; i < KTAB_STRLEN; i++)
key->kio_string[i] = keystringtab[strtabindex][i];
}
return (0);
}
/*
* Set individual keystation translation from new-style entry.
* TODO: Have each keyboard own own translation tables.
*/
static int
kbdskey(register struct kbddata *kbdd, struct kiockeymap *key, cred_t *cr)
{
int strtabindex, i;
struct keymap *km;
if (key->kio_station >= KEYMAP_SIZE)
return (EINVAL);
if (kbdd->kbdd_state.k_curkeyboard == NULL)
return (EINVAL);
if (key->kio_tablemask == KIOCABORT1) {
if (secpolicy_console(cr) != 0)
return (EPERM);
kbdd->kbdd_state.k_curkeyboard->k_abort1 = key->kio_station;
return (0);
}
if (key->kio_tablemask == KIOCABORT2) {
if (secpolicy_console(cr) != 0)
return (EPERM);
kbdd->kbdd_state.k_curkeyboard->k_abort2 = key->kio_station;
return (0);
}
if ((km = settable(kbdd, (uint_t)key->kio_tablemask)) == NULL)
return (EINVAL);
if (key->kio_entry >= STRING &&
key->kio_entry <= (ushort_t)(STRING + 15)) {
strtabindex = key->kio_entry-STRING;
for (i = 0; i < KTAB_STRLEN; i++)
keystringtab[strtabindex][i] = key->kio_string[i];
keystringtab[strtabindex][KTAB_STRLEN-1] = '\0';
}
km->keymap[key->kio_station] = key->kio_entry;
return (0);
}
/*
* Get individual keystation translation as new-style entry.
*/
static int
kbdgkey(register struct kbddata *kbdd, struct kiockeymap *key)
{
int strtabindex, i;
struct keymap *km;
if (key->kio_station >= KEYMAP_SIZE)
return (EINVAL);
if (kbdd->kbdd_state.k_curkeyboard == NULL)
return (EINVAL);
if (key->kio_tablemask == KIOCABORT1) {
key->kio_station = kbdd->kbdd_state.k_curkeyboard->k_abort1;
return (0);
}
if (key->kio_tablemask == KIOCABORT2) {
key->kio_station = kbdd->kbdd_state.k_curkeyboard->k_abort2;
return (0);
}
if ((km = settable(kbdd, (uint_t)key->kio_tablemask)) == NULL)
return (EINVAL);
key->kio_entry = km->keymap[key->kio_station];
if (key->kio_entry >= STRING &&
key->kio_entry <= (ushort_t)(STRING + 15)) {
strtabindex = key->kio_entry-STRING;
for (i = 0; i < KTAB_STRLEN; i++)
key->kio_string[i] = keystringtab[strtabindex][i];
}
return (0);
}
static void
kbdlayouttimeout(void *arg)
{
struct kbddata *kbdd = arg;
mblk_t *mp;
kbdd->kbdd_layoutid = 0;
/*
* Timed out waiting for reply to "get keyboard layout" command.
* Return an ETIME error.
*/
if ((mp = kbdd->kbdd_replypending) != NULL) {
kbdd->kbdd_replypending = NULL;
mp->b_datap->db_type = M_IOCNAK;
((struct iocblk *)mp->b_rptr)->ioc_error = ETIME;
putnext(kbdd->kbdd_readq, mp);
}
}
/*
* Put procedure for input from driver end of stream (read queue).
*/
static void
kbdrput(register queue_t *q, register mblk_t *mp)
{
struct kbddata *kbdd = (struct kbddata *)q->q_ptr;
register mblk_t *bp;
register uchar_t *readp;
struct iocblk *iocp;
if (kbdd == 0) {
freemsg(mp); /* nobody's listening */
return;
}
switch (mp->b_datap->db_type) {
case M_FLUSH:
if (*mp->b_rptr & FLUSHW)
flushq(WR(q), FLUSHDATA);
if (*mp->b_rptr & FLUSHR)
flushq(q, FLUSHDATA);
default:
putnext(q, mp);
return;
case M_BREAK:
/*
* Will get M_BREAK only if this is not the system
* keyboard, otherwise serial port will eat break
* and call kmdb/OBP, without passing anything up.
*/
freemsg(mp);
return;
case M_IOCACK:
case M_IOCNAK:
/*
* If we are doing an "ioctl" ourselves, check if this
* is the reply to that code. If so, wake up the
* "open" routine, and toss the reply, otherwise just
* pass it up.
*/
iocp = (struct iocblk *)mp->b_rptr;
if (!(kbdd->kbdd_flags & KBD_IOCWAIT) ||
iocp->ioc_id != kbdd->kbdd_iocid) {
/*
* This isn't the reply we're looking for. Move along.
*/
if (kbdd->kbdd_flags & KBD_OPEN)
putnext(q, mp);
else
freemsg(mp); /* not ready to listen */
} else {
kbdd->kbdd_flags &= ~KBD_IOCWAIT;
kbdd->kbdd_iocerror = iocp->ioc_error;
freemsg(mp);
}
return;
case M_DATA:
if (!(kbdd->kbdd_flags & KBD_OPEN)) {
freemsg(mp); /* not read to listen */
return;
}
break;
}
/*
* A data message, consisting of bytes from the keyboard.
* Ram them through our state machine.
*/
bp = mp;
do {
readp = bp->b_rptr;
while (readp < bp->b_wptr)
kbdinput(kbdd, *readp++);
bp->b_rptr = readp;
} while ((bp = bp->b_cont) != NULL); /* next block, if any */
freemsg(mp);
}
/*
* A keypress was received. Process it through the state machine
* to check for aborts.
*/
static void
kbdinput(register struct kbddata *kbdd, register unsigned key)
{
register struct keyboardstate *k;
register mblk_t *mp;
k = &kbdd->kbdd_state;
#ifdef KBD_DEBUG
if (kbd_input_debug)
printf("kbdinput key %x\n", key);
#endif
switch (k->k_idstate) {
case KID_NONE:
if (key == RESETKEY) {
k->k_idstate = KID_GOT_PREFACE;
} else {
kbdreset(kbdd, HARD_RESET);
/* allows hot plug of kbd after booting without kbd */
}
return;
case KID_GOT_PREFACE:
kbdid(kbdd, (int)key);
/*
* We just did a reset command to a Type 3 or Type 4
* keyboard which sets the click back to the default
* (which is currently ON!). We use the kbdclick
* variable to see if the keyboard should be turned on
* or off. If it has not been set, then we use the
* keyboard-click? property.
*/
switch (kbdclick) {
case 0:
kbdcmd(kbdd->kbdd_writeq, KBD_CMD_NOCLICK);
break;
case 1:
kbdcmd(kbdd->kbdd_writeq, KBD_CMD_CLICK);
break;
case -1:
default:
{
char wrkbuf[8];
int len;
kbdcmd(kbdd->kbdd_writeq, KBD_CMD_NOCLICK);
bzero(wrkbuf, 8);
len = 7;
if (ddi_getlongprop_buf(DDI_DEV_T_ANY,
ddi_root_node(), 0, "keyboard-click?",
(caddr_t)wrkbuf, &len) ==
DDI_PROP_SUCCESS &&
len > 0 && len < 8) {
if (strcmp(wrkbuf, "true") == 0) {
kbdcmd(kbdd->kbdd_writeq,
KBD_CMD_CLICK);
}
}
}
break;
}
/*
* A keyboard reset clears the LEDs.
* Restore the LEDs from the last value we set
* them to.
*/
kbdsetled(kbdd);
return;
case KID_OK:
switch (key) {
#if defined(KBD_PRESSED_PREFIX)
case KBD_PRESSED_PREFIX:
k->k_idstate = KID_GOT_PRESSED;
return;
#endif
#if defined(KBD_RELEASED_PREFIX)
case KBD_RELEASED_PREFIX:
k->k_idstate = KID_GOT_RELEASED;
return;
#endif
case 0:
kbdreset(kbdd, HARD_RESET);
return;
/*
* we want to check for ID only if we are in
* translatable mode.
*/
case RESETKEY:
kbdreset(kbdd, NO_HARD_RESET);
if (k->k_idstate == KID_NONE) {
k->k_idstate = KID_GOT_PREFACE;
}
return;
case LAYOUTKEY:
k->k_idstate = KID_GOT_LAYOUT;
return;
}
break;
#if defined(KBD_PRESSED_PREFIX)
case KID_GOT_PRESSED:
key = BUILDKEY(key, PRESSED);
k->k_idstate = KID_OK;
break;
#endif
#if defined(KBD_RELEASED_PREFIX)
case KID_GOT_RELEASED:
key = BUILDKEY(key, RELEASED);
k->k_idstate = KID_OK;
break;
#endif
case KID_GOT_LAYOUT:
if (kbdd->kbdd_layoutid)
(void) quntimeout(kbdd->kbdd_readq,
kbdd->kbdd_layoutid);
if ((mp = kbdd->kbdd_replypending) != NULL) {
kbdd->kbdd_replypending = NULL;
*(int *)mp->b_cont->b_wptr = key;
mp->b_cont->b_wptr += sizeof (int);
putnext(kbdd->kbdd_readq, mp);
}
k->k_idstate = KID_OK;
return;
}
switch (k->k_state) {
#if defined(__sparc)
normalstate:
k->k_state = NORMAL;
/* FALLTHRU */
#endif
case NORMAL:
#if defined(__sparc)
if (k->k_curkeyboard) {
if (key == k->k_curkeyboard->k_abort1) {
k->k_state = ABORT1;
break;
}
if ((key == k->k_curkeyboard->k_newabort1) ||
(key == k->k_curkeyboard->k_newabort1a)) {
k->k_state = NEWABORT1;
kbdd->shiftkey = key;
}
}
#endif
kbduse(kbdd, key);
break;
#if defined(__sparc)
case ABORT1:
if (k->k_curkeyboard) {
/*
* Only recognize this as an abort sequence if
* the "hardware" console is set to be this device.
*/
if (key == k->k_curkeyboard->k_abort2 &&
rconsvp == wsconsvp) {
DELAY(100000);
abort_sequence_enter((char *)NULL);
k->k_state = NORMAL;
kbduse(kbdd, IDLEKEY); /* fake */
return;
} else {
kbduse(kbdd, k->k_curkeyboard->k_abort1);
goto normalstate;
}
}
break;
case NEWABORT1:
if (k->k_curkeyboard) {
/*
* Only recognize this as an abort sequence if
* the "hardware" console is set to be this device.
*/
if (key == k->k_curkeyboard->k_newabort2 &&
rconsvp == wsconsvp) {
DELAY(100000);
abort_sequence_enter((char *)NULL);
k->k_state = NORMAL;
kbdd->shiftkey |= RELEASED;
kbduse(kbdd, kbdd->shiftkey);
kbduse(kbdd, IDLEKEY); /* fake */
return;
} else {
goto normalstate;
}
}
break;
#endif
case COMPOSE1:
case COMPOSE2:
case FLTACCENT:
if (key != IDLEKEY)
kbduse(kbdd, key);
break;
}
}
static void
kbdid(register struct kbddata *kbdd, int id)
{
register struct keyboardstate *k;
int i;
k = &kbdd->kbdd_state;
k->k_idstate = KID_OK;
k->k_shiftmask = 0;
k->k_buckybits = 0;
/*
* Reset k_rptkey to IDLEKEY. We need to cancel
* the autorepeat feature, if any.
*/
if (k->k_rptkey != IDLEKEY) {
if (kbdd->kbdd_rptid)
(void) quntimeout(kbdd->kbdd_readq, kbdd->kbdd_rptid);
kbdd->kbdd_rptid = 0;
k->k_rptkey = IDLEKEY;
}
k->k_curkeyboard = NULL;
for (i = 0; keytables[i].table; i++) {
if (keytables[i].id == id) {
k->k_id = id;
k->k_curkeyboard = keytables[i].table;
break;
}
}
if (!k->k_curkeyboard) {
k->k_id = keytables[0].id;
k->k_curkeyboard = keytables[0].table;
cmn_err(CE_WARN, "kbd: Unknown keyboard type, "
"Type %d assumed", k->k_id);
}
}
/*
* This routine determines which table we should look in to decode
* the current keycode.
*/
static struct keymap *
settable(register struct kbddata *kbdd, register uint_t mask)
{
register struct keyboard *kp;
kp = kbdd->kbdd_state.k_curkeyboard;
if (kp == NULL)
return (NULL);
if (mask & UPMASK)
return (kp->k_up);
if (mask & NUMLOCKMASK)
return (kp->k_numlock);
if (mask & CTRLMASK)
return (kp->k_control);
if (mask & ALTGRAPHMASK)
return (kp->k_altgraph);
if (mask & SHIFTMASK)
return (kp->k_shifted);
if (mask & CAPSMASK)
return (kp->k_caps);
return (kp->k_normal);
}
static void
kbdrpt(void *arg)
{
struct kbddata *kbdd = arg;
struct keyboardstate *k;
k = &kbdd->kbdd_state;
#ifdef KBD_DEBUG
if (kbd_rpt_debug)
printf("kbdrpt key %x\n", k->k_rptkey);
#endif
kbdd->kbdd_rptid = 0;
kbdkeyreleased(kbdd, KEYOF(k->k_rptkey));
kbduse(kbdd, k->k_rptkey);
if (k->k_rptkey != IDLEKEY) {
kbdd->kbdd_rptid = qtimeout(kbdd->kbdd_readq, kbdrpt,
kbdd, kbd_repeatrate);
}
}
static void
kbdcancelrpt(register struct kbddata *kbdd)
{
register struct keyboardstate *k;
k = &kbdd->kbdd_state;
if (k->k_rptkey != IDLEKEY) {
if (kbdd->kbdd_rptid)
(void) quntimeout(kbdd->kbdd_readq, kbdd->kbdd_rptid);
kbdd->kbdd_rptid = 0;
k->k_rptkey = IDLEKEY;
}
ASSERT(kbdd->kbdd_rptid == 0);
}
static void
kbdtranslate(struct kbddata *kbdd, unsigned keycode, queue_t *q)
{
register uchar_t key;
register unsigned newstate;
unsigned shiftmask;
register ushort_t entry, entrytype;
register char *cp, *bufp;
register struct keyboardstate *k;
ushort_t result_iso;
struct keymap *km;
Firm_event fe;
int i, ret_val;
char buf[14];
k = &kbdd->kbdd_state;
newstate = STATEOF(keycode);
key = KEYOF(keycode);
#ifdef KBD_DEBUG
if (kbd_input_debug) {
printf("KBD TRANSLATE keycode=0x%x newstate=0x%x key=0x%x\n",
keycode, newstate, key);
}
#endif
if (kbdd->kbdd_translate == TR_UNTRANS_EVENT) {
if (newstate == PRESSED) {
bzero(&fe, sizeof (fe));
fe.id = key;
fe.value = 1;
kbdqueuepress(kbdd, key, &fe);
} else {
kbdkeyreleased(kbdd, key);
}
return;
}
shiftmask = k->k_shiftmask;
if (newstate == RELEASED)
shiftmask |= UPMASK;
km = settable(kbdd, shiftmask);
if (km == NULL) { /* gross error */
kbdcancelrpt(kbdd);
return;
}
if (key >= KEYMAP_SIZE)
return;
entry = km->keymap[key];
if (entry == NONL) {
/*
* NONL appears only in the Num Lock table, and indicates that
* this key is not affected by Num Lock. This means we should
* ask for the table we would have gotten had Num Lock not been
* down, and translate using that table.
*/
km = settable(kbdd, shiftmask & ~NUMLOCKMASK);
if (km == NULL) { /* gross error */
kbdcancelrpt(kbdd);
return;
}
entry = km->keymap[key];
}
entrytype = (ushort_t)(entry & 0xFF00) >> 8;
if (entrytype == (SHIFTKEYS >> 8)) {
/*
* Handle the state of toggle shifts specially.
* Ups should be ignored, and downs should be mapped to ups if
* that shift is currently on.
*/
if ((1 << (entry & 0x0F)) & k->k_curkeyboard->k_toggleshifts) {
if ((1 << (entry & 0x0F)) & k->k_togglemask) {
newstate = RELEASED; /* toggling off */
} else {
newstate = PRESSED; /* toggling on */
}
}
} else {
/*
* Handle Compose and floating accent key sequences
*/
if (k->k_state == COMPOSE1) {
if (newstate == RELEASED)
return;
if (entry < ASCII_SET_SIZE) {
if (kb_compose_map[entry] >= 0) {
kbdd->compose_key = entry;
k->k_state = COMPOSE2;
return;
}
}
k->k_state = NORMAL;
kbdd->led_state &= ~LED_COMPOSE;
kbdsetled(kbdd);
return;
} else if (k->k_state == COMPOSE2) {
if (newstate == RELEASED)
return;
k->k_state = NORMAL; /* next state is "normal" */
kbdd->led_state &= ~LED_COMPOSE;
kbdsetled(kbdd);
if (entry < ASCII_SET_SIZE) {
if (kb_compose_map[entry] >= 0) {
if (kbdd->compose_key <= entry) {
ret_val = kbd_do_compose(
kbdd->compose_key,
entry,
&result_iso);
} else {
ret_val = kbd_do_compose(
entry,
kbdd->compose_key,
&result_iso);
}
if (ret_val == 1) {
if (kbdd->kbdd_translate ==
TR_EVENT) {
fe.id =
(kbdd->kbdd_compat ?
ISO_FIRST :
EUC_FIRST)
+ result_iso;
fe.value = 1;
kbdqueueevent(
kbdd,
&fe);
} else if (
kbdd->kbdd_translate ==
TR_ASCII)
kbdputcode(
result_iso,
q);
}
}
}
return;
} else if (k->k_state == FLTACCENT) {
if (newstate == RELEASED)
return;
k->k_state = NORMAL; /* next state is "normal" */
for (i = 0;
(kb_fltaccent_table[i].fa_entry
!= kbdd->fltaccent_entry) ||
(kb_fltaccent_table[i].ascii != entry);
i++) {
if (kb_fltaccent_table[i].fa_entry == 0)
/* Invalid second key: ignore key */
return;
}
if (kbdd->kbdd_translate == TR_EVENT) {
fe.id = (kbdd->kbdd_compat ?
ISO_FIRST : EUC_FIRST)
+ kb_fltaccent_table[i].iso;
fe.value = 1;
kbdqueueevent(kbdd, &fe);
} else if (kbdd->kbdd_translate == TR_ASCII)
kbdputcode(kb_fltaccent_table[i].iso, q);
return;
}
}
/*
* If the key is going down, and it's not one of the keys that doesn't
* auto-repeat, set up the auto-repeat timeout.
*
* The keys that don't auto-repeat are the Compose key,
* the shift keys, the "bucky bit" keys, the "floating accent" keys,
* and the function keys when in TR_EVENT mode.
*/
if (newstate == PRESSED && entrytype != (SHIFTKEYS >> 8) &&
entrytype != (BUCKYBITS >> 8) && entrytype != (FUNNY >> 8) &&
entrytype != (FA_CLASS >> 8) &&
!((entrytype == (FUNCKEYS >> 8) || entrytype == (PADKEYS >> 8)) &&
kbdd->kbdd_translate == TR_EVENT)) {
if (k->k_rptkey != keycode) {
kbdcancelrpt(kbdd);
kbdd->kbdd_rptid = qtimeout(q, kbdrpt, kbdd,
kbd_repeatdelay);
k->k_rptkey = keycode;
}
} else if (key == KEYOF(k->k_rptkey)) /* key going up */
kbdcancelrpt(kbdd);
if ((newstate == RELEASED) && (kbdd->kbdd_translate == TR_EVENT))
kbdkeyreleased(kbdd, key);
/*
* We assume here that keys other than shift keys and bucky keys have
* entries in the "up" table that cause nothing to be done, and thus we
* don't have to check for newstate == RELEASED.
*/
switch (entrytype) {
case 0x0: /* regular key */
switch (kbdd->kbdd_translate) {
case TR_EVENT:
fe.id = entry | k->k_buckybits;
fe.value = 1;
kbdkeypressed(kbdd, key, &fe, entry);
break;
case TR_ASCII:
kbdputcode(entry | k->k_buckybits, q);
break;
}
break;
case SHIFTKEYS >> 8: {
uint_t shiftbit = 1 << (entry & 0x0F);
/* Modify toggle state (see toggle processing above) */
if (shiftbit & k->k_curkeyboard->k_toggleshifts) {
if (newstate == RELEASED) {
if (shiftbit == CAPSMASK) {
kbdd->led_state &= ~LED_CAPS_LOCK;
kbdsetled(kbdd);
} else if (shiftbit == NUMLOCKMASK) {
kbdd->led_state &= ~LED_NUM_LOCK;
kbdsetled(kbdd);
}
k->k_togglemask &= ~shiftbit;
} else {
if (shiftbit == CAPSMASK) {
kbdd->led_state |= LED_CAPS_LOCK;
kbdsetled(kbdd);
} else if (shiftbit == NUMLOCKMASK) {
kbdd->led_state |= LED_NUM_LOCK;
kbdsetled(kbdd);
}
k->k_togglemask |= shiftbit;
}
}
if (newstate == RELEASED)
k->k_shiftmask &= ~shiftbit;
else
k->k_shiftmask |= shiftbit;
if (kbdd->kbdd_translate == TR_EVENT && newstate == PRESSED) {
/*
* Relying on ordinal correspondence between
* vuid_event.h SHIFT_CAPSLOCK-SHIFT_RIGHTCTRL &
* kbd.h CAPSLOCK-RIGHTCTRL in order to
* correctly translate entry into fe.id.
*/
fe.id = SHIFT_CAPSLOCK + (entry & 0x0F);
fe.value = 1;
kbdkeypressed(kbdd, key, &fe, fe.id);
}
break;
}
case BUCKYBITS >> 8:
k->k_buckybits ^= 1 << (7 + (entry & 0x0F));
if (kbdd->kbdd_translate == TR_EVENT && newstate == PRESSED) {
/*
* Relying on ordinal correspondence between
* vuid_event.h SHIFT_META-SHIFT_TOP &
* kbd.h METABIT-SYSTEMBIT in order to
* correctly translate entry into fe.id.
*/
fe.id = SHIFT_META + (entry & 0x0F);
fe.value = 1;
kbdkeypressed(kbdd, key, &fe, fe.id);
}
break;
case FUNNY >> 8:
switch (entry) {
case NOP:
break;
case IDLE:
/* Fall thru into RESET code */
/* FALLTHRU */
case RESET:
gotreset:
k->k_shiftmask &= k->k_curkeyboard->k_idleshifts;
k->k_shiftmask |= k->k_togglemask;
k->k_buckybits &= k->k_curkeyboard->k_idlebuckys;
kbdcancelrpt(kbdd);
kbdreleaseall(kbdd);
break;
case ERROR:
cmn_err(CE_WARN, "kbd: Error detected");
goto gotreset;
case COMPOSE:
k->k_state = COMPOSE1;
kbdd->led_state |= LED_COMPOSE;
kbdsetled(kbdd);
break;
/*
* Remember when adding new entries that,
* if they should NOT auto-repeat,
* they should be put into the IF statement
* just above this switch block.
*/
default:
goto badentry;
}
break;
case FA_CLASS >> 8:
if (k->k_state == NORMAL) {
kbdd->fltaccent_entry = entry;
k->k_state = FLTACCENT;
}
return;
case STRING >> 8:
cp = &keystringtab[entry & 0x0F][0];
while (*cp != '\0') {
switch (kbdd->kbdd_translate) {
case TR_EVENT:
kbd_send_esc_event(*cp, kbdd);
break;
case TR_ASCII:
kbdputcode((uchar_t)*cp, q);
break;
}
cp++;
}
break;
case FUNCKEYS >> 8:
switch (kbdd->kbdd_translate) {
case TR_ASCII:
bufp = buf;
cp = strsetwithdecimal(bufp + 2,
(uint_t)((entry & 0x003F) + 192),
sizeof (buf) - 5);
*bufp++ = '\033'; /* Escape */
*bufp++ = '[';
while (*cp != '\0')
*bufp++ = *cp++;
*bufp++ = 'z';
*bufp = '\0';
kbdputbuf(buf, q);
break;
case TR_EVENT:
/*
* Take advantage of the similar
* ordering of kbd.h function keys and
* vuid_event.h function keys to do a
* simple translation to achieve a
* mapping between the 2 different
* address spaces.
*/
fe.id = (entry & 0x003F) + KEY_LEFTFIRST;
fe.value = 1;
/*
* Assume "up" table only generates
* shift changes.
*/
kbdkeypressed(kbdd, key, &fe, fe.id);
/*
* Function key events can be expanded
* by terminal emulator software to
* produce the standard escape sequence
* generated by the TR_ASCII case above
* if a function key event is not used
* by terminal emulator software
* directly.
*/
break;
}
break;
/*
* Remember when adding new entries that,
* if they should NOT auto-repeat,
* they should be put into the IF statement
* just above this switch block.
*/
case PADKEYS >> 8:
switch (kbdd->kbdd_translate) {
case TR_ASCII:
kbdputcode(kb_numlock_table[entry&0x1F], q);
break;
case TR_EVENT:
/*
* Take advantage of the similar
* ordering of kbd.h keypad keys and
* vuid_event.h keypad keys to do a
* simple translation to achieve a
* mapping between the 2 different
* address spaces.
*/
fe.id = (entry & 0x001F) + VKEY_FIRSTPAD;
fe.value = 1;
/*
* Assume "up" table only generates
* shift changes.
*/
kbdkeypressed(kbdd, key, &fe, fe.id);
/*
* Keypad key events can be expanded
* by terminal emulator software to
* produce the standard ascii character
* generated by the TR_ASCII case above
* if a keypad key event is not used
* by terminal emulator software
* directly.
*/
break;
}
badentry:
break;
}
}
static int
kbd_do_compose(ushort_t first_entry, ushort_t second_entry,
ushort_t *result_iso_ptr)
{
struct compose_sequence_t *ptr;
ptr = &kb_compose_table[kb_compose_map[first_entry]];
while (ptr->first == first_entry) {
if (ptr->second == second_entry) {
*result_iso_ptr = ptr->iso;
return (1);
}
ptr++;
}
return (0);
}
static void
kbd_send_esc_event(char c, register struct kbddata *kbdd)
{
Firm_event fe;
fe.id = c;
fe.value = 1;
fe.pair_type = FE_PAIR_NONE;
fe.pair = 0;
/*
* Pretend as if each cp pushed and released
* Calling kbdqueueevent avoids addr translation
* and pair base determination of kbdkeypressed.
*/
kbdqueueevent(kbdd, &fe);
fe.value = 0;
kbdqueueevent(kbdd, &fe);
}
char *
strsetwithdecimal(char *buf, uint_t val, uint_t maxdigs)
{
int hradix = 5;
char *bp;
int lowbit;
char *tab = "0123456789abcdef";
bp = buf + maxdigs;
*(--bp) = '\0';
while (val) {
lowbit = val & 1;
val = (val >> 1);
*(--bp) = tab[val % hradix * 2 + lowbit];
val /= hradix;
}
return (bp);
}
static void
kbdkeypressed(struct kbddata *kbdd, uchar_t key_station, Firm_event *fe,
ushort_t base)
{
register struct keyboardstate *k;
register short id_addr;
/* Set pair values */
if (fe->id < (ushort_t)VKEY_FIRST) {
/*
* If CTRLed, find the ID that would have been used had it
* not been CTRLed.
*/
k = &kbdd->kbdd_state;
if (k->k_shiftmask & (CTRLMASK | CTLSMASK)) {
struct keymap *km;
km = settable(kbdd,
k->k_shiftmask & ~(CTRLMASK | CTLSMASK | UPMASK));
if (km == NULL)
return;
base = km->keymap[key_station];
}
if (base != fe->id) {
fe->pair_type = FE_PAIR_SET;
fe->pair = base;
goto send;
}
}
fe->pair_type = FE_PAIR_NONE;
fe->pair = 0;
send:
/* Adjust event id address for multiple keyboard/workstation support */
switch (vuid_id_addr(fe->id)) {
case ASCII_FIRST:
id_addr = kbdd->kbdd_ascii_addr;
break;
case TOP_FIRST:
id_addr = kbdd->kbdd_top_addr;
break;
case VKEY_FIRST:
id_addr = kbdd->kbdd_vkey_addr;
break;
default:
id_addr = vuid_id_addr(fe->id);
}
fe->id = vuid_id_offset(fe->id) | id_addr;
kbdqueuepress(kbdd, key_station, fe);
}
static void
kbdqueuepress(struct kbddata *kbdd, uchar_t key_station, Firm_event *fe)
{
register struct key_event *ke, *ke_free;
register int i;
if (key_station == IDLEKEY)
return;
#ifdef KBD_DEBUG
if (kbd_input_debug) printf("KBD PRESSED key=%d\n", key_station);
#endif
ke_free = 0;
/* Scan table of down key stations */
if (kbdd->kbdd_translate == TR_EVENT ||
kbdd->kbdd_translate == TR_UNTRANS_EVENT) {
for (i = 0, ke = kbdd->kbdd_downs;
i < kbdd->kbdd_downs_entries;
i++, ke++) {
/* Keycode already down? */
if (ke->key_station == key_station) {
#ifdef KBD_DEBUG
printf("kbd: Double entry in downs table (%d,%d)!\n", key_station, i);
#endif
goto add_event;
}
if (ke->key_station == 0)
ke_free = ke;
}
if (ke_free) {
ke = ke_free;
goto add_event;
}
cmn_err(CE_WARN, "kbd: Too many keys down!");
ke = kbdd->kbdd_downs;
}
add_event:
ke->key_station = key_station;
ke->event = *fe;
kbdqueueevent(kbdd, fe);
}
static void
kbdkeyreleased(register struct kbddata *kbdd, uchar_t key_station)
{
register struct key_event *ke;
register int i;
if (key_station == IDLEKEY)
return;
#ifdef KBD_DEBUG
if (kbd_input_debug)
printf("KBD RELEASE key=%d\n", key_station);
#endif
if (kbdd->kbdd_translate != TR_EVENT &&
kbdd->kbdd_translate != TR_UNTRANS_EVENT)
return;
/* Scan table of down key stations */
for (i = 0, ke = kbdd->kbdd_downs;
i < kbdd->kbdd_downs_entries;
i++, ke++) {
/* Found? */
if (ke->key_station == key_station) {
ke->key_station = 0;
ke->event.value = 0;
kbdqueueevent(kbdd, &ke->event);
}
}
/*
* Ignore if couldn't find because may be called twice
* for the same key station in the case of the kbdrpt
* routine being called unnecessarily.
*/
}
static void
kbdreleaseall(struct kbddata *kbdd)
{
register struct key_event *ke;
register int i;
#ifdef KBD_DEBUG
if (kbd_debug && kbd_ra_debug) printf("KBD RELEASE ALL\n");
#endif
/* Scan table of down key stations */
for (i = 0, ke = kbdd->kbdd_downs;
i < kbdd->kbdd_downs_entries; i++, ke++) {
/* Key station not zero */
if (ke->key_station)
kbdkeyreleased(kbdd, ke->key_station);
/* kbdkeyreleased resets kbdd_downs entry */
}
}
/*
* Pass a keycode up the stream, if you can, otherwise throw it away.
*/
static void
kbdputcode(uint_t code, queue_t *q)
{
register mblk_t *bp;
if (!canput(q))
cmn_err(CE_WARN, "kbdputcode: Can't put block for keycode");
else {
if ((bp = allocb(sizeof (uint_t), BPRI_HI)) == NULL)
cmn_err(CE_WARN,
"kbdputcode: Can't allocate block for keycode");
else {
*bp->b_wptr++ = code;
putnext(q, bp);
}
}
}
/*
* Pass generated keycode sequence to upstream, if possible.
*/
static void
kbdputbuf(char *buf, queue_t *q)
{
register mblk_t *bp;
if (!canput(q))
cmn_err(CE_WARN, "kbdputbuf: Can't put block for keycode");
else {
if ((bp = allocb((int)strlen(buf), BPRI_HI)) == NULL)
cmn_err(CE_WARN,
"kbdputbuf: Can't allocate block for keycode");
else {
while (*buf) {
*bp->b_wptr++ = *buf;
buf++;
}
putnext(q, bp);
}
}
}
/*
* Pass a VUID "firm event" up the stream, if you can.
*/
static void
kbdqueueevent(struct kbddata *kbdd, Firm_event *fe)
{
register queue_t *q;
register mblk_t *bp;
if ((q = kbdd->kbdd_readq) == NULL)
return;
if (!canput(q)) {
if (kbd_overflow_msg)
cmn_err(CE_WARN,
"kbd: Buffer flushed when overflowed");
kbdflush(kbdd);
kbd_overflow_cnt++;
} else {
if ((bp = allocb(sizeof (Firm_event), BPRI_HI)) == NULL)
cmn_err(CE_WARN,
"kbdqueueevent: Can't allocate block for event");
else {
#if 1 /* XX64 */
struct timeval now;
/*
* XX64: This is something of a compromise. It
* seems justifiable based on the usage of these
* timestamps as an ordering relation as opposed
* to a strict timing thing.
*
* But should we restore Firm_event's time stamp
* to be a timeval, and send 32-bit and 64-bit
* events up the pipe?
*/
uniqtime(&now);
TIMEVAL_TO_TIMEVAL32(&fe->time, &now);
#else
uniqtime(&fe->time);
#endif
*(Firm_event *)bp->b_wptr = *fe;
bp->b_wptr += sizeof (Firm_event);
putnext(q, bp);
}
}
}