usbkbm.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* USB keyboard input streams module - processes USB keypacket
* received from HID driver below to either ASCII or event
* format for windowing system.
*/
#include <sys/usb/usba/usbai_version.h>
#define KEYMAP_SIZE_VARIABLE
#include <sys/usb/usba.h>
#include <sys/usb/clients/hid/hid.h>
#include <sys/usb/clients/hid/hid_polled.h>
#include <sys/usb/clients/hidparser/hidparser.h>
#include <sys/stropts.h>
#include <sys/stream.h>
#include <sys/strsun.h>
#include <sys/kbio.h>
#include <sys/vuid_event.h>
#include <sys/kbd.h>
#include <sys/consdev.h>
#include <sys/kbtrans.h>
#include <sys/usb/clients/usbkbm/usbkbm.h>
#include <sys/beep.h>
#include <sys/policy.h>
/* debugging information */
static uint_t usbkbm_errmask = (uint_t)PRINT_MASK_ALL;
static uint_t usbkbm_errlevel = USB_LOG_L2;
static usb_log_handle_t usbkbm_log_handle;
typedef void (*process_key_callback_t)(usbkbm_state_t *, int, enum keystate);
/*
* Internal Function Prototypes
*/
static void usbkbm_streams_setled(struct kbtrans_hardware *, int);
static void usbkbm_polled_setled(struct kbtrans_hardware *, int);
static boolean_t usbkbm_polled_keycheck(struct kbtrans_hardware *,
int *, enum keystate *);
static void usbkbm_poll_callback(usbkbm_state_t *, int, enum keystate);
static void usbkbm_streams_callback(usbkbm_state_t *, int, enum keystate);
static void usbkbm_unpack_usb_packet(usbkbm_state_t *, process_key_callback_t,
uchar_t *, int);
static boolean_t usbkbm_is_modkey(uchar_t);
static void usbkbm_reioctl(void *);
static int usbkbm_polled_getchar(struct cons_polledio_arg *);
static boolean_t usbkbm_polled_ischar(struct cons_polledio_arg *);
static void usbkbm_polled_enter(struct cons_polledio_arg *);
static void usbkbm_polled_exit(struct cons_polledio_arg *);
static void usbkbm_mctl_receive(queue_t *, mblk_t *);
static enum kbtrans_message_response usbkbm_ioctl(queue_t *, mblk_t *);
static int usbkbm_kioccmd(usbkbm_state_t *, mblk_t *, char, size_t *);
static void usbkbm_usb2pc_xlate(usbkbm_state_t *, int, enum keystate);
static void usbkbm_wrap_kbtrans(usbkbm_state_t *, int, enum keystate);
static int usbkbm_set_protocol(usbkbm_state_t *, uint16_t);
/* stream qinit functions defined here */
static int usbkbm_open(queue_t *, dev_t *, int, int, cred_t *);
static int usbkbm_close(queue_t *, int, cred_t *);
static void usbkbm_wput(queue_t *, mblk_t *);
static void usbkbm_rput(queue_t *, mblk_t *);
static ushort_t usbkbm_get_state(usbkbm_state_t *);
static void usbkbm_get_scancode(usbkbm_state_t *, int *, enum keystate *);
static struct keyboard *usbkbm_keyindex;
/* External Functions */
extern void space_free(char *);
extern uintptr_t space_fetch(char *);
extern int space_store(char *, uintptr_t);
extern struct keyboard *kbtrans_usbkb_maptab_init(void);
extern void kbtrans_usbkb_maptab_fini(struct keyboard **);
extern keymap_entry_t kbtrans_keycode_usb2pc(int);
/*
* Structure to setup callbacks
*/
struct kbtrans_callbacks kbd_usb_callbacks = {
usbkbm_streams_setled,
usbkbm_polled_setled,
usbkbm_polled_keycheck,
};
/*
* Global Variables
*/
/* This variable saves the LED state across hotplugging. */
static uchar_t usbkbm_led_state = 0;
/* This variable saves the layout state */
static uchar_t usbkbm_layout = 0;
/*
* Function pointer array for mapping of scancodes.
*/
void (*usbkbm_xlate[2])(usbkbm_state_t *, int, enum keystate) = {
usbkbm_wrap_kbtrans,
usbkbm_usb2pc_xlate
};
static struct streamtab usbkbm_info;
static struct fmodsw fsw = {
"usbkbm",
&usbkbm_info,
D_MP | D_MTPERMOD
};
/*
* Module linkage information for the kernel.
*/
static struct modlstrmod modlstrmod = {
&mod_strmodops,
"USB keyboard streams %I%",
&fsw
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlstrmod,
NULL
};
int
_init(void)
{
int rval = mod_install(&modlinkage);
usbkbm_save_state_t *sp;
if (rval != 0) {
return (rval);
}
usbkbm_keyindex = kbtrans_usbkb_maptab_init();
usbkbm_log_handle = usb_alloc_log_hdl(NULL, "usbkbm",
&usbkbm_errlevel, &usbkbm_errmask, NULL, 0);
sp = (usbkbm_save_state_t *)space_fetch("SUNW,usbkbm_state");
if (sp == NULL) {
return (0);
}
/* Restore LED information */
usbkbm_led_state = sp->usbkbm_save_led;
/* Restore the Layout */
usbkbm_layout = sp->usbkbm_layout;
/* Restore abort information */
usbkbm_keyindex->k_abort1 =
sp->usbkbm_save_keyindex.k_abort1;
usbkbm_keyindex->k_abort2 =
sp->usbkbm_save_keyindex.k_abort2;
/* Restore keytables */
bcopy(sp->usbkbm_save_keyindex.k_normal,
usbkbm_keyindex->k_normal, USB_KEYTABLE_SIZE);
bcopy(sp->usbkbm_save_keyindex.k_shifted,
usbkbm_keyindex->k_shifted, USB_KEYTABLE_SIZE);
bcopy(sp->usbkbm_save_keyindex.k_caps,
usbkbm_keyindex->k_caps, USB_KEYTABLE_SIZE);
bcopy(sp->usbkbm_save_keyindex.k_altgraph,
usbkbm_keyindex->k_altgraph, USB_KEYTABLE_SIZE);
bcopy(sp->usbkbm_save_keyindex.k_numlock,
usbkbm_keyindex->k_numlock, USB_KEYTABLE_SIZE);
bcopy(sp->usbkbm_save_keyindex.k_control,
usbkbm_keyindex->k_control, USB_KEYTABLE_SIZE);
bcopy(sp->usbkbm_save_keyindex.k_up,
usbkbm_keyindex->k_up, USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_normal,
USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_shifted,
USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_caps,
USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_altgraph,
USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_numlock,
USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_control,
USB_KEYTABLE_SIZE);
kmem_free(sp->usbkbm_save_keyindex.k_up,
USB_KEYTABLE_SIZE);
kmem_free(sp, sizeof (usbkbm_save_state_t));
space_free("SUNW,usbkbm_state");
return (0);
}
int
_fini(void)
{
usbkbm_save_state_t *sp;
int sval;
int rval;
sp = kmem_alloc(sizeof (usbkbm_save_state_t), KM_SLEEP);
sval = space_store("SUNW,usbkbm_state", (uintptr_t)sp);
/*
* If it's not possible to store the state, return
* EBUSY.
*/
if (sval != 0) {
kmem_free(sp, sizeof (usbkbm_save_state_t));
return (EBUSY);
}
rval = mod_remove(&modlinkage);
if (rval != 0) {
kmem_free(sp, sizeof (usbkbm_save_state_t));
space_free("SUNW,usbkbm_state");
return (rval);
}
usb_free_log_hdl(usbkbm_log_handle);
/* Save the LED state */
sp->usbkbm_save_led = usbkbm_led_state;
/* Save the layout */
sp->usbkbm_layout = usbkbm_layout;
/*
* Save entries of the keyboard structure that
* have changed.
*/
sp->usbkbm_save_keyindex.k_abort1 = usbkbm_keyindex->k_abort1;
sp->usbkbm_save_keyindex.k_abort2 = usbkbm_keyindex->k_abort2;
/* Allocate space for keytables to be stored */
sp->usbkbm_save_keyindex.k_normal =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
sp->usbkbm_save_keyindex.k_shifted =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
sp->usbkbm_save_keyindex.k_caps =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
sp->usbkbm_save_keyindex.k_altgraph =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
sp->usbkbm_save_keyindex.k_numlock =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
sp->usbkbm_save_keyindex.k_control =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
sp->usbkbm_save_keyindex.k_up =
kmem_alloc(USB_KEYTABLE_SIZE, KM_SLEEP);
/* Copy over the keytables */
bcopy(usbkbm_keyindex->k_normal,
sp->usbkbm_save_keyindex.k_normal, USB_KEYTABLE_SIZE);
bcopy(usbkbm_keyindex->k_shifted,
sp->usbkbm_save_keyindex.k_shifted, USB_KEYTABLE_SIZE);
bcopy(usbkbm_keyindex->k_caps,
sp->usbkbm_save_keyindex.k_caps, USB_KEYTABLE_SIZE);
bcopy(usbkbm_keyindex->k_altgraph,
sp->usbkbm_save_keyindex.k_altgraph, USB_KEYTABLE_SIZE);
bcopy(usbkbm_keyindex->k_numlock,
sp->usbkbm_save_keyindex.k_numlock, USB_KEYTABLE_SIZE);
bcopy(usbkbm_keyindex->k_control,
sp->usbkbm_save_keyindex.k_control, USB_KEYTABLE_SIZE);
bcopy(usbkbm_keyindex->k_up,
sp->usbkbm_save_keyindex.k_up, USB_KEYTABLE_SIZE);
kbtrans_usbkb_maptab_fini(&usbkbm_keyindex);
return (0);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* Module qinit functions
*/
static struct module_info usbkbm_minfo = {
0, /* module id number */
"usbkbm", /* module name */
0, /* min packet size accepted */
INFPSZ, /* max packet size accepted */
2048, /* hi-water mark */
128 /* lo-water mark */
};
/* read side for key data and ioctl replies */
static struct qinit usbkbm_rinit = {
(int (*)())usbkbm_rput,
(int (*)())NULL, /* service not used */
usbkbm_open,
usbkbm_close,
(int (*)())NULL,
&usbkbm_minfo
};
/* write side for ioctls */
static struct qinit usbkbm_winit = {
(int (*)())usbkbm_wput,
(int (*)())NULL,
usbkbm_open,
usbkbm_close,
(int (*)())NULL,
&usbkbm_minfo
};
static struct streamtab usbkbm_info = {
&usbkbm_rinit,
&usbkbm_winit,
NULL, /* for muxes */
NULL, /* for muxes */
};
/*
* usbkbm_open :
* Open a keyboard
*/
/* ARGSUSED */
static int
usbkbm_open(queue_t *q, dev_t *devp, int oflag, int sflag, cred_t *crp)
{
usbkbm_state_t *usbkbmd;
struct iocblk mctlmsg;
uint32_t packet_size;
mblk_t *mctl_ptr;
int error, ret;
packet_size = 0;
if (q->q_ptr) {
USB_DPRINTF_L3(PRINT_MASK_OPEN, usbkbm_log_handle,
"usbkbm_open already opened");
return (0); /* already opened */
}
/*
* Only allow open requests to succeed for privileged users. This
* necessary to prevent users from pushing the "usbkbm" module again
* on the stream associated with /dev/kbd.
*/
if (secpolicy_console(crp) != 0)
return (EPERM);
switch (sflag) {
case MODOPEN:
break;
case CLONEOPEN:
USB_DPRINTF_L3(PRINT_MASK_OPEN, usbkbm_log_handle,
"usbkbm_open: Clone open not supported");
/* FALLTHRU */
default:
return (EINVAL);
}
/* allocate usb keyboard state structure */
usbkbmd = kmem_zalloc(sizeof (usbkbm_state_t), KM_SLEEP);
USB_DPRINTF_L3(PRINT_MASK_OPEN, usbkbm_log_handle,
"usbkbm_state= %p", (void *)usbkbmd);
/*
* Set up private data.
*/
usbkbmd->usbkbm_readq = q;
usbkbmd->usbkbm_writeq = WR(q);
usbkbmd->usbkbm_vkbd_type = KB_USB;
/*
* Set up queue pointers, so that the "put" procedure will accept
* the reply to the "ioctl" message we send down.
*/
q->q_ptr = (caddr_t)usbkbmd;
WR(q)->q_ptr = (caddr_t)usbkbmd;
error = kbtrans_streams_init(q, sflag, crp,
(struct kbtrans_hardware *)usbkbmd, &kbd_usb_callbacks,
&usbkbmd->usbkbm_kbtrans, usbkbm_led_state, 0);
if (error != 0) {
USB_DPRINTF_L3(PRINT_MASK_OPEN, usbkbm_log_handle,
"kbdopen: kbtrans_streams_init failed\n");
kmem_free(usbkbmd, sizeof (*usbkbmd));
return (error);
}
/*
* Set the polled information in the state structure.
* This information is set once, and doesn't change
*/
usbkbmd->usbkbm_polled_info.cons_polledio_version =
CONSPOLLEDIO_V1;
usbkbmd->usbkbm_polled_info.cons_polledio_argument =
(struct cons_polledio_arg *)usbkbmd;
usbkbmd->usbkbm_polled_info.cons_polledio_putchar = NULL;
usbkbmd->usbkbm_polled_info.cons_polledio_getchar =
usbkbm_polled_getchar;
usbkbmd->usbkbm_polled_info.cons_polledio_ischar =
usbkbm_polled_ischar;
usbkbmd->usbkbm_polled_info.cons_polledio_enter =
usbkbm_polled_enter;
usbkbmd->usbkbm_polled_info.cons_polledio_exit =
usbkbm_polled_exit;
usbkbmd->usbkbm_polled_info.cons_polledio_setled =
(void (*)(struct cons_polledio_arg *, int))usbkbm_polled_setled;
usbkbmd->usbkbm_polled_info.cons_polledio_keycheck =
(boolean_t (*)(struct cons_polledio_arg *, int *,
enum keystate *))usbkbm_polled_keycheck;
/*
* The head and the tail pointing at the same byte means empty or
* full. usbkbm_polled_buffer_num_characters is used to
* tell the difference.
*/
usbkbmd->usbkbm_polled_buffer_head =
usbkbmd->usbkbm_polled_scancode_buffer;
usbkbmd->usbkbm_polled_buffer_tail =
usbkbmd->usbkbm_polled_scancode_buffer;
usbkbmd->usbkbm_polled_buffer_num_characters = 0;
qprocson(q);
if (ret = usbkbm_set_protocol(usbkbmd, SET_BOOT_PROTOCOL)) {
return (ret);
}
/* request hid report descriptor from HID */
mctlmsg.ioc_cmd = HID_GET_PARSER_HANDLE;
mctlmsg.ioc_count = 0;
mctl_ptr = usba_mk_mctl(mctlmsg, NULL, 0);
if (mctl_ptr == NULL) {
/* failure to allocate M_CTL message */
(void) kbtrans_streams_fini(usbkbmd->usbkbm_kbtrans);
qprocsoff(q);
kmem_free(usbkbmd, sizeof (*usbkbmd));
return (ENOMEM);
}
/* send message to hid */
putnext(usbkbmd->usbkbm_writeq, mctl_ptr);
/*
* Now that M_CTL has been sent, wait for report descriptor. Cleanup
* if user signals in the mean time (as when this gets opened in an
* inappropriate context and the user types a ^C).
*/
usbkbmd->usbkbm_flags |= USBKBM_QWAIT;
while (usbkbmd->usbkbm_flags & USBKBM_QWAIT) {
if (qwait_sig(q) == 0) {
usbkbmd->usbkbm_flags = 0;
(void) kbtrans_streams_fini(usbkbmd->usbkbm_kbtrans);
qprocsoff(q);
kmem_free(usbkbmd, sizeof (*usbkbmd));
return (EINTR);
}
}
/*
* In case of non-self identifying keyboards the country code
* has value 0. In this case we will set the layout to default US
* if the usbkbm_layout is 0.
*/
if (usbkbmd->usbkbm_report_descr != NULL) {
if ((hidparser_get_country_code(usbkbmd->usbkbm_report_descr,
(uint16_t *)&usbkbmd->usbkbm_layout) ==
HIDPARSER_FAILURE) || (usbkbmd->usbkbm_layout == 0)) {
if (!usbkbm_layout) {
USB_DPRINTF_L3(PRINT_MASK_OPEN,
usbkbm_log_handle, "get_country_code failed"
"setting default layout(0x21)");
/* Setting to default layout = US */
usbkbmd->usbkbm_layout = 0x21;
} else
usbkbmd->usbkbm_layout = usbkbm_layout;
}
if (hidparser_get_packet_size(usbkbmd->usbkbm_report_descr,
0, HIDPARSER_ITEM_INPUT, (uint32_t *)&packet_size) ==
HIDPARSER_FAILURE) {
USB_DPRINTF_L3(PRINT_MASK_OPEN,
usbkbm_log_handle, "get_packet_size failed"
"setting default packet size(8)");
/* Setting to default packet size = 8 */
usbkbmd->usbkbm_packet_size =
USB_KBD_DEFAULT_PACKET_SIZE;
} else {
usbkbmd->usbkbm_packet_size = packet_size/8;
}
} else {
USB_DPRINTF_L3(PRINT_MASK_OPEN, usbkbm_log_handle,
"usbkbm: Invalid HID Descriptor Tree."
"setting default layout(0x21) & packet_size(8)");
if (!usbkbm_layout) {
/* Setting to default = US */
usbkbmd->usbkbm_layout = 0x21;
} else
usbkbmd->usbkbm_layout = usbkbm_layout;
usbkbmd->usbkbm_packet_size =
USB_KBD_DEFAULT_PACKET_SIZE;
}
kbtrans_streams_set_keyboard(usbkbmd->usbkbm_kbtrans, KB_USB,
usbkbm_keyindex);
usbkbmd->usbkbm_flags = USBKBM_OPEN;
kbtrans_streams_enable(usbkbmd->usbkbm_kbtrans);
USB_DPRINTF_L3(PRINT_MASK_OPEN, usbkbm_log_handle,
"usbkbm_open exiting");
return (0);
}
/*
* usbkbm_close :
* Close a keyboard.
*/
/* ARGSUSED1 */
static int
usbkbm_close(register queue_t *q, int flag, cred_t *crp)
{
usbkbm_state_t *usbkbmd = (usbkbm_state_t *)q->q_ptr;
/* If a beep is in progress, stop that */
beeper_off();
(void) kbtrans_streams_fini(usbkbmd->usbkbm_kbtrans);
qprocsoff(q);
/*
* 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.
*/
usbkbmd->usbkbm_flags = 0;
kmem_free(usbkbmd, sizeof (usbkbm_state_t));
USB_DPRINTF_L3(PRINT_MASK_CLOSE, usbkbm_log_handle,
"usbkbm_close exiting");
return (0);
}
/*
* usbkbm_wput :
* usb keyboard module output queue put procedure: handles M_IOCTL
* messages.
*/
static void
usbkbm_wput(register queue_t *q, register mblk_t *mp)
{
usbkbm_state_t *usbkbmd;
enum kbtrans_message_response ret;
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_wput entering");
usbkbmd = (usbkbm_state_t *)q->q_ptr;
/* First, see if kbtrans will handle the message */
ret = kbtrans_streams_message(usbkbmd->usbkbm_kbtrans, mp);
if (ret == KBTRANS_MESSAGE_HANDLED) {
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_wput exiting:2");
return;
}
/* kbtrans didn't handle the message. Try to handle it here */
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);
}
break;
case M_IOCTL:
ret = usbkbm_ioctl(q, mp);
if (ret == KBTRANS_MESSAGE_HANDLED) {
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_wput exiting:1");
return;
}
default:
break;
}
/*
* The message has not been handled
* by kbtrans or this module. Pass it down the stream
*/
putnext(q, mp);
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_wput exiting:3");
}
/*
* usbkbm_ioctl :
* Handles the ioctls sent from upper module. Returns
* ACK/NACK back.
*/
static enum kbtrans_message_response
usbkbm_ioctl(register queue_t *q, register mblk_t *mp)
{
usbkbm_state_t *usbkbmd;
struct iocblk mctlmsg;
struct iocblk *iocp;
mblk_t *datap, *mctl_ptr;
size_t ioctlrespsize;
int err;
int tmp;
char command;
err = 0;
usbkbmd = (usbkbm_state_t *)q->q_ptr;
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
case CONSSETKBDTYPE:
err = miocpullup(mp, sizeof (int));
if (err != 0) {
break;
}
tmp = *(int *)mp->b_cont->b_rptr;
if (tmp != KB_PC && tmp != KB_USB) {
err = EINVAL;
break;
}
usbkbmd->usbkbm_vkbd_type = tmp;
break;
case KIOCLAYOUT:
datap = allocb(sizeof (int), BPRI_HI);
if (datap == NULL) {
ioctlrespsize = sizeof (int);
goto allocfailure;
}
*(int *)datap->b_wptr = usbkbmd->usbkbm_layout;
datap->b_wptr += sizeof (int);
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KIOCSLAYOUT:
/*
* Supply a layout if not specified by the hardware, or
* override any that was specified.
*/
if (iocp->ioc_count != TRANSPARENT) {
err = EINVAL;
break;
}
usbkbmd->usbkbm_layout = *(intptr_t *)mp->b_cont->b_rptr;
/*
* Save the layout in usbkbm_layout so as to handle the
* the case when the user has re-plugged in the non-self
* identifying non US keyboard. In this the layout is saved
* in global variable, so the user does not have to run
* kdmconfig again after the X server reset
*/
usbkbm_layout = usbkbmd->usbkbm_layout;
break;
case KIOCCMD:
/*
* Check if we have at least the subcommand field; any
* other argument validation has to occur inside
* usbkbm_kioccmd().
*/
err = miocpullup(mp, sizeof (int));
if (err != 0)
break;
/* Subcommand */
command = (char)(*(int *)mp->b_cont->b_rptr);
/*
* Check if this ioctl is followed by a previous
* KBD_CMD_SETLED command, in which case we take
* the command byte as the data for setting the LED
*/
if (usbkbmd->usbkbm_setled_second_byte) {
usbkbm_streams_setled((struct kbtrans_hardware *)
usbkbmd, command);
usbkbmd->usbkbm_setled_second_byte = 0;
break;
}
/*
* In case of allocb failure, this will
* return the size of the allocation which
* failed so that it can be allocated later
* through bufcall.
*/
ioctlrespsize = 0;
err = usbkbm_kioccmd(usbkbmd, mp, command, &ioctlrespsize);
if (ioctlrespsize != 0) {
goto allocfailure;
}
break;
case CONSOPENPOLLEDIO:
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_ioctl CONSOPENPOLLEDIO");
err = miocpullup(mp, sizeof (struct cons_polledio *));
if (err != 0) {
USB_DPRINTF_L2(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_ioctl: malformed request");
break;
}
usbkbmd->usbkbm_pending_link = mp;
/*
* Get the polled input structure from hid
*/
mctlmsg.ioc_cmd = HID_OPEN_POLLED_INPUT;
mctlmsg.ioc_count = 0;
mctl_ptr = usba_mk_mctl(mctlmsg, NULL, 0);
if (mctl_ptr == NULL) {
ioctlrespsize = sizeof (mctlmsg);
goto allocfailure;
}
putnext(usbkbmd->usbkbm_writeq, mctl_ptr);
/*
* Do not ack or nack the message, we will wait for the
* result of HID_OPEN_POLLED_INPUT
*/
return (KBTRANS_MESSAGE_HANDLED);
case CONSCLOSEPOLLEDIO:
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_ioctl CONSCLOSEPOLLEDIO mp = 0x%p", (void *)mp);
usbkbmd->usbkbm_pending_link = mp;
/*
* Get the polled input structure from hid
*/
mctlmsg.ioc_cmd = HID_CLOSE_POLLED_INPUT;
mctlmsg.ioc_count = 0;
mctl_ptr = usba_mk_mctl(mctlmsg, NULL, 0);
if (mctl_ptr == NULL) {
ioctlrespsize = sizeof (mctlmsg);
goto allocfailure;
}
putnext(usbkbmd->usbkbm_writeq, mctl_ptr);
/*
* Do not ack or nack the message, we will wait for the
* result of HID_CLOSE_POLLED_INPUT
*/
return (KBTRANS_MESSAGE_HANDLED);
case CONSSETABORTENABLE:
/*
* Nothing special to do for USB.
*/
break;
default:
return (KBTRANS_MESSAGE_NOT_HANDLED);
}
/*
* Send ACK/NACK to upper module for
* the messages that have been handled.
*/
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;
}
/* Send the response back up the stream */
putnext(usbkbmd->usbkbm_readq, mp);
return (KBTRANS_MESSAGE_HANDLED);
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.
*/
freemsg(usbkbmd->usbkbm_streams_iocpending);
usbkbmd->usbkbm_streams_iocpending = mp;
if (usbkbmd->usbkbm_streams_bufcallid) {
qunbufcall(usbkbmd->usbkbm_readq,
usbkbmd->usbkbm_streams_bufcallid);
}
usbkbmd->usbkbm_streams_bufcallid =
qbufcall(usbkbmd->usbkbm_readq, ioctlrespsize, BPRI_HI,
usbkbm_reioctl, usbkbmd);
return (KBTRANS_MESSAGE_HANDLED);
}
/*
* usbkbm_kioccmd :
* Handles KIOCCMD ioctl.
*/
static int
usbkbm_kioccmd(usbkbm_state_t *usbkbmd, register mblk_t *mp,
char command, size_t *ioctlrepsize)
{
register mblk_t *datap;
register struct iocblk *iocp;
int err = 0;
iocp = (struct iocblk *)mp->b_rptr;
switch (command) {
/* Keyboard layout command */
case KBD_CMD_GETLAYOUT:
/* layout learned at attached time. */
datap = allocb(sizeof (int), BPRI_HI);
/* Return error on allocation failure */
if (datap == NULL) {
*ioctlrepsize = sizeof (int);
return (EIO);
}
*(int *)datap->b_wptr = usbkbmd->usbkbm_layout;
datap->b_wptr += sizeof (int);
freemsg(mp->b_cont);
mp->b_cont = datap;
iocp->ioc_count = sizeof (int);
break;
case KBD_CMD_SETLED:
/*
* Emulate type 4 keyboard :
* Ignore this ioctl; the following
* ioctl will specify the data byte for
* setting the LEDs; setting usbkbm_setled_second_byte
* will help recognizing that ioctl
*/
usbkbmd->usbkbm_setled_second_byte = 1;
break;
case KBD_CMD_RESET:
break;
case KBD_CMD_BELL:
/*
* USB keyboards do not have a beeper
* in it, the generic beeper interface
* is used. Turn the beeper on.
*/
beeper_on(BEEP_TYPE4);
break;
case KBD_CMD_NOBELL:
/*
* USB keyboards do not have a beeper
* in it, the generic beeper interface
* is used. Turn the beeper off.
*/
beeper_off();
break;
case KBD_CMD_CLICK:
/* FALLTHRU */
case KBD_CMD_NOCLICK:
break;
default:
err = EIO;
break;
}
return (err);
}
/*
* usbkbm_rput :
* Put procedure for input from driver end of stream (read queue).
*/
static void
usbkbm_rput(register queue_t *q, register mblk_t *mp)
{
usbkbm_state_t *usbkbmd;
usbkbmd = (usbkbm_state_t *)q->q_ptr;
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_rput");
if (usbkbmd == 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);
freemsg(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_DATA:
if (!(usbkbmd->usbkbm_flags & USBKBM_OPEN)) {
freemsg(mp); /* not ready to listen */
return;
}
break;
case M_CTL:
usbkbm_mctl_receive(q, mp);
return;
case M_ERROR:
usbkbmd->usbkbm_flags &= ~USBKBM_QWAIT;
freemsg(mp);
return;
case M_IOCACK:
case M_IOCNAK:
putnext(q, mp);
return;
default:
putnext(q, mp);
return;
}
/*
* A data message, consisting of bytes from the keyboard.
* Ram them through the translator, only if there are
* correct no. of bytes.
*/
if ((mp->b_wptr - mp->b_rptr) == usbkbmd->usbkbm_packet_size) {
usbkbm_unpack_usb_packet(usbkbmd, usbkbm_streams_callback,
(uchar_t *)mp->b_rptr, usbkbmd->usbkbm_packet_size);
}
freemsg(mp);
}
/*
* usbkbm_mctl_receive :
* Handle M_CTL messages from hid. If we don't understand
* the command, send it up.
*/
static void
usbkbm_mctl_receive(register queue_t *q, register mblk_t *mp)
{
register usbkbm_state_t *usbkbmd = (usbkbm_state_t *)q->q_ptr;
register struct iocblk *iocp, mctlmsg;
caddr_t data = NULL;
mblk_t *reply_mp, *mctl_ptr;
uchar_t new_buffer[USBKBM_MAXPKTSIZE];
size_t size;
hid_req_t buf;
size_t len = sizeof (buf);
iocp = (struct iocblk *)mp->b_rptr;
if (mp->b_cont != NULL)
data = (caddr_t)mp->b_cont->b_rptr;
switch (iocp->ioc_cmd) {
case HID_SET_REPORT:
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive HID_SET mctl");
freemsg(mp);
/* Setting of the LED is not waiting for this message */
break;
case HID_SET_PROTOCOL:
freemsg(mp);
usbkbmd->usbkbm_flags &= ~USBKBM_QWAIT;
break;
case HID_GET_PARSER_HANDLE:
if ((data != NULL) &&
(iocp->ioc_count == sizeof (hidparser_handle_t)) &&
((mp->b_cont->b_wptr - mp->b_cont->b_rptr) ==
iocp->ioc_count)) {
usbkbmd->usbkbm_report_descr =
*(hidparser_handle_t *)data;
} else {
usbkbmd->usbkbm_report_descr = NULL;
}
freemsg(mp);
usbkbmd->usbkbm_flags &= ~USBKBM_QWAIT;
break;
case HID_OPEN_POLLED_INPUT:
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive HID_OPEN_POLLED_INPUT");
size = sizeof (hid_polled_input_callback_t);
reply_mp = usbkbmd->usbkbm_pending_link;
if ((data != NULL) &&
(iocp->ioc_count == size) &&
((mp->b_cont->b_wptr - mp->b_cont->b_rptr) == size)) {
/*
* Copy the information from hid into the
* state structure
*/
bcopy(data, &usbkbmd->usbkbm_hid_callback, size);
reply_mp->b_datap->db_type = M_IOCACK;
/*
* We are given an appropriate-sized data block,
* and return a pointer to our structure in it.
* The structure is saved in the states structure
*/
*(cons_polledio_t **)reply_mp->b_cont->b_rptr =
&usbkbmd->usbkbm_polled_info;
} else {
reply_mp->b_datap->db_type = M_IOCNAK;
}
freemsg(mp);
usbkbmd->usbkbm_pending_link = NULL;
putnext(q, reply_mp);
break;
case HID_CLOSE_POLLED_INPUT:
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive HID_CLOSE_POLLED_INPUT");
bzero(&usbkbmd->usbkbm_hid_callback,
sizeof (hid_polled_input_callback_t));
freemsg(mp);
reply_mp = usbkbmd->usbkbm_pending_link;
iocp = (struct iocblk *)reply_mp->b_rptr;
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive reply reply_mp 0x%p cmd 0x%x",
(void *)reply_mp, iocp->ioc_cmd);
reply_mp->b_datap->db_type = M_IOCACK;
usbkbmd->usbkbm_pending_link = NULL;
putnext(q, reply_mp);
break;
case HID_DISCONNECT_EVENT :
case HID_POWER_OFF:
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive HID_DISCONNECT_EVENT/HID_POWER_OFF");
/* Indicate all keys have been released */
bzero(new_buffer, USBKBM_MAXPKTSIZE);
usbkbm_unpack_usb_packet(usbkbmd, usbkbm_streams_callback,
new_buffer, usbkbmd->usbkbm_packet_size);
freemsg(mp);
break;
case HID_CONNECT_EVENT:
mctlmsg.ioc_cmd = HID_SET_PROTOCOL;
mctlmsg.ioc_count = 0;
buf.hid_req_version_no = HID_VERSION_V_0;
buf.hid_req_wValue = SET_BOOT_PROTOCOL;
buf.hid_req_wLength = 0;
buf.hid_req_data = NULL;
mctl_ptr = usba_mk_mctl(mctlmsg, &buf, len);
if (mctl_ptr == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive HID_CONNECT_EVENT: "
"Set protocol failed");
} else {
putnext(usbkbmd->usbkbm_writeq, mctl_ptr);
}
/* FALLTHRU */
case HID_FULL_POWER :
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm_mctl_receive restore LEDs");
/* send setled command down to restore LED states */
usbkbm_streams_setled((struct kbtrans_hardware *)usbkbmd,
usbkbm_led_state);
freemsg(mp);
break;
default:
putnext(q, mp);
break;
}
}
/*
* usbkbm_streams_setled :
* Update the keyboard LEDs to match the current keyboard state.
* Send LED state downstreams to hid driver.
*/
static void
usbkbm_streams_setled(struct kbtrans_hardware *kbtrans_hw, int state)
{
struct iocblk mctlmsg;
mblk_t *LED_message, *mctl_ptr;
hid_req_t *LED_report;
usbkbm_state_t *usbkbmd;
uchar_t led_state;
usbkbm_led_state = (uchar_t)state;
usbkbmd = (usbkbm_state_t *)kbtrans_hw;
LED_report = kmem_zalloc(sizeof (hid_req_t), KM_NOSLEEP);
if (LED_report == NULL) {
return;
}
/*
* Send the request to the hid driver to set LED.
*/
/* Create an mblk_t */
LED_message = allocb(sizeof (uchar_t), BPRI_HI);
if (LED_message == NULL) {
kmem_free(LED_report, sizeof (hid_req_t));
return;
}
led_state = 0;
/*
* Set the led state based on the state that is passed in.
*/
if (state & LED_NUM_LOCK) {
led_state |= USB_LED_NUM_LOCK;
}
if (state & LED_COMPOSE) {
led_state |= USB_LED_COMPOSE;
}
if (state & LED_SCROLL_LOCK) {
led_state |= USB_LED_SCROLL_LOCK;
}
if (state & LED_CAPS_LOCK) {
led_state |= USB_LED_CAPS_LOCK;
}
if (state & LED_KANA) {
led_state |= USB_LED_KANA;
}
bcopy((void *)&led_state, (void *)LED_message->b_wptr, 1);
LED_message->b_wptr = LED_message->b_wptr + 1;
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"usbkbm: Send Ctrl Request. Data is 0x%x",
(uchar_t)*LED_message->b_rptr);
LED_report->hid_req_version_no = HID_VERSION_V_0;
LED_report->hid_req_wValue = REPORT_TYPE_OUTPUT;
LED_report->hid_req_wLength = sizeof (uchar_t);
LED_report->hid_req_data = LED_message;
mctlmsg.ioc_cmd = HID_SET_REPORT;
mctlmsg.ioc_count = sizeof (LED_report);
mctl_ptr = usba_mk_mctl(mctlmsg, LED_report, sizeof (hid_req_t));
if (mctl_ptr != NULL) {
putnext(usbkbmd->usbkbm_writeq, mctl_ptr);
} else {
freemsg(LED_message);
}
/*
* We are not waiting for response of HID_SET_REPORT
* mctl for setting the LED.
*/
kmem_free(LED_report, sizeof (hid_req_t));
}
/*
* usbkbm_polled_keycheck :
* This routine is called to determine if there is a scancode that
* is available for input. This routine is called at poll time and
* returns a key/state pair to the caller. If there are characters
* buffered up, the routine returns right away with the key/state pair.
* Otherwise, the routine calls down to check for characters and returns
* the first key/state pair if there are any characters pending.
*/
static boolean_t
usbkbm_polled_keycheck(struct kbtrans_hardware *hw,
int *key, enum keystate *state)
{
usbkbm_state_t *usbkbmd;
uchar_t *buffer;
unsigned num_keys;
hid_polled_handle_t hid_polled_handle;
usbkbmd = (usbkbm_state_t *)hw;
/*
* If there are already characters buffered up, then we are done.
*/
if (usbkbmd->usbkbm_polled_buffer_num_characters != 0) {
usbkbm_get_scancode(usbkbmd, key, state);
return (B_TRUE);
}
hid_polled_handle =
usbkbmd->usbkbm_hid_callback.hid_polled_input_handle;
num_keys = (usbkbmd->usbkbm_hid_callback.hid_polled_read)
(hid_polled_handle, &buffer);
/*
* If we don't get any characters back then indicate that, and we
* are done.
*/
if (num_keys == 0) {
return (B_FALSE);
}
/*
* We have a usb packet, so pass this packet to
* usbkbm_unpack_usb_packet so that it can be broken up into
* individual key/state values.
*/
usbkbm_unpack_usb_packet(usbkbmd, usbkbm_poll_callback,
buffer, num_keys);
/*
* If a scancode was returned as a result of this packet,
* then translate the scancode.
*/
if (usbkbmd->usbkbm_polled_buffer_num_characters != 0) {
usbkbm_get_scancode(usbkbmd, key, state);
return (B_TRUE);
}
return (B_FALSE);
}
static ushort_t usbkbm_get_state(usbkbm_state_t *usbkbmd)
{
ushort_t ret;
ASSERT(usbkbmd->usbkbm_vkbd_type == KB_PC ||
usbkbmd->usbkbm_vkbd_type == KB_USB);
if (usbkbmd->usbkbm_vkbd_type == KB_PC)
ret = INDEXTO_PC;
else
ret = INDEXTO_USB;
return (ret);
}
/*
* usbkbm_streams_callback :
* This is the routine that is going to be called when unpacking
* usb packets for normal streams-based input. We pass a pointer
* to this routine to usbkbm_unpack_usb_packet. This routine will
* get called with an unpacked key (scancode) and state (press/release).
* We pass it to the generic keyboard module.
*
* 'index' and the function pointers:
* Map USB scancodes to PC scancodes by lookup table.
* This fix is mainly meant for x86 platforms. For SPARC systems
* this fix doesn't change the way in which the scancodes are processed.
*/
static void
usbkbm_streams_callback(usbkbm_state_t *usbkbmd, int key, enum keystate state)
{
ushort_t index = usbkbm_get_state(usbkbmd);
(*usbkbm_xlate[index])(usbkbmd, key, state);
}
/*
* Don't do any translations. Send to 'kbtrans' for processing.
*/
static void
usbkbm_wrap_kbtrans(usbkbm_state_t *usbkbmd, int key, enum keystate state)
{
kbtrans_streams_key(usbkbmd->usbkbm_kbtrans, key, state);
}
/*
* Translate USB scancodes to PC scancodes before sending it to 'kbtrans'
*/
void
usbkbm_usb2pc_xlate(usbkbm_state_t *usbkbmd, int key, enum keystate state)
{
key = kbtrans_keycode_usb2pc(key);
kbtrans_streams_key(usbkbmd->usbkbm_kbtrans, key, state);
}
/*
* usbkbm_poll_callback :
* This is the routine that is going to be called when unpacking
* usb packets for polled input. We pass a pointer to this routine
* to usbkbm_unpack_usb_packet. This routine will get called with
* an unpacked key (scancode) and state (press/release). We will
* store the key/state pair into a circular buffer so that it can
* be translated into an ascii key later.
*/
static void
usbkbm_poll_callback(usbkbm_state_t *usbkbmd, int key, enum keystate state)
{
/*
* Check to make sure that the buffer isn't already full
*/
if (usbkbmd->usbkbm_polled_buffer_num_characters ==
USB_POLLED_BUFFER_SIZE) {
/*
* The buffer is full, we will drop this character.
*/
return;
}
/*
* Save the scancode in the buffer
*/
usbkbmd->usbkbm_polled_buffer_head->poll_key = key;
usbkbmd->usbkbm_polled_buffer_head->poll_state = state;
/*
* We have one more character in the buffer
*/
usbkbmd->usbkbm_polled_buffer_num_characters++;
/*
* Increment to the next available slot.
*/
usbkbmd->usbkbm_polled_buffer_head++;
/*
* Check to see if the tail has wrapped.
*/
if (usbkbmd->usbkbm_polled_buffer_head -
usbkbmd->usbkbm_polled_scancode_buffer ==
USB_POLLED_BUFFER_SIZE) {
usbkbmd->usbkbm_polled_buffer_head =
usbkbmd->usbkbm_polled_scancode_buffer;
}
}
/*
* usbkbm_get_scancode :
* This routine retreives a key/state pair from the circular buffer.
* The pair was put in the buffer by usbkbm_poll_callback when a
* USB packet was translated into a key/state by usbkbm_unpack_usb_packet.
*/
static void
usbkbm_get_scancode(usbkbm_state_t *usbkbmd, int *key, enum keystate *state)
{
/*
* Copy the character.
*/
*key = usbkbmd->usbkbm_polled_buffer_tail->poll_key;
*state = usbkbmd->usbkbm_polled_buffer_tail->poll_state;
/*
* Increment to the next character to be copied from
* and to.
*/
usbkbmd->usbkbm_polled_buffer_tail++;
/*
* Check to see if the tail has wrapped.
*/
if (usbkbmd->usbkbm_polled_buffer_tail -
usbkbmd->usbkbm_polled_scancode_buffer ==
USB_POLLED_BUFFER_SIZE) {
usbkbmd->usbkbm_polled_buffer_tail =
usbkbmd->usbkbm_polled_scancode_buffer;
}
/*
* We have one less character in the buffer.
*/
usbkbmd->usbkbm_polled_buffer_num_characters--;
}
/*
* usbkbm_polled_setled :
* This routine is a place holder. Someday, we may want to allow led
* state to be updated from within polled mode.
*/
/* ARGSUSED */
static void
usbkbm_polled_setled(struct kbtrans_hardware *hw, int led_state)
{
/* nothing to do for now */
}
/*
* This is a pass-thru routine to get a character at poll time.
*/
static int
usbkbm_polled_getchar(struct cons_polledio_arg *arg)
{
usbkbm_state_t *usbkbmd;
usbkbmd = (usbkbm_state_t *)arg;
return (kbtrans_getchar(usbkbmd->usbkbm_kbtrans));
}
/*
* This is a pass-thru routine to test if character is available for reading
* at poll time.
*/
static boolean_t
usbkbm_polled_ischar(struct cons_polledio_arg *arg)
{
usbkbm_state_t *usbkbmd;
usbkbmd = (usbkbm_state_t *)arg;
return (kbtrans_ischar(usbkbmd->usbkbm_kbtrans));
}
/*
* usbkbm_polled_input_enter :
* This is a pass-thru initialization routine for the lower layer drivers.
* This routine is called at poll time to set the state for polled input.
*/
static void
usbkbm_polled_enter(struct cons_polledio_arg *arg)
{
usbkbm_state_t *usbkbmd;
hid_polled_handle_t hid_polled_handle;
uint_t uindex;
usbkbmd = (usbkbm_state_t *)arg;
/*
* Before switching to POLLED mode, copy the contents of
* usbkbm_pendingusbpacket to usbkbm_lastusbpacket since
* usbkbm_pendingusbpacket field has currently processed
* key events of the current OS mode usb keyboard packet.
*/
for (uindex = 2; uindex < USBKBM_MAXPKTSIZE; uindex ++) {
usbkbmd->usbkbm_lastusbpacket[uindex] =
usbkbmd->usbkbm_pendingusbpacket[uindex];
usbkbmd->usbkbm_pendingusbpacket[uindex] = 0;
}
hid_polled_handle =
usbkbmd->usbkbm_hid_callback.hid_polled_input_handle;
(void) (usbkbmd->usbkbm_hid_callback.hid_polled_input_enter)
(hid_polled_handle);
}
/*
* usbkbm_polled_input_exit :
* This is a pass-thru restoration routine for the lower layer drivers.
* This routine is called at poll time to reset the state back to streams
* input.
*/
static void
usbkbm_polled_exit(struct cons_polledio_arg *arg)
{
usbkbm_state_t *usbkbmd;
hid_polled_handle_t hid_polled_handle;
uint_t uindex;
usbkbmd = (usbkbm_state_t *)arg;
/*
* Before returning to OS mode, copy the contents of
* usbkbm_lastusbpacket to usbkbm_pendingusbpacket since
* usbkbm_lastusbpacket field has processed key events
* of the last POLLED mode usb keyboard packet.
*/
for (uindex = 2; uindex < USBKBM_MAXPKTSIZE; uindex ++) {
usbkbmd->usbkbm_pendingusbpacket[uindex] =
usbkbmd->usbkbm_lastusbpacket[uindex];
usbkbmd->usbkbm_lastusbpacket[uindex] = 0;
}
hid_polled_handle =
usbkbmd->usbkbm_hid_callback.hid_polled_input_handle;
(void) (usbkbmd->usbkbm_hid_callback.hid_polled_input_exit)
(hid_polled_handle);
}
/*
* usbkbm_unpack_usb_packet :
* USB key packets contain 8 bytes while in boot protocol mode.
* The first byte contains bit packed modifier key information.
* Second byte is reserved. The last 6 bytes contain bytes of
* currently pressed keys. If a key was not recorded on the
* previous packet, but present in the current packet, then set
* state to KEY_PRESSED. If a key was recorded in the previous packet,
* but not present in the current packet, then state to KEY_RELEASED
* Follow a similar algorithm for bit packed modifier keys.
*/
static void
usbkbm_unpack_usb_packet(usbkbm_state_t *usbkbmd, process_key_callback_t func,
uchar_t *usbpacket, int packet_size)
{
uchar_t mkb;
uchar_t lastmkb;
uchar_t *lastusbpacket = usbkbmd->usbkbm_lastusbpacket;
int uindex, lindex, rollover;
mkb = usbpacket[0];
lastmkb = lastusbpacket[0];
for (uindex = 0; uindex < packet_size; uindex++) {
USB_DPRINTF_L3(PRINT_MASK_PACKET, usbkbm_log_handle,
" %x ", usbpacket[uindex]);
}
USB_DPRINTF_L3(PRINT_MASK_PACKET, usbkbm_log_handle,
" is the usbkeypacket");
/* check to see if modifier keys are different */
if (mkb != lastmkb) {
if ((mkb & USB_LSHIFTBIT) != (lastmkb & USB_LSHIFTBIT)) {
(*func)(usbkbmd, USB_LSHIFTKEY, (mkb&USB_LSHIFTBIT) ?
KEY_PRESSED : KEY_RELEASED);
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"unpack: sending USB_LSHIFTKEY");
}
if ((mkb & USB_LCTLBIT) != (lastmkb & USB_LCTLBIT)) {
(*func)(usbkbmd, USB_LCTLCKEY, mkb&USB_LCTLBIT ?
KEY_PRESSED : KEY_RELEASED);
}
if ((mkb & USB_LALTBIT) != (lastmkb & USB_LALTBIT)) {
(*func)(usbkbmd, USB_LALTKEY, mkb&USB_LALTBIT ?
KEY_PRESSED : KEY_RELEASED);
}
if ((mkb & USB_LMETABIT) != (lastmkb & USB_LMETABIT)) {
(*func)(usbkbmd, USB_LMETAKEY, mkb&USB_LMETABIT ?
KEY_PRESSED : KEY_RELEASED);
}
if ((mkb & USB_RMETABIT) != (lastmkb & USB_RMETABIT)) {
(*func)(usbkbmd, USB_RMETAKEY, mkb&USB_RMETABIT ?
KEY_PRESSED : KEY_RELEASED);
}
if ((mkb & USB_RALTBIT) != (lastmkb & USB_RALTBIT)) {
(*func)(usbkbmd, USB_RALTKEY, mkb&USB_RALTBIT ?
KEY_PRESSED : KEY_RELEASED);
}
if ((mkb & USB_RCTLBIT) != (lastmkb & USB_RCTLBIT)) {
(*func)(usbkbmd, USB_RCTLCKEY, mkb&USB_RCTLBIT ?
KEY_PRESSED : KEY_RELEASED);
}
if ((mkb & USB_RSHIFTBIT) != (lastmkb & USB_RSHIFTBIT)) {
(*func)(usbkbmd, USB_RSHIFTKEY, mkb&USB_RSHIFTBIT ?
KEY_PRESSED : KEY_RELEASED);
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"unpack: sending USB_RSHIFTKEY");
}
}
/* save modifier bits */
lastusbpacket[0] = usbpacket[0];
/* Check Keyboard rollover error. */
if (usbpacket[2] == USB_ERRORROLLOVER) {
rollover = 1;
for (uindex = 3; uindex < packet_size;
uindex++) {
if (usbpacket[uindex] != USB_ERRORROLLOVER) {
rollover = 0;
break;
}
}
if (rollover) {
USB_DPRINTF_L3(PRINT_MASK_ALL, usbkbm_log_handle,
"unpack: errorrollover");
return;
}
}
/* check for released keys */
for (lindex = 2; lindex < packet_size; lindex++) {
int released = 1;
if (lastusbpacket[lindex] == 0) {
continue;
}
for (uindex = 2; uindex < packet_size; uindex++)
if (usbpacket[uindex] == lastusbpacket[lindex]) {
released = 0;
break;
}
if (released) {
(*func)(usbkbmd, lastusbpacket[lindex], KEY_RELEASED);
}
}
/* check for new presses */
for (uindex = 2; uindex < packet_size; uindex++) {
int newkey = 1;
usbkbmd->usbkbm_pendingusbpacket[uindex] = usbpacket[uindex];
if (usbpacket[uindex] == 0) {
continue;
}
for (lindex = 2; lindex < packet_size; lindex++) {
if (usbpacket[uindex] == lastusbpacket[lindex]) {
newkey = 0;
break;
}
}
if (newkey) {
/*
* Modifier keys can be present as part of both the
* first byte and as separate key bytes. In the sec-
* ond case ignore it.
*/
if (!usbkbm_is_modkey(usbpacket[uindex])) {
(*func)(usbkbmd, usbpacket[uindex],
KEY_PRESSED);
} else {
usbkbmd->usbkbm_pendingusbpacket[uindex] = 0;
continue;
}
}
}
/*
* Copy the processed key events of the current usb keyboard
* packet, which is saved in the usbkbm_pendingusbpacket field
* to the usbkbm_lastusbpacket field.
*/
for (uindex = 2; uindex < USBKBM_MAXPKTSIZE; uindex++) {
lastusbpacket[uindex] =
usbkbmd->usbkbm_pendingusbpacket[uindex];
usbkbmd->usbkbm_pendingusbpacket[uindex] = 0;
}
}
static boolean_t
usbkbm_is_modkey(uchar_t key)
{
switch (key) {
case USB_LSHIFTKEY:
case USB_LCTLCKEY:
case USB_LALTKEY:
case USB_LMETAKEY:
case USB_RCTLCKEY:
case USB_RSHIFTKEY:
case USB_RMETAKEY:
case USB_RALTKEY:
return (B_TRUE);
default:
break;
}
return (B_FALSE);
}
/*
* usbkbm_reioctl :
* This function is set up as call-back function should an ioctl fail.
* It retries the ioctl
*/
static void
usbkbm_reioctl(void *arg)
{
usbkbm_state_t *usbkbmd;
mblk_t *mp;
usbkbmd = (usbkbm_state_t *)arg;
usbkbmd->usbkbm_streams_bufcallid = 0;
if ((mp = usbkbmd->usbkbm_streams_iocpending) != NULL) {
/* not pending any more */
usbkbmd->usbkbm_streams_iocpending = NULL;
(void) usbkbm_ioctl(usbkbmd->usbkbm_writeq, mp);
}
}
/*
* usbkbm_set_protocol
* Issue an M_CTL to hid to set the desired protocol
*/
static int
usbkbm_set_protocol(usbkbm_state_t *usbkbmd, uint16_t protocol)
{
struct iocblk mctlmsg;
hid_req_t buf;
mblk_t *mctl_ptr;
size_t len = sizeof (buf);
queue_t *q = usbkbmd->usbkbm_readq;
mctlmsg.ioc_cmd = HID_SET_PROTOCOL;
mctlmsg.ioc_count = 0;
buf.hid_req_version_no = HID_VERSION_V_0;
buf.hid_req_wValue = protocol;
buf.hid_req_wLength = 0;
buf.hid_req_data = NULL;
mctl_ptr = usba_mk_mctl(mctlmsg, &buf, len);
if (mctl_ptr == NULL) {
usbkbmd->usbkbm_flags = 0;
(void) kbtrans_streams_fini(usbkbmd->usbkbm_kbtrans);
qprocsoff(q);
kmem_free(usbkbmd, sizeof (usbkbm_state_t));
return (ENOMEM);
}
usbkbmd->usbkbm_flags |= USBKBM_QWAIT;
putnext(usbkbmd->usbkbm_writeq, mctl_ptr);
while (usbkbmd->usbkbm_flags & USBKBM_QWAIT) {
if (qwait_sig(q) == 0) {
usbkbmd->usbkbm_flags = 0;
(void) kbtrans_streams_fini(usbkbmd->usbkbm_kbtrans);
qprocsoff(q);
kmem_free(usbkbmd, sizeof (usbkbm_state_t));
return (EINTR);
}
}
return (0);
}