OpenSolaris_b135/uts/common/io/kbd.c

Compare this file to the similar file:
Show the results in this format:

/*
 * 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);
		}
	}
}