usbkbm.c revision ac9468f8dd2abd7271cca0d9e1b15831d367cf9f
/*
* 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
* 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 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* USB keyboard input streams module - processes USB keypacket
* received from HID driver below to either ASCII or event
* format for windowing system.
*/
#define KEYMAP_SIZE_VARIABLE
#include <sys/vuid_event.h>
#include <sys/inttypes.h>
/* debugging information */
static usb_log_handle_t usbkbm_log_handle;
/*
* Internal Function Prototypes
*/
static void usbkbm_streams_setled(struct kbtrans_hardware *, int);
static void usbkbm_polled_setled(struct kbtrans_hardware *, int);
int *, enum keystate *);
uchar_t *, int);
static void usbkbm_reioctl(void *);
static int usbkbm_polled_getchar(cons_polledio_arg_t);
static void usbkbm_polled_enter(cons_polledio_arg_t);
static void usbkbm_polled_exit(cons_polledio_arg_t);
static int usbkbm_get_protocol(usbkbm_state_t *);
static int usbkbm_get_vid_pid(usbkbm_state_t *);
/* stream qinit functions defined here */
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 = {
};
/*
* Global Variables
*/
/* This variable saves the LED state across hotplugging. */
static uchar_t usbkbm_led_state = 0;
/* This variable saves the layout state */
static uint16_t usbkbm_layout = 0;
/*
* Function pointer array for mapping of scancodes.
*/
};
static struct streamtab usbkbm_info;
"usbkbm",
};
/*
* Module linkage information for the kernel.
*/
static struct modlstrmod modlstrmod = {
"USB keyboard streams 1.44",
&fsw
};
static struct modlinkage modlinkage = {
(void *)&modlstrmod,
};
int
_init(void)
{
if (rval != 0) {
return (rval);
}
return (0);
}
/* Restore LED information */
/* Restore the Layout */
/* Restore abort information */
/* Restore keytables */
space_free("SUNW,usbkbm_state");
return (0);
}
int
_fini(void)
{
int sval;
int rval;
/*
* If it's not possible to store the state, return
* EBUSY.
*/
if (sval != 0) {
return (EBUSY);
}
if (rval != 0) {
space_free("SUNW,usbkbm_state");
return (rval);
}
/* Save the LED state */
/* Save the layout */
/*
* Save entries of the keyboard structure that
* have changed.
*/
/* Allocate space for keytables to be stored */
/* Copy over the keytables */
return (0);
}
int
{
}
/*
* 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 */
(int (*)())NULL,
};
/* write side for ioctls */
static struct qinit usbkbm_winit = {
(int (*)())usbkbm_wput,
(int (*)())NULL,
(int (*)())NULL,
};
static struct streamtab usbkbm_info = {
NULL, /* for muxes */
NULL, /* for muxes */
};
/*
* usbkbm_open :
* Open a keyboard
*/
/* ARGSUSED */
static int
{
if (q->q_ptr) {
"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
*/
if (secpolicy_console(crp) != 0)
return (EPERM);
switch (sflag) {
case MODOPEN:
break;
case CLONEOPEN:
"usbkbm_open: Clone open not supported");
/* FALLTHRU */
default:
return (EINVAL);
}
/* allocate usb keyboard state structure */
"usbkbm_state= %p", (void *)usbkbmd);
/*
* Set up private data.
*/
usbkbmd->usbkbm_readq = q;
/*
* Set up queue pointers, so that the "put" procedure will accept
* the reply to the "ioctl" message we send down.
*/
if (error != 0) {
"kbdopen: kbtrans_streams_init failed\n");
return (error);
}
/*
* Set the polled information in the state structure.
* This information is set once, and doesn't change
*/
(void (*)(cons_polledio_arg_t, int))usbkbm_polled_setled;
(boolean_t (*)(cons_polledio_arg_t, 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.
*/
qprocson(q);
return (ret);
}
/*
* The current usbkbm only knows how to deal with boot-protocol mode,
* so switch into boot-protocol mode now.
*/
return (ret);
}
}
/*
* USB keyboards are expected to send well-defined 8-byte data
* packets in boot-protocol mode (the format of which is documented
* in the HID specification).
*
* Note: We do not look at the interface's HID report descriptors to
* derive the report size, because the HID report descriptor describes
* the format of each report in report mode. This format might be
* different from the format used in boot-protocol mode. The internal
* USB keyboard in a recent version of the Apple MacBook Pro is one
* example of a USB keyboard that uses different formats for
* boot-protocol-mode reports and report-mode reports.
*/
/* request hid report descriptor from HID */
/* failure to allocate M_CTL message */
qprocsoff(q);
return (ENOMEM);
}
/* send message to hid */
/*
* 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).
*/
if (qwait_sig(q) == 0) {
usbkbmd->usbkbm_flags = 0;
qprocsoff(q);
return (EINTR);
}
}
usbkbm_log_handle, "get_country_code failed"
"setting default layout(0)");
}
} else {
"usbkbm: Invalid HID Descriptor Tree."
"setting default layout(0)");
}
/*
* Although Sun Japanese type6 and type7 keyboards have the same
* layout number(15), they should be recognized for loading the
* different keytables on upper apps (e.g. X). The new layout
* number (271) is defined for the Sun Japanese type6 keyboards.
* The layout number (15) specified in HID spec is used for other
* Japanese keyboards. It is a workaround for the old Sun Japanese
* type6 keyboards defect.
*/
return (ret);
}
}
}
/*
* Enable abort sequence on inital. For an internal open, conskbd
* will disable driver abort handling (later through M_IOCTL) and
* handle it by itself.
* For an external (aka. physical) open, this is necessary since
* no STREAMS module linked on top of usbkbm handles abort sequence.
*/
!= KBTRANS_MESSAGE_HANDLED) {
}
} else {
"usbkbm: enable abort sequence failed");
}
"usbkbm_open exiting");
return (0);
}
/*
* usbkbm_close :
* Close a keyboard.
*/
/* ARGSUSED1 */
static int
{
/* If a beep is in progress, stop that */
(void) beeper_off();
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;
"usbkbm_close exiting");
return (0);
}
/*
* usbkbm_wput :
* usb keyboard module output queue put procedure: handles M_IOCTL
* messages.
*/
static void
{
enum kbtrans_message_response ret;
"usbkbm_wput entering");
/* First, see if kbtrans will handle the message */
if (ret == KBTRANS_MESSAGE_HANDLED) {
"usbkbm_wput exiting:2");
return;
}
/* kbtrans didn't handle the message. Try to handle it here */
case M_FLUSH:
}
}
break;
case M_IOCTL:
if (ret == KBTRANS_MESSAGE_HANDLED) {
"usbkbm_wput exiting:1");
return;
}
default:
break;
}
/*
* The message has not been handled
* by kbtrans or this module. Pass it down the stream
*/
"usbkbm_wput exiting:3");
}
/*
* usbkbm_ioctl :
* Handles the ioctls sent from upper module. Returns
*/
static enum kbtrans_message_response
{
int err;
int tmp;
int cycles;
int frequency;
int msecs;
char command;
err = 0;
case CONSSETKBDTYPE:
if (err != 0) {
break;
}
break;
}
break;
case KIOCLAYOUT:
ioctlrespsize = sizeof (int);
goto allocfailure;
}
break;
case KIOCSLAYOUT:
/*
* Supply a layout if not specified by the hardware, or
* override any that was specified.
*/
break;
}
/*
* 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
*/
break;
case KIOCCMD:
/*
* Check if we have at least the subcommand field; any
* other argument validation has to occur inside
* usbkbm_kioccmd().
*/
if (err != 0)
break;
/* Subcommand */
/*
* 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 *)
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;
if (ioctlrespsize != 0) {
goto allocfailure;
}
break;
case CONSOPENPOLLEDIO:
"usbkbm_ioctl CONSOPENPOLLEDIO");
if (err != 0) {
"usbkbm_ioctl: malformed request");
break;
}
/*
* Get the polled input structure from hid
*/
ioctlrespsize = sizeof (mctlmsg);
goto allocfailure;
}
/*
* Do not ack or nack the message, we will wait for the
* result of HID_OPEN_POLLED_INPUT
*/
return (KBTRANS_MESSAGE_HANDLED);
case CONSCLOSEPOLLEDIO:
"usbkbm_ioctl CONSCLOSEPOLLEDIO mp = 0x%p", (void *)mp);
/*
* Get the polled input structure from hid
*/
ioctlrespsize = sizeof (mctlmsg);
goto allocfailure;
}
/*
* 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;
case KIOCMKTONE:
break;
}
if (cycles == 0)
else if (cycles == UINT16_MAX)
frequency = 0;
else {
if (frequency > UINT16_MAX)
}
break;
default:
return (KBTRANS_MESSAGE_NOT_HANDLED);
}
/*
* the messages that have been handled.
*/
if (err != 0) {
} else {
}
/* Send the response back up the stream */
return (KBTRANS_MESSAGE_HANDLED);
/*
* 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 (usbkbmd->usbkbm_streams_bufcallid) {
}
return (KBTRANS_MESSAGE_HANDLED);
}
/*
* usbkbm_kioccmd :
* Handles KIOCCMD ioctl.
*/
static int
{
int err = 0;
switch (command) {
/* Keyboard layout command */
case KBD_CMD_GETLAYOUT:
/* layout learned at attached time. */
/* Return error on allocation failure */
*ioctlrepsize = sizeof (int);
return (EIO);
}
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
*/
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.
*/
(void) 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.
*/
(void) beeper_off();
break;
case KBD_CMD_CLICK:
/* FALLTHRU */
case KBD_CMD_NOCLICK:
break;
default:
break;
}
return (err);
}
/*
* usbkbm_rput :
* Put procedure for input from driver end of stream (read queue).
*/
static void
{
"usbkbm_rput");
if (usbkbmd == 0) {
return;
}
case M_FLUSH:
return;
case M_BREAK:
/*
* Will get M_BREAK only if this is not the system
* keyboard, otherwise serial port will eat break
*/
return;
case M_DATA:
return;
}
break;
case M_CTL:
usbkbm_mctl_receive(q, mp);
return;
case M_ERROR:
} else {
}
return;
case M_IOCACK:
case M_IOCNAK:
return;
default:
return;
}
/*
* A data message, consisting of bytes from the keyboard.
* Ram them through the translator, only if there are
* correct no. of bytes.
*/
}
}
/*
* usbkbm_mctl_receive :
* Handle M_CTL messages from hid. If we don't understand
* the command, send it up.
*/
static void
{
case HID_SET_REPORT:
"usbkbm_mctl_receive HID_SET mctl");
/* Setting of the LED is not waiting for this message */
break;
case HID_SET_PROTOCOL:
break;
case HID_GET_PARSER_HANDLE:
*(hidparser_handle_t *)data;
} else {
}
break;
case HID_GET_VID_PID:
}
break;
case HID_OPEN_POLLED_INPUT:
"usbkbm_mctl_receive HID_OPEN_POLLED_INPUT");
size = sizeof (hid_polled_input_callback_t);
/*
* Copy the information from hid into the
* state structure
*/
/*
* 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
*/
} else {
}
break;
case HID_CLOSE_POLLED_INPUT:
"usbkbm_mctl_receive HID_CLOSE_POLLED_INPUT");
sizeof (hid_polled_input_callback_t));
"usbkbm_mctl_receive reply reply_mp 0x%p cmd 0x%x",
break;
case HID_DISCONNECT_EVENT :
case HID_POWER_OFF:
"usbkbm_mctl_receive HID_DISCONNECT_EVENT/HID_POWER_OFF");
/* Indicate all keys have been released */
break;
case HID_CONNECT_EVENT:
case HID_FULL_POWER :
"usbkbm_mctl_receive restore LEDs");
/* send setled command down to restore LED states */
break;
case HID_GET_PROTOCOL:
}
break;
default:
break;
}
}
/*
* usbkbm_streams_setled :
* Update the keyboard LEDs to match the current keyboard state.
* Send LED state downstreams to hid driver.
*/
static void
{
if (LED_report == NULL) {
return;
}
/*
* Send the request to the hid driver to set LED.
*/
led_state = 0;
/*
* Set the led state based on the state that is passed in.
*/
if (state & LED_NUM_LOCK) {
}
if (state & LED_COMPOSE) {
}
if (state & LED_SCROLL_LOCK) {
}
if (state & LED_CAPS_LOCK) {
}
}
}
/*
* We are not waiting for response of HID_SET_REPORT
* mctl for setting the LED.
*/
}
/*
* 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
* Otherwise, the routine calls down to check for characters and returns
*/
static boolean_t
{
unsigned num_keys;
/*
* If there are already characters buffered up, then we are done.
*/
if (usbkbmd->usbkbm_polled_buffer_num_characters != 0) {
return (B_TRUE);
}
(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
*/
/*
* If a scancode was returned as a result of this packet,
* then translate the scancode.
*/
if (usbkbmd->usbkbm_polled_buffer_num_characters != 0) {
return (B_TRUE);
}
return (B_FALSE);
}
{
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
* 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
{
}
/*
* Don't do any translations. Send to 'kbtrans' for processing.
*/
static void
{
}
/*
* Translate USB scancodes to PC scancodes before sending it to 'kbtrans'
*/
void
{
}
/*
* 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
* be translated into an ascii key later.
*/
static void
{
/*
* Check to make sure that the buffer isn't already full
*/
/*
* The buffer is full, we will drop this character.
*/
return;
}
/*
* Save the scancode in the buffer
*/
/*
* We have one more character in the buffer
*/
/*
* Increment to the next available slot.
*/
/*
* Check to see if the tail has wrapped.
*/
if (usbkbmd->usbkbm_polled_buffer_head -
}
}
/*
* usbkbm_get_scancode :
* The pair was put in the buffer by usbkbm_poll_callback when a
*/
static void
{
/*
* Copy the character.
*/
/*
* Increment to the next character to be copied from
* and to.
*/
/*
* Check to see if the tail has wrapped.
*/
if (usbkbmd->usbkbm_polled_buffer_tail -
}
/*
* We have one less character in the buffer.
*/
}
/*
* 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
{
/* nothing to do for now */
}
/*
* This is a pass-thru routine to get a character at poll time.
*/
static int
{
}
/*
* This is a pass-thru routine to test if character is available for reading
* at poll time.
*/
static boolean_t
{
}
/*
* 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
{
/*
* 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.
*/
}
}
/*
* 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
{
/*
* 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.
*/
}
}
/*
* 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
{
lastmkb = lastusbpacket[0];
}
" is the usbkeypacket");
/* check to see if modifier keys are different */
"unpack: sending USB_LSHIFTKEY");
}
}
}
}
}
}
}
"unpack: sending USB_RSHIFTKEY");
}
}
/* save modifier bits */
lastusbpacket[0] = usbpacket[0];
/* Check Keyboard rollover error. */
rollover = 1;
uindex++) {
rollover = 0;
break;
}
}
if (rollover) {
"unpack: errorrollover");
return;
}
}
/* check for released keys */
int released = 1;
if (lastusbpacket[lindex] == 0) {
continue;
}
released = 0;
break;
}
if (released) {
}
}
/* check for new presses */
int newkey = 1;
continue;
}
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.
*/
} else {
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.
*/
}
}
static boolean_t
{
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)
{
/* not pending any more */
}
}
/*
* usbkbm_set_protocol
* Issue an M_CTL to hid to set the desired protocol
*/
static int
{
buf.hid_req_wLength = 0;
usbkbmd->usbkbm_flags = 0;
qprocsoff(q);
return (ENOMEM);
}
if (qwait_sig(q) == 0) {
usbkbmd->usbkbm_flags = 0;
qprocsoff(q);
return (EINTR);
}
}
return (0);
}
/*
* usbkbm_get_vid_pid
* Issue a M_CTL to hid to get the device info
*/
static int
{
qprocsoff(q);
return (ENOMEM);
}
if (qwait_sig(q) == 0) {
usbkbmd->usbkbm_flags = 0;
qprocsoff(q);
return (EINTR);
}
}
return (0);
}
/*
* usbkbm_get_protocol
* Issue a M_CTL to hid to get the device info
*/
static int
{
qprocsoff(q);
return (ENOMEM);
}
if (qwait_sig(q) == 0) {
usbkbmd->usbkbm_flags = 0;
qprocsoff(q);
return (EINTR);
}
}
return (0);
}