/*
* 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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Human Interface Device driver (HID)
*
* The HID driver is a software driver which acts as a class
* driver for USB human input devices like keyboard, mouse,
* joystick etc and provides the class-specific interfaces
* between these client driver modules and the Universal Serial
* Bus Driver(USBA).
*
* NOTE: This driver is not DDI compliant in that it uses undocumented
* functions for logging (USB_DPRINTF_L*, usb_alloc_log_hdl, usb_free_log_hdl).
*
* Undocumented functions may go away in a future Solaris OS release.
*
* Please see the DDK for sample code of these functions, and for the usbskel
* skeleton template driver which contains scaled-down versions of these
* functions written in a DDI-compliant way.
*/
#define USBDRV_MAJOR_VER 2
#define USBDRV_MINOR_VER 0
#include <sys/usb/usba.h>
#include <sys/usb/usba/genconsole.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/usb/clients/hid/hidvar.h>
#include <sys/usb/clients/hid/hidminor.h>
#include <sys/usb/clients/hidparser/hid_parser_driver.h>
#include <sys/stropts.h>
#include <sys/sunddi.h>
#include <sys/stream.h>
#include <sys/strsun.h>
extern int ddi_create_internal_pathname(dev_info_t *, char *, int, minor_t);
/* Debugging support */
uint_t hid_errmask = (uint_t)PRINT_MASK_ALL;
uint_t hid_errlevel = USB_LOG_L4;
uint_t hid_instance_debug = (uint_t)-1;
/* tunables */
int hid_default_pipe_drain_timeout = HID_DEFAULT_PIPE_DRAIN_TIMEOUT;
int hid_pm_mouse = 1; /* enable remote_wakeup for USB mouse/keyboard */
/* soft state structures */
#define HID_INITIAL_SOFT_SPACE 4
static void *hid_statep;
/* Callbacks */
static void hid_interrupt_pipe_callback(usb_pipe_handle_t,
usb_intr_req_t *);
static void hid_default_pipe_callback(usb_pipe_handle_t, usb_ctrl_req_t *);
static void hid_interrupt_pipe_exception_callback(usb_pipe_handle_t,
usb_intr_req_t *);
static void hid_default_pipe_exception_callback(usb_pipe_handle_t,
usb_ctrl_req_t *);
static int hid_restore_state_event_callback(dev_info_t *);
static int hid_disconnect_event_callback(dev_info_t *);
static int hid_cpr_suspend(hid_state_t *hidp);
static void hid_cpr_resume(hid_state_t *hidp);
static void hid_power_change_callback(void *arg, int rval);
/* Supporting routines */
static size_t hid_parse_hid_descr(usb_hid_descr_t *, size_t,
usb_alt_if_data_t *, usb_ep_data_t *);
static int hid_parse_hid_descr_failure(hid_state_t *);
static int hid_handle_report_descriptor(hid_state_t *, int);
static void hid_set_idle(hid_state_t *);
static void hid_set_protocol(hid_state_t *, int);
static void hid_detach_cleanup(dev_info_t *, hid_state_t *);
static int hid_start_intr_polling(hid_state_t *);
static void hid_close_intr_pipe(hid_state_t *);
static int hid_mctl_execute_cmd(queue_t *, int, hid_req_t *,
mblk_t *);
static int hid_mctl_receive(queue_t *, mblk_t *);
static int hid_send_async_ctrl_request(hid_default_pipe_arg_t *, hid_req_t *,
uchar_t, int, ushort_t);
static void hid_create_pm_components(dev_info_t *, hid_state_t *);
static int hid_is_pm_enabled(dev_info_t *);
static void hid_restore_device_state(dev_info_t *, hid_state_t *);
static void hid_save_device_state(hid_state_t *);
static void hid_qreply_merror(queue_t *, mblk_t *, uchar_t);
static mblk_t *hid_data2mblk(uchar_t *, int);
static void hid_flush(queue_t *);
static int hid_pwrlvl0(hid_state_t *);
static int hid_pwrlvl1(hid_state_t *);
static int hid_pwrlvl2(hid_state_t *);
static int hid_pwrlvl3(hid_state_t *);
static void hid_pm_busy_component(hid_state_t *);
static void hid_pm_idle_component(hid_state_t *);
static int hid_polled_read(hid_polled_handle_t, uchar_t **);
static int hid_polled_input_enter(hid_polled_handle_t);
static int hid_polled_input_exit(hid_polled_handle_t);
static int hid_polled_input_init(hid_state_t *);
static int hid_polled_input_fini(hid_state_t *);
/* Streams entry points */
static int hid_open(queue_t *, dev_t *, int, int, cred_t *);
static int hid_close(queue_t *, int, cred_t *);
static int hid_wput(queue_t *, mblk_t *);
static int hid_wsrv(queue_t *);
/* dev_ops entry points */
static int hid_info(dev_info_t *, ddi_info_cmd_t, void *, void **);
static int hid_attach(dev_info_t *, ddi_attach_cmd_t);
static int hid_detach(dev_info_t *, ddi_detach_cmd_t);
static int hid_power(dev_info_t *, int, int);
/*
* Warlock is not aware of the automatic locking mechanisms for
* streams drivers. The hid streams enter points are protected by
* a per module perimeter. If the locking in hid is a bottleneck
* per queue pair or per queue locking may be used. Since warlock
* is not aware of the streams perimeters, these notes have been added.
*
* Note that the perimeters do not protect the driver from callbacks
* happening while a streams entry point is executing. So, the hid_mutex
* has been created to protect the data.
*/
_NOTE(SCHEME_PROTECTS_DATA("unique per call", iocblk))
_NOTE(SCHEME_PROTECTS_DATA("unique per call", datab))
_NOTE(SCHEME_PROTECTS_DATA("unique per call", msgb))
_NOTE(SCHEME_PROTECTS_DATA("unique per call", queue))
_NOTE(SCHEME_PROTECTS_DATA("unique per call", usb_ctrl_req))
_NOTE(SCHEME_PROTECTS_DATA("unique per call", usb_intr_req))
/* module information */
static struct module_info hid_mod_info = {
0x0ffff, /* module id number */
"hid", /* module name */
0, /* min packet size accepted */
INFPSZ, /* max packet size accepted */
512, /* hi-water mark */
128 /* lo-water mark */
};
/* read queue information structure */
static struct qinit rinit = {
NULL, /* put procedure not needed */
NULL, /* service procedure not needed */
hid_open, /* called on startup */
hid_close, /* called on finish */
NULL, /* for future use */
&hid_mod_info, /* module information structure */
NULL /* module statistics structure */
};
/* write queue information structure */
static struct qinit winit = {
hid_wput, /* put procedure */
hid_wsrv, /* service procedure */
NULL, /* open not used on write side */
NULL, /* close not used on write side */
NULL, /* for future use */
&hid_mod_info, /* module information structure */
NULL /* module statistics structure */
};
struct streamtab hid_streamtab = {
&rinit,
&winit,
NULL, /* not a MUX */
NULL /* not a MUX */
};
struct cb_ops hid_cb_ops = {
nulldev, /* open */
nulldev, /* close */
nulldev, /* strategy */
nulldev, /* print */
nulldev, /* dump */
nulldev, /* read */
nulldev, /* write */
nulldev, /* ioctl */
nulldev, /* devmap */
nulldev, /* mmap */
nulldev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
&hid_streamtab, /* streamtab */
D_MP | D_MTPERQ
};
static struct dev_ops hid_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
hid_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
hid_attach, /* attach */
hid_detach, /* detach */
nodev, /* reset */
&hid_cb_ops, /* driver operations */
NULL, /* bus operations */
hid_power, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
static struct modldrv hidmodldrv = {
&mod_driverops,
"USB HID Client Driver",
&hid_ops /* driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1,
&hidmodldrv,
NULL,
};
static usb_event_t hid_events = {
hid_disconnect_event_callback,
hid_restore_state_event_callback,
NULL,
NULL,
};
int
_init(void)
{
int rval;
if (((rval = ddi_soft_state_init(&hid_statep, sizeof (hid_state_t),
HID_INITIAL_SOFT_SPACE)) != 0)) {
return (rval);
}
if ((rval = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&hid_statep);
}
return (rval);
}
int
_fini(void)
{
int rval;
if ((rval = mod_remove(&modlinkage)) != 0) {
return (rval);
}
ddi_soft_state_fini(&hid_statep);
return (rval);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
/*
* hid_info :
* Get minor number, soft state structure etc.
*/
/*ARGSUSED*/
static int
hid_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result)
{
hid_state_t *hidp = NULL;
int error = DDI_FAILURE;
minor_t minor = getminor((dev_t)arg);
int instance = HID_MINOR_TO_INSTANCE(minor);
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
if ((hidp = ddi_get_soft_state(hid_statep, instance)) != NULL) {
*result = hidp->hid_dip;
if (*result != NULL) {
error = DDI_SUCCESS;
}
} else
*result = NULL;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* hid_attach :
* Gets called at the time of attach. Do allocation,
* and initialization of the software structure.
* Get all the descriptors, setup the
* report descriptor tree by calling hidparser
* function.
*/
static int
hid_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
int parse_hid_descr_error = 0;
hid_state_t *hidp = NULL;
uint32_t usage_page;
uint32_t usage;
usb_client_dev_data_t *dev_data;
usb_alt_if_data_t *altif_data;
char minor_name[HID_MINOR_NAME_LEN];
usb_ep_data_t *ep_data;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
hidp = ddi_get_soft_state(hid_statep, instance);
hid_cpr_resume(hidp);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* Allocate softstate information and get softstate pointer
*/
if (ddi_soft_state_zalloc(hid_statep, instance) == DDI_SUCCESS) {
hidp = ddi_get_soft_state(hid_statep, instance);
}
if (hidp == NULL) {
goto fail;
}
hidp->hid_log_handle = usb_alloc_log_hdl(dip, NULL, &hid_errlevel,
&hid_errmask, &hid_instance_debug, 0);
hidp->hid_instance = instance;
hidp->hid_dip = dip;
/*
* Register with USBA. Just retrieve interface descriptor
*/
if (usb_client_attach(dip, USBDRV_VERSION, 0) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_attach: client attach failed");
goto fail;
}
if (usb_get_dev_data(dip, &dev_data, USB_PARSE_LVL_IF, 0) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_attach: usb_get_dev_data() failed");
goto fail;
}
/* initialize mutex */
mutex_init(&hidp->hid_mutex, NULL, MUTEX_DRIVER,
dev_data->dev_iblock_cookie);
hidp->hid_attach_flags |= HID_LOCK_INIT;
/* get interface data for alternate 0 */
altif_data = &dev_data->dev_curr_cfg->
cfg_if[dev_data->dev_curr_if].if_alt[0];
mutex_enter(&hidp->hid_mutex);
hidp->hid_dev_data = dev_data;
hidp->hid_dev_descr = dev_data->dev_descr;
hidp->hid_interfaceno = dev_data->dev_curr_if;
hidp->hid_if_descr = altif_data->altif_descr;
/*
* Make sure that the bInterfaceProtocol only has meaning to
* Boot Interface Subclass.
*/
if (hidp->hid_if_descr.bInterfaceSubClass != BOOT_INTERFACE)
hidp->hid_if_descr.bInterfaceProtocol = NONE_PROTOCOL;
mutex_exit(&hidp->hid_mutex);
if ((ep_data = usb_lookup_ep_data(dip, dev_data,
hidp->hid_interfaceno, 0, 0,
(uint_t)USB_EP_ATTR_INTR, (uint_t)USB_EP_DIR_IN)) == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"no interrupt IN endpoint found");
goto fail;
}
mutex_enter(&hidp->hid_mutex);
hidp->hid_ep_intr_descr = ep_data->ep_descr;
/*
* Attempt to find the hid descriptor, it could be after interface
* or after endpoint descriptors
*/
if (hid_parse_hid_descr(&hidp->hid_hid_descr, USB_HID_DESCR_SIZE,
altif_data, ep_data) != USB_HID_DESCR_SIZE) {
/*
* If parsing of hid descriptor failed and
* the device is a keyboard or mouse, use predefined
* length and packet size.
*/
if (hid_parse_hid_descr_failure(hidp) == USB_FAILURE) {
mutex_exit(&hidp->hid_mutex);
goto fail;
}
/*
* hid descriptor was bad but since
* the device is a keyboard or mouse,
* we will use the default length
* and packet size.
*/
parse_hid_descr_error = HID_BAD_DESCR;
} else {
/* Parse hid descriptor successful */
USB_DPRINTF_L3(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Hid descriptor:\n\t"
"bLength = 0x%x bDescriptorType = 0x%x "
"bcdHID = 0x%x\n\t"
"bCountryCode = 0x%x bNumDescriptors = 0x%x\n\t"
"bReportDescriptorType = 0x%x\n\t"
"wReportDescriptorLength = 0x%x",
hidp->hid_hid_descr.bLength,
hidp->hid_hid_descr.bDescriptorType,
hidp->hid_hid_descr.bcdHID,
hidp->hid_hid_descr.bCountryCode,
hidp->hid_hid_descr.bNumDescriptors,
hidp->hid_hid_descr.bReportDescriptorType,
hidp->hid_hid_descr.wReportDescriptorLength);
}
/*
* Save a copy of the default pipe for easy reference
*/
hidp->hid_default_pipe = hidp->hid_dev_data->dev_default_ph;
/* we copied the descriptors we need, free the dev_data */
usb_free_dev_data(dip, dev_data);
hidp->hid_dev_data = NULL;
/*
* Don't get the report descriptor if parsing hid descriptor earlier
* failed since device probably won't return valid report descriptor
* either. Though parsing of hid descriptor failed, we have reached
* this point because the device has been identified as a
* keyboard or a mouse successfully and the default packet
* size and layout(in case of keyboard only) will be used, so it
* is ok to go ahead even if parsing of hid descriptor failed and
* we will not try to get the report descriptor.
*/
if (parse_hid_descr_error != HID_BAD_DESCR) {
/*
* Sun mouse rev 105 is a bit slow in responding to this
* request and requires multiple retries
*/
int retry;
/*
* Get and parse the report descriptor.
* Set the packet size if parsing is successful.
* Note that we start retry at 1 to have a delay
* in the first iteration.
*/
mutex_exit(&hidp->hid_mutex);
for (retry = 1; retry < HID_RETRY; retry++) {
if (hid_handle_report_descriptor(hidp,
hidp->hid_interfaceno) == USB_SUCCESS) {
break;
}
delay(retry * drv_usectohz(1000));
}
if (retry >= HID_RETRY) {
goto fail;
}
mutex_enter(&hidp->hid_mutex);
/*
* If packet size is zero, but the device is identified
* as a mouse or a keyboard, use predefined packet
* size.
*/
if (hidp->hid_packet_size == 0) {
if (hidp->hid_if_descr.bInterfaceProtocol ==
KEYBOARD_PROTOCOL) {
/* device is a keyboard */
hidp->hid_packet_size = USBKPSZ;
} else if (hidp->
hid_if_descr.bInterfaceProtocol ==
MOUSE_PROTOCOL) {
/* device is a mouse */
hidp->hid_packet_size = USBMSSZ;
} else {
USB_DPRINTF_L2(PRINT_MASK_ATTA,
hidp->hid_log_handle,
"Failed to find hid packet size");
mutex_exit(&hidp->hid_mutex);
goto fail;
}
}
}
/*
* initialize the pipe policy for the interrupt pipe.
*/
hidp->hid_intr_pipe_policy.pp_max_async_reqs = 1;
/*
* Make a clas specific request to SET_IDLE
* In this case send no reports if state has not changed.
* See HID 7.2.4.
*/
mutex_exit(&hidp->hid_mutex);
hid_set_idle(hidp);
/* always initialize to report protocol */
hid_set_protocol(hidp, SET_REPORT_PROTOCOL);
mutex_enter(&hidp->hid_mutex);
/*
* Create minor node based on information from the
* descriptors
*/
switch (hidp->hid_if_descr.bInterfaceProtocol) {
case KEYBOARD_PROTOCOL:
(void) strcpy(minor_name, "keyboard");
break;
case MOUSE_PROTOCOL:
(void) strcpy(minor_name, "mouse");
break;
default:
/*
* If the report descriptor has the GD mouse collection in
* its multiple collection, create a minor node and support it.
* It is used on some advanced keyboard/mouse set.
*/
if (hidparser_lookup_usage_collection(
hidp->hid_report_descr, HID_GENERIC_DESKTOP,
HID_GD_MOUSE) != HIDPARSER_FAILURE) {
(void) strcpy(minor_name, "mouse");
break;
}
if (hidparser_get_top_level_collection_usage(
hidp->hid_report_descr, &usage_page, &usage) !=
HIDPARSER_FAILURE) {
switch (usage_page) {
case HID_CONSUMER:
switch (usage) {
case HID_CONSUMER_CONTROL:
(void) strcpy(minor_name,
"consumer_control");
break;
default:
(void) sprintf(minor_name,
"hid_%d_%d", usage_page, usage);
break;
}
break;
case HID_GENERIC_DESKTOP:
switch (usage) {
case HID_GD_POINTER:
(void) strcpy(minor_name,
"pointer");
break;
case HID_GD_MOUSE:
(void) strcpy(minor_name,
"mouse");
break;
case HID_GD_KEYBOARD:
(void) strcpy(minor_name,
"keyboard");
break;
default:
(void) sprintf(minor_name,
"hid_%d_%d", usage_page, usage);
break;
}
break;
default:
(void) sprintf(minor_name,
"hid_%d_%d", usage_page, usage);
break;
}
} else {
USB_DPRINTF_L1(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_attach: Unsupported HID device");
mutex_exit(&hidp->hid_mutex);
goto fail;
}
break;
}
mutex_exit(&hidp->hid_mutex);
if ((ddi_create_minor_node(dip, minor_name, S_IFCHR,
HID_CONSTRUCT_EXTERNAL_MINOR(instance),
DDI_PSEUDO, 0)) != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_attach: Could not create minor node");
goto fail;
}
/* create internal path for virtual */
if (strcmp(minor_name, "mouse") == 0) {
if (ddi_create_internal_pathname(dip, "internal_mouse", S_IFCHR,
HID_CONSTRUCT_INTERNAL_MINOR(instance)) != DDI_SUCCESS) {
goto fail;
}
}
if (strcmp(minor_name, "keyboard") == 0) {
if (ddi_create_internal_pathname(dip, "internal_keyboard",
S_IFCHR, HID_CONSTRUCT_INTERNAL_MINOR(instance)) !=
DDI_SUCCESS) {
goto fail;
}
}
mutex_enter(&hidp->hid_mutex);
hidp->hid_attach_flags |= HID_MINOR_NODES;
hidp->hid_dev_state = USB_DEV_ONLINE;
mutex_exit(&hidp->hid_mutex);
/* register for all events */
if (usb_register_event_cbs(dip, &hid_events, 0) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"usb_register_event_cbs failed");
goto fail;
}
/* now create components to power manage this device */
hid_create_pm_components(dip, hidp);
hid_pm_busy_component(hidp);
(void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
hid_pm_idle_component(hidp);
hidp->hid_internal_rq = hidp->hid_external_rq = NULL;
hidp->hid_internal_flag = hidp->hid_external_flag = 0;
hidp->hid_inuse_rq = NULL;
/*
* report device
*/
ddi_report_dev(dip);
USB_DPRINTF_L4(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_attach: End");
return (DDI_SUCCESS);
fail:
if (hidp) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_attach: fail");
hid_detach_cleanup(dip, hidp);
}
return (DDI_FAILURE);
}
/*
* hid_detach :
* Gets called at the time of detach.
*/
static int
hid_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
hid_state_t *hidp;
int rval = DDI_FAILURE;
hidp = ddi_get_soft_state(hid_statep, instance);
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle, "hid_detach");
switch (cmd) {
case DDI_DETACH:
/*
* Undo what we did in client_attach, freeing resources
* and removing things we installed. The system
* framework guarantees we are not active with this devinfo
* node in any other entry points at this time.
*/
hid_detach_cleanup(dip, hidp);
return (DDI_SUCCESS);
case DDI_SUSPEND:
rval = hid_cpr_suspend(hidp);
return (rval == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE);
default:
break;
}
return (rval);
}
/*
* hid_open :
* Open entry point: Opens the interrupt pipe. Sets up queues.
*/
/*ARGSUSED*/
static int
hid_open(queue_t *q, dev_t *devp, int flag, int sflag, cred_t *credp)
{
int no_of_ep = 0;
int rval;
int instance;
hid_state_t *hidp;
minor_t minor = getminor(*devp);
instance = HID_MINOR_TO_INSTANCE(minor);
hidp = ddi_get_soft_state(hid_statep, instance);
if (hidp == NULL) {
return (ENXIO);
}
USB_DPRINTF_L4(PRINT_MASK_OPEN, hidp->hid_log_handle,
"hid_open: Begin");
if (sflag) {
/* clone open NOT supported here */
return (ENXIO);
}
if (!(flag & FREAD)) {
return (EIO);
}
/*
* This is a workaround:
* Currently, if we open an already disconnected device, and send
* a CONSOPENPOLL ioctl to it, the system will panic, please refer
* to the processing HID_OPEN_POLLED_INPUT ioctl in the routine
* hid_mctl_receive().
* The consconfig_dacf module need this interface to detect if the
* device is already disconnnected.
*/
mutex_enter(&hidp->hid_mutex);
if (HID_IS_INTERNAL_OPEN(minor) &&
(hidp->hid_dev_state == USB_DEV_DISCONNECTED)) {
mutex_exit(&hidp->hid_mutex);
return (ENODEV);
}
if (HID_IS_INTERNAL_OPEN(minor) &&
(hidp->hid_internal_rq != NULL)) {
ASSERT(hidp->hid_internal_rq == q);
mutex_exit(&hidp->hid_mutex);
return (0);
}
if ((!HID_IS_INTERNAL_OPEN(minor)) &&
(hidp->hid_external_rq != NULL)) {
ASSERT(hidp->hid_external_rq == q);
mutex_exit(&hidp->hid_mutex);
return (0);
}
mutex_exit(&hidp->hid_mutex);
q->q_ptr = hidp;
WR(q)->q_ptr = hidp;
mutex_enter(&hidp->hid_mutex);
if (hidp->hid_inuse_rq != NULL) {
/* Pipe has already been setup */
if (HID_IS_INTERNAL_OPEN(minor)) {
hidp->hid_internal_flag = HID_STREAMS_OPEN;
hidp->hid_inuse_rq = hidp->hid_internal_rq = q;
} else {
hidp->hid_external_flag = HID_STREAMS_OPEN;
hidp->hid_inuse_rq = hidp->hid_external_rq = q;
}
mutex_exit(&hidp->hid_mutex);
qprocson(q);
return (0);
}
/* Pipe only needs to be opened once */
hidp->hid_interrupt_pipe = NULL;
no_of_ep = hidp->hid_if_descr.bNumEndpoints;
mutex_exit(&hidp->hid_mutex);
/* Check if interrupt endpoint exists */
if (no_of_ep > 0) {
/* Open the interrupt pipe */
if (usb_pipe_open(hidp->hid_dip,
&hidp->hid_ep_intr_descr,
&hidp->hid_intr_pipe_policy, USB_FLAGS_SLEEP,
&hidp->hid_interrupt_pipe) !=
USB_SUCCESS) {
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
return (EIO);
}
}
hid_pm_busy_component(hidp);
(void) pm_raise_power(hidp->hid_dip, 0, USB_DEV_OS_FULL_PWR);
mutex_enter(&hidp->hid_mutex);
if (HID_IS_INTERNAL_OPEN(minor)) {
hidp->hid_internal_flag = HID_STREAMS_OPEN;
hidp->hid_inuse_rq = hidp->hid_internal_rq = q;
} else {
hidp->hid_external_flag = HID_STREAMS_OPEN;
hidp->hid_inuse_rq = hidp->hid_external_rq = q;
}
mutex_exit(&hidp->hid_mutex);
qprocson(q);
mutex_enter(&hidp->hid_mutex);
if ((rval = hid_start_intr_polling(hidp)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_OPEN, hidp->hid_log_handle,
"unable to start intr pipe polling. rval = %d", rval);
if (HID_IS_INTERNAL_OPEN(minor))
hidp->hid_internal_flag = HID_STREAMS_DISMANTLING;
else
hidp->hid_external_flag = HID_STREAMS_DISMANTLING;
mutex_exit(&hidp->hid_mutex);
usb_pipe_close(hidp->hid_dip, hidp->hid_interrupt_pipe,
USB_FLAGS_SLEEP, NULL, NULL);
mutex_enter(&hidp->hid_mutex);
hidp->hid_interrupt_pipe = NULL;
mutex_exit(&hidp->hid_mutex);
qprocsoff(q);
mutex_enter(&hidp->hid_mutex);
if (HID_IS_INTERNAL_OPEN(minor)) {
hidp->hid_internal_flag = 0;
hidp->hid_internal_rq = NULL;
if (hidp->hid_external_flag == HID_STREAMS_OPEN)
hidp->hid_inuse_rq = hidp->hid_external_rq;
else
hidp->hid_inuse_rq = NULL;
} else {
hidp->hid_external_flag = 0;
hidp->hid_external_rq = NULL;
if (hidp->hid_internal_flag == HID_STREAMS_OPEN)
hidp->hid_inuse_rq = hidp->hid_internal_rq;
else
hidp->hid_inuse_rq = NULL;
}
mutex_exit(&hidp->hid_mutex);
q->q_ptr = NULL;
WR(q)->q_ptr = NULL;
hid_pm_idle_component(hidp);
return (EIO);
}
mutex_exit(&hidp->hid_mutex);
USB_DPRINTF_L4(PRINT_MASK_OPEN, hidp->hid_log_handle, "hid_open: End");
/*
* Keyboard and mouse is Power managed by device activity.
* All other devices go busy on open and idle on close.
*/
switch (hidp->hid_pm->hid_pm_strategy) {
case HID_PM_ACTIVITY:
hid_pm_idle_component(hidp);
break;
default:
break;
}
return (0);
}
/*
* hid_close :
* Close entry point.
*/
/*ARGSUSED*/
static int
hid_close(queue_t *q, int flag, cred_t *credp)
{
hid_state_t *hidp = (hid_state_t *)q->q_ptr;
queue_t *wq;
mblk_t *mp;
USB_DPRINTF_L4(PRINT_MASK_CLOSE, hidp->hid_log_handle, "hid_close:");
mutex_enter(&hidp->hid_mutex);
ASSERT((hidp->hid_internal_rq == q) ||
(hidp->hid_external_rq == q));
if (hidp->hid_internal_rq == q)
hidp->hid_internal_flag = HID_STREAMS_DISMANTLING;
else
hidp->hid_external_flag = HID_STREAMS_DISMANTLING;
mutex_exit(&hidp->hid_mutex);
/*
* In case there are any outstanding requests on
* the default pipe, wait forever for them to complete.
*/
(void) usb_pipe_drain_reqs(hidp->hid_dip,
hidp->hid_default_pipe, 0, USB_FLAGS_SLEEP, NULL, 0);
mutex_enter(&hidp->hid_mutex);
wq = WR(q);
/* drain any M_CTLS on the WQ */
while (mp = getq(wq)) {
hid_qreply_merror(wq, mp, EIO);
mutex_exit(&hidp->hid_mutex);
hid_pm_idle_component(hidp);
mutex_enter(&hidp->hid_mutex);
}
mutex_exit(&hidp->hid_mutex);
qprocsoff(q);
q->q_ptr = NULL;
wq->q_ptr = NULL;
mutex_enter(&hidp->hid_mutex);
if (hidp->hid_internal_rq == q) {
hidp->hid_internal_rq = NULL;
hidp->hid_internal_flag = 0;
if (hidp->hid_inuse_rq == q) {
/* We are closing the active stream */
if (hidp->hid_external_flag == HID_STREAMS_OPEN)
hidp->hid_inuse_rq = hidp->hid_external_rq;
else
hidp->hid_inuse_rq = NULL;
}
} else {
hidp->hid_external_rq = NULL;
hidp->hid_external_flag = 0;
if (hidp->hid_inuse_rq == q) {
/* We are closing the active stream */
if (hidp->hid_internal_flag == HID_STREAMS_OPEN)
hidp->hid_inuse_rq = hidp->hid_internal_rq;
else
hidp->hid_inuse_rq = NULL;
}
}
if (hidp->hid_inuse_rq != NULL) {
mutex_exit(&hidp->hid_mutex);
return (0);
}
/* all queues are closed, close USB pipes */
hid_close_intr_pipe(hidp);
mutex_exit(&hidp->hid_mutex);
/*
* Devices other than keyboard/mouse go idle on close.
*/
switch (hidp->hid_pm->hid_pm_strategy) {
case HID_PM_ACTIVITY:
break;
default:
hid_pm_idle_component(hidp);
break;
}
USB_DPRINTF_L4(PRINT_MASK_CLOSE, hidp->hid_log_handle,
"hid_close: End");
return (0);
}
/*
* hid_wput :
* write put routine for the hid module
*/
static int
hid_wput(queue_t *q, mblk_t *mp)
{
hid_state_t *hidp = (hid_state_t *)q->q_ptr;
int error = USB_SUCCESS;
struct iocblk *iocbp;
mblk_t *datap;
int direction;
struct copyresp *crp;
queue_t *tmpq;
int flag;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_wput: Begin");
/* See if the upper module is passing the right thing */
ASSERT(mp != NULL);
ASSERT(mp->b_datap != NULL);
switch (mp->b_datap->db_type) {
case M_FLUSH: /* Canonical flush handling */
if (*mp->b_rptr & FLUSHW) {
flushq(q, FLUSHDATA);
}
/* read queue not used so just send up */
if (*mp->b_rptr & FLUSHR) {
*mp->b_rptr &= ~FLUSHW;
qreply(q, mp);
} else {
freemsg(mp);
}
break;
case M_IOCTL:
iocbp = (struct iocblk *)mp->b_rptr;
/* Only accept transparent ioctls */
if (iocbp->ioc_count != TRANSPARENT) {
miocnak(q, mp, 0, EINVAL);
break;
}
switch (iocbp->ioc_cmd) {
case HIDIOCKMGDIRECT:
mutex_enter(&hidp->hid_mutex);
ASSERT(hidp->hid_inuse_rq != NULL);
mutex_exit(&hidp->hid_mutex);
if ((datap = allocb(sizeof (int), BPRI_MED)) == NULL) {
miocnak(q, mp, 0, ENOMEM);
break;
}
mutex_enter(&hidp->hid_mutex);
if (hidp->hid_inuse_rq == hidp->hid_internal_rq) {
*(int *)datap->b_wptr = 0;
datap->b_wptr += sizeof (int);
} else {
ASSERT(hidp->hid_inuse_rq ==
hidp->hid_external_rq);
*(int *)datap->b_wptr = 1;
datap->b_wptr += sizeof (int);
}
mutex_exit(&hidp->hid_mutex);
mcopyout(mp, NULL, sizeof (int), NULL, datap);
qreply(q, mp);
break;
case HIDIOCKMSDIRECT:
mcopyin(mp, NULL, sizeof (int), NULL);
qreply(q, mp);
break;
default:
miocnak(q, mp, 0, ENOTTY);
}
break;
case M_IOCDATA:
crp = (void *)mp->b_rptr;
if (crp->cp_rval != 0) {
miocnak(q, mp, 0, EIO);
break;
}
switch (crp->cp_cmd) {
case HIDIOCKMGDIRECT:
miocack(q, mp, 0, 0);
break;
case HIDIOCKMSDIRECT:
direction = *(int *)mp->b_cont->b_rptr;
if ((direction != 0) && (direction != 1)) {
miocnak(q, mp, 0, EINVAL);
break;
}
mutex_enter(&hidp->hid_mutex);
if (direction == 0) {
/* The internal stream is made active */
flag = hidp->hid_internal_flag;
tmpq = hidp->hid_internal_rq;
} else {
/* The external stream is made active */
flag = hidp->hid_external_flag;
tmpq = hidp->hid_external_rq;
}
if (flag != HID_STREAMS_OPEN) {
mutex_exit(&hidp->hid_mutex);
miocnak(q, mp, 0, EIO);
break;
}
hidp->hid_inuse_rq = tmpq;
mutex_exit(&hidp->hid_mutex);
miocack(q, mp, 0, 0);
break;
default:
miocnak(q, mp, 0, ENOTTY);
break;
}
break;
case M_CTL:
/* we are busy now */
hid_pm_busy_component(hidp);
if (q->q_first) {
(void) putq(q, mp);
} else {
error = hid_mctl_receive(q, mp);
switch (error) {
case HID_ENQUEUE:
/*
* put this mblk on the WQ for the wsrv to
* process
*/
(void) putq(q, mp);
break;
case HID_INPROGRESS:
/* request has been queued to the device */
break;
case HID_SUCCESS:
/*
* returned by M_CTLS that are processed
* immediately
*/
/* FALLTHRU */
case HID_FAILURE:
default:
hid_pm_idle_component(hidp);
break;
}
}
break;
default:
hid_qreply_merror(q, mp, EINVAL);
error = USB_FAILURE;
break;
}
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_wput: End");
return (DDI_SUCCESS);
}
/*
* hid_wsrv :
* Write service routine for hid. When a message arrives through
* hid_wput(), it is kept in write queue to be serviced later.
*/
static int
hid_wsrv(queue_t *q)
{
hid_state_t *hidp = (hid_state_t *)q->q_ptr;
int error;
mblk_t *mp;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_wsrv: Begin");
mutex_enter(&hidp->hid_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_wsrv: dev_state: %s",
usb_str_dev_state(hidp->hid_dev_state));
/*
* raise power if we are powered down. It is OK to block here since
* we have a separate thread to process this STREAM
*/
if (hidp->hid_dev_state == USB_DEV_PWRED_DOWN) {
mutex_exit(&hidp->hid_mutex);
(void) pm_raise_power(hidp->hid_dip, 0, USB_DEV_OS_FULL_PWR);
mutex_enter(&hidp->hid_mutex);
}
/*
* continue servicing all the M_CTL's till the queue is empty
* or the device gets disconnected or till a hid_close()
*/
while ((hidp->hid_dev_state == USB_DEV_ONLINE) &&
(HID_STREAMS_FLAG(q, hidp) != HID_STREAMS_DISMANTLING) &&
((mp = getq(q)) != NULL)) {
/* Send a message down */
mutex_exit(&hidp->hid_mutex);
error = hid_mctl_receive(q, mp);
switch (error) {
case HID_ENQUEUE:
/* put this mblk back on q to preserve order */
(void) putbq(q, mp);
break;
case HID_INPROGRESS:
/* request has been queued to the device */
break;
case HID_SUCCESS:
case HID_FAILURE:
default:
hid_pm_idle_component(hidp);
break;
}
mutex_enter(&hidp->hid_mutex);
}
mutex_exit(&hidp->hid_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_wsrv: End");
return (DDI_SUCCESS);
}
/*
* hid_power:
* power entry point
*/
static int
hid_power(dev_info_t *dip, int comp, int level)
{
int instance = ddi_get_instance(dip);
hid_state_t *hidp;
hid_power_t *hidpm;
int retval;
hidp = ddi_get_soft_state(hid_statep, instance);
USB_DPRINTF_L3(PRINT_MASK_PM, hidp->hid_log_handle, "hid_power:"
" hid_state: comp=%d level=%d", comp, level);
/* check if we are transitioning to a legal power level */
mutex_enter(&hidp->hid_mutex);
hidpm = hidp->hid_pm;
if (USB_DEV_PWRSTATE_OK(hidpm->hid_pwr_states, level)) {
USB_DPRINTF_L2(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_power: illegal level=%d hid_pwr_states=%d",
level, hidpm->hid_pwr_states);
mutex_exit(&hidp->hid_mutex);
return (DDI_FAILURE);
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
retval = hid_pwrlvl0(hidp);
break;
case USB_DEV_OS_PWR_1:
retval = hid_pwrlvl1(hidp);
break;
case USB_DEV_OS_PWR_2:
retval = hid_pwrlvl2(hidp);
break;
case USB_DEV_OS_FULL_PWR:
retval = hid_pwrlvl3(hidp);
break;
default:
retval = USB_FAILURE;
break;
}
mutex_exit(&hidp->hid_mutex);
return ((retval == USB_SUCCESS) ? DDI_SUCCESS : DDI_FAILURE);
}
/*
* hid_interrupt_pipe_callback:
* Callback function for the hid intr pipe. This function is called by
* USBA when a buffer has been filled. This driver does not cook the data,
* it just sends the message up.
*/
static void
hid_interrupt_pipe_callback(usb_pipe_handle_t pipe, usb_intr_req_t *req)
{
hid_state_t *hidp = (hid_state_t *)req->intr_client_private;
queue_t *q;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_interrupt_pipe_callback: ph = 0x%p req = 0x%p",
(void *)pipe, (void *)req);
hid_pm_busy_component(hidp);
mutex_enter(&hidp->hid_mutex);
/*
* If hid_close() is in progress, we shouldn't try accessing queue
* Otherwise indicate that a putnext is going to happen, so
* if close after this, that should wait for the putnext to finish.
*/
if (HID_STREAMS_FLAG(hidp->hid_inuse_rq, hidp) ==
HID_STREAMS_OPEN) {
/*
* Check if data can be put to the next queue.
*/
if (!canputnext(hidp->hid_inuse_rq)) {
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"Buffer flushed when overflowed.");
/* Flush the queue above */
hid_flush(hidp->hid_inuse_rq);
mutex_exit(&hidp->hid_mutex);
} else {
q = hidp->hid_inuse_rq;
mutex_exit(&hidp->hid_mutex);
/* Put data upstream */
putnext(q, req->intr_data);
/* usb_free_intr_req should not free data */
req->intr_data = NULL;
}
} else {
mutex_exit(&hidp->hid_mutex);
}
/* free request and data */
usb_free_intr_req(req);
hid_pm_idle_component(hidp);
}
/*
* hid_default_pipe_callback :
* Callback routine for the asynchronous control transfer
* Called from hid_send_async_ctrl_request() where we open
* the pipe in exclusive mode
*/
static void
hid_default_pipe_callback(usb_pipe_handle_t pipe, usb_ctrl_req_t *req)
{
hid_default_pipe_arg_t *hid_default_pipe_arg =
(hid_default_pipe_arg_t *)req->ctrl_client_private;
queue_t *wq = hid_default_pipe_arg->hid_default_pipe_arg_queue;
queue_t *rq = RD(wq);
hid_state_t *hidp = (hid_state_t *)rq->q_ptr;
mblk_t *mctl_mp;
mblk_t *data = NULL;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_default_pipe_callback: "
"ph = 0x%p, req = 0x%p, data= 0x%p",
(void *)pipe, (void *)req, (void *)data);
ASSERT((req->ctrl_cb_flags & USB_CB_INTR_CONTEXT) == 0);
if (req->ctrl_data) {
data = req->ctrl_data;
req->ctrl_data = NULL;
}
/*
* Free the b_cont of the original message that was sent down.
*/
mctl_mp = hid_default_pipe_arg->hid_default_pipe_arg_mblk;
freemsg(mctl_mp->b_cont);
/* chain the mblk received to the original & send it up */
mctl_mp->b_cont = data;
if (canputnext(rq)) {
putnext(rq, mctl_mp);
} else {
freemsg(mctl_mp); /* avoid leak */
}
/*
* Free the argument for the asynchronous callback
*/
kmem_free(hid_default_pipe_arg, sizeof (hid_default_pipe_arg_t));
/*
* Free the control pipe request structure.
*/
usb_free_ctrl_req(req);
mutex_enter(&hidp->hid_mutex);
hidp->hid_default_pipe_req--;
ASSERT(hidp->hid_default_pipe_req >= 0);
mutex_exit(&hidp->hid_mutex);
hid_pm_idle_component(hidp);
qenable(wq);
}
/*
* hid_interrupt_pipe_exception_callback:
* Exception callback routine for interrupt pipe. If there is any data,
* destroy it. No threads are waiting for the exception callback.
*/
/*ARGSUSED*/
static void
hid_interrupt_pipe_exception_callback(usb_pipe_handle_t pipe,
usb_intr_req_t *req)
{
hid_state_t *hidp = (hid_state_t *)req->intr_client_private;
mblk_t *data = req->intr_data;
usb_cb_flags_t flags = req->intr_cb_flags;
int rval;
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_interrupt_pipe_exception_callback: "
"completion_reason = 0x%x, data = 0x%p, flag = 0x%x",
req->intr_completion_reason, (void *)data, req->intr_cb_flags);
ASSERT((req->intr_cb_flags & USB_CB_INTR_CONTEXT) == 0);
if (((flags & USB_CB_FUNCTIONAL_STALL) != 0) &&
((flags & USB_CB_STALL_CLEARED) == 0)) {
USB_DPRINTF_L2(PRINT_MASK_ALL,
hidp->hid_log_handle,
"hid_interrupt_pipe_exception_callback: "
"unable to clear stall. flags = 0x%x",
req->intr_cb_flags);
}
mutex_enter(&hidp->hid_mutex);
switch (req->intr_completion_reason) {
case USB_CR_STOPPED_POLLING:
case USB_CR_PIPE_CLOSING:
default:
break;
case USB_CR_PIPE_RESET:
case USB_CR_NO_RESOURCES:
if ((hidp->hid_dev_state == USB_DEV_ONLINE) &&
((rval = hid_start_intr_polling(hidp)) !=
USB_SUCCESS)) {
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"unable to restart interrupt poll. rval = %d",
rval);
}
break;
}
mutex_exit(&hidp->hid_mutex);
usb_free_intr_req(req);
}
/*
* hid_default_pipe_exception_callback:
* Exception callback routine for default pipe.
*/
/*ARGSUSED*/
static void
hid_default_pipe_exception_callback(usb_pipe_handle_t pipe,
usb_ctrl_req_t *req)
{
hid_default_pipe_arg_t *hid_default_pipe_arg =
(hid_default_pipe_arg_t *)req->ctrl_client_private;
queue_t *wq = hid_default_pipe_arg->hid_default_pipe_arg_queue;
queue_t *rq = RD(wq);
hid_state_t *hidp = (hid_state_t *)rq->q_ptr;
usb_cr_t ctrl_completion_reason = req->ctrl_completion_reason;
mblk_t *mp, *data = NULL;
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_default_pipe_exception_callback: "
"completion_reason = 0x%x, data = 0x%p, flag = 0x%x",
ctrl_completion_reason, (void *)data, req->ctrl_cb_flags);
ASSERT((req->ctrl_cb_flags & USB_CB_INTR_CONTEXT) == 0);
mp = hid_default_pipe_arg->hid_default_pipe_arg_mblk;
/*
* Pass an error message up. Reuse existing mblk.
*/
if (canputnext(rq)) {
mp->b_datap->db_type = M_ERROR;
mp->b_rptr = mp->b_datap->db_base;
mp->b_wptr = mp->b_rptr + sizeof (char);
*mp->b_rptr = EIO;
putnext(rq, mp);
} else {
freemsg(mp);
}
kmem_free(hid_default_pipe_arg, sizeof (hid_default_pipe_arg_t));
mutex_enter(&hidp->hid_mutex);
hidp->hid_default_pipe_req--;
ASSERT(hidp->hid_default_pipe_req >= 0);
mutex_exit(&hidp->hid_mutex);
qenable(wq);
usb_free_ctrl_req(req);
hid_pm_idle_component(hidp);
}
/*
* event handling:
*
* hid_reconnect_event_callback:
* the device was disconnected but this instance not detached, probably
* because the device was busy
*
* If the same device, continue with restoring state
*/
static int
hid_restore_state_event_callback(dev_info_t *dip)
{
hid_state_t *hidp = (hid_state_t *)ddi_get_soft_state(hid_statep,
ddi_get_instance(dip));
ASSERT(hidp != NULL);
USB_DPRINTF_L3(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_restore_state_event_callback: dip=0x%p", (void *)dip);
hid_restore_device_state(dip, hidp);
return (USB_SUCCESS);
}
/*
* hid_cpr_suspend
* Fail suspend if we can't finish outstanding i/o activity.
*/
static int
hid_cpr_suspend(hid_state_t *hidp)
{
int rval, prev_state;
int retval = USB_FAILURE;
USB_DPRINTF_L4(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_cpr_suspend: dip=0x%p", (void *)hidp->hid_dip);
mutex_enter(&hidp->hid_mutex);
switch (hidp->hid_dev_state) {
case USB_DEV_ONLINE:
case USB_DEV_PWRED_DOWN:
prev_state = hidp->hid_dev_state;
hidp->hid_dev_state = USB_DEV_SUSPENDED;
mutex_exit(&hidp->hid_mutex);
/* drain all request outstanding on the default control pipe */
rval = usb_pipe_drain_reqs(hidp->hid_dip,
hidp->hid_default_pipe, hid_default_pipe_drain_timeout,
USB_FLAGS_SLEEP, NULL, 0);
/* fail checkpoint if we haven't finished the job yet */
mutex_enter(&hidp->hid_mutex);
if ((rval != USB_SUCCESS) || (hidp->hid_default_pipe_req > 0)) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_cpr_suspend: "
"device busy - can't checkpoint");
/* fall back to previous state */
hidp->hid_dev_state = prev_state;
} else {
retval = USB_SUCCESS;
hid_save_device_state(hidp);
}
break;
case USB_DEV_DISCONNECTED:
hidp->hid_dev_state = USB_DEV_SUSPENDED;
hid_save_device_state(hidp);
retval = USB_SUCCESS;
break;
case USB_DEV_SUSPENDED:
default:
USB_DPRINTF_L2(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_cpr_suspend: Illegal dev state: %d",
hidp->hid_dev_state);
break;
}
mutex_exit(&hidp->hid_mutex);
return (retval);
}
static void
hid_cpr_resume(hid_state_t *hidp)
{
USB_DPRINTF_L4(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_cpr_resume: dip=0x%p", (void *)hidp->hid_dip);
hid_restore_device_state(hidp->hid_dip, hidp);
}
/*
* hid_disconnect_event_callback:
* The device has been disconnected. We either wait for
* detach or a reconnect event. Close all pipes and timeouts.
*/
static int
hid_disconnect_event_callback(dev_info_t *dip)
{
hid_state_t *hidp;
mblk_t *mp;
hidp = (hid_state_t *)ddi_get_soft_state(hid_statep,
ddi_get_instance(dip));
ASSERT(hidp != NULL);
USB_DPRINTF_L4(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_disconnect_event_callback: dip=0x%p", (void *)dip);
mutex_enter(&hidp->hid_mutex);
switch (hidp->hid_dev_state) {
case USB_DEV_ONLINE:
case USB_DEV_PWRED_DOWN:
hidp->hid_dev_state = USB_DEV_DISCONNECTED;
if (HID_IS_OPEN(hidp)) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"busy device has been disconnected");
}
hid_save_device_state(hidp);
/*
* Notify applications about device removal, this only
* applies to an external (aka. physical) open. For an
* internal open, consconfig_dacf closes the queue.
*/
if (hidp->hid_external_flag == HID_STREAMS_OPEN) {
queue_t *q = hidp->hid_external_rq;
mutex_exit(&hidp->hid_mutex);
mp = allocb(sizeof (uchar_t), BPRI_HI);
if (mp != NULL) {
mp->b_datap->db_type = M_ERROR;
mp->b_rptr = mp->b_datap->db_base;
mp->b_wptr = mp->b_rptr + sizeof (char);
*mp->b_rptr = ENODEV;
putnext(q, mp);
}
mutex_enter(&hidp->hid_mutex);
}
break;
case USB_DEV_SUSPENDED:
/* we remain suspended */
break;
default:
USB_DPRINTF_L2(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_disconnect_event_callback: Illegal dev state: %d",
hidp->hid_dev_state);
break;
}
mutex_exit(&hidp->hid_mutex);
return (USB_SUCCESS);
}
/*
* hid_power_change_callback:
* Async callback function to notify pm_raise_power completion
* after hid_power entry point is called.
*/
static void
hid_power_change_callback(void *arg, int rval)
{
hid_state_t *hidp;
queue_t *wq;
hidp = (hid_state_t *)arg;
USB_DPRINTF_L4(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_power_change_callback - rval: %d", rval);
mutex_enter(&hidp->hid_mutex);
hidp->hid_pm->hid_raise_power = B_FALSE;
if (hidp->hid_dev_state == USB_DEV_ONLINE) {
wq = WR(hidp->hid_inuse_rq);
mutex_exit(&hidp->hid_mutex);
qenable(wq);
} else {
mutex_exit(&hidp->hid_mutex);
}
}
/*
* hid_parse_hid_descr:
* Parse the hid descriptor, check after interface and after
* endpoint descriptor
*/
static size_t
hid_parse_hid_descr(
usb_hid_descr_t *ret_descr,
size_t ret_buf_len,
usb_alt_if_data_t *altif_data,
usb_ep_data_t *ep_data)
{
usb_cvs_data_t *cvs;
int which_cvs;
for (which_cvs = 0; which_cvs < altif_data->altif_n_cvs; which_cvs++) {
cvs = &altif_data->altif_cvs[which_cvs];
if (cvs->cvs_buf == NULL) {
continue;
}
if (cvs->cvs_buf[1] == USB_DESCR_TYPE_HID) {
return (usb_parse_data("ccscccs",
cvs->cvs_buf, cvs->cvs_buf_len,
(void *)ret_descr,
(size_t)ret_buf_len));
}
}
/* now try after endpoint */
for (which_cvs = 0; which_cvs < ep_data->ep_n_cvs; which_cvs++) {
cvs = &ep_data->ep_cvs[which_cvs];
if (cvs->cvs_buf == NULL) {
continue;
}
if (cvs->cvs_buf[1] == USB_DESCR_TYPE_HID) {
return (usb_parse_data("ccscccs",
cvs->cvs_buf, cvs->cvs_buf_len,
(void *)ret_descr,
(size_t)ret_buf_len));
}
}
return (USB_PARSE_ERROR);
}
/*
* hid_parse_hid_descr_failure:
* If parsing of hid descriptor failed and the device is
* a keyboard or mouse, use predefined length and packet size.
*/
static int
hid_parse_hid_descr_failure(hid_state_t *hidp)
{
/*
* Parsing hid descriptor failed, probably because the
* device did not return a valid hid descriptor. Check to
* see if this is a keyboard or mouse. If so, use the
* predefined hid descriptor length and packet size.
* Otherwise, detach and return failure.
*/
USB_DPRINTF_L1(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Parsing of hid descriptor failed");
if (hidp->hid_if_descr.bInterfaceProtocol == KEYBOARD_PROTOCOL) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Set hid descriptor length to predefined "
"USB_KB_HID_DESCR_LENGTH for keyboard.");
/* device is a keyboard */
hidp->hid_hid_descr.wReportDescriptorLength =
USB_KB_HID_DESCR_LENGTH;
hidp->hid_packet_size = USBKPSZ;
} else if (hidp->hid_if_descr.bInterfaceProtocol ==
MOUSE_PROTOCOL) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Set hid descriptor length to predefined "
"USB_MS_HID_DESCR_LENGTH for mouse.");
/* device is a mouse */
hidp->hid_hid_descr.wReportDescriptorLength =
USB_MS_HID_DESCR_LENGTH;
hidp->hid_packet_size = USBMSSZ;
} else {
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hid_handle_report_descriptor:
* Get the report descriptor, call hidparser routine to parse
* it and query the hidparser tree to get the packet size
*/
static int
hid_handle_report_descriptor(hid_state_t *hidp,
int interface)
{
usb_cr_t completion_reason;
usb_cb_flags_t cb_flags;
mblk_t *data = NULL;
hidparser_packet_info_t hpack;
int i;
usb_ctrl_setup_t setup = {
USB_DEV_REQ_DEV_TO_HOST | /* bmRequestType */
USB_DEV_REQ_RCPT_IF,
USB_REQ_GET_DESCR, /* bRequest */
USB_CLASS_DESCR_TYPE_REPORT, /* wValue */
0, /* wIndex: interface, fill in later */
0, /* wLength, fill in later */
0 /* attributes */
};
/*
* Parsing hid desciptor was successful earlier.
* Get Report Descriptor
*/
setup.wIndex = (uint16_t)interface;
setup.wLength = hidp->hid_hid_descr.wReportDescriptorLength;
if (usb_pipe_ctrl_xfer_wait(hidp->hid_default_pipe,
&setup,
&data, /* data */
&completion_reason, &cb_flags, 0) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Failed to receive the Report Descriptor");
freemsg(data);
return (USB_FAILURE);
} else {
int n = hidp->hid_hid_descr.wReportDescriptorLength;
ASSERT(data);
/* Print the report descriptor */
for (i = 0; i < n; i++) {
USB_DPRINTF_L3(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Index = %d\tvalue =0x%x", i,
(int)(data->b_rptr[i]));
}
/* Get Report Descriptor was successful */
if (hidparser_parse_report_descriptor(
data->b_rptr,
hidp->hid_hid_descr.wReportDescriptorLength,
&hidp->hid_hid_descr,
&hidp->hid_report_descr) == HIDPARSER_SUCCESS) {
/* find max intr-in xfer length */
hidparser_find_max_packet_size_from_report_descriptor(
hidp->hid_report_descr, &hpack);
/* round up to the nearest byte */
hidp->hid_packet_size = (hpack.max_packet_size + 7) / 8;
/* if report id is used, add more more byte for it */
if (hpack.report_id != HID_REPORT_ID_UNDEFINED) {
hidp->hid_packet_size++;
}
} else {
USB_DPRINTF_L1(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Invalid Report Descriptor");
freemsg(data);
return (USB_FAILURE);
}
freemsg(data);
return (USB_SUCCESS);
}
}
/*
* hid_set_idle:
* Make a clas specific request to SET_IDLE.
* In this case send no reports if state has not changed.
* See HID 7.2.4.
*/
/*ARGSUSED*/
static void
hid_set_idle(hid_state_t *hidp)
{
usb_cr_t completion_reason;
usb_cb_flags_t cb_flags;
usb_ctrl_setup_t setup = {
USB_DEV_REQ_HOST_TO_DEV | /* bmRequestType */
USB_DEV_REQ_TYPE_CLASS |
USB_DEV_REQ_RCPT_IF,
SET_IDLE, /* bRequest */
DURATION, /* wValue */
0, /* wIndex: interface, fill in later */
0, /* wLength */
0 /* attributes */
};
USB_DPRINTF_L4(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_set_idle: Begin");
setup.wIndex = hidp->hid_if_descr.bInterfaceNumber;
if (usb_pipe_ctrl_xfer_wait(
hidp->hid_default_pipe,
&setup,
NULL, /* no data to send. */
&completion_reason, &cb_flags, 0) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Failed while trying to set idle,"
"cr = %d, cb_flags = 0x%x\n",
completion_reason, cb_flags);
}
USB_DPRINTF_L4(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_set_idle: End");
}
/*
* hid_set_protocol:
* Initialize the device to set the preferred protocol
*/
/*ARGSUSED*/
static void
hid_set_protocol(hid_state_t *hidp, int protocol)
{
usb_cr_t completion_reason;
usb_cb_flags_t cb_flags;
usb_ctrl_setup_t setup;
USB_DPRINTF_L4(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_set_protocol(%d): Begin", protocol);
/* initialize the setup request */
setup.bmRequestType = USB_DEV_REQ_HOST_TO_DEV |
USB_DEV_REQ_TYPE_CLASS | USB_DEV_REQ_RCPT_IF;
setup.bRequest = SET_PROTOCOL;
setup.wValue = (uint16_t)protocol;
setup.wIndex = hidp->hid_if_descr.bInterfaceNumber;
setup.wLength = 0;
setup.attrs = 0;
if (usb_pipe_ctrl_xfer_wait(
hidp->hid_default_pipe, /* bmRequestType */
&setup,
NULL, /* no data to send */
&completion_reason, &cb_flags, 0) != USB_SUCCESS) {
/*
* Some devices fail to follow the specification
* and instead of STALLing, they continously
* NAK the SET_IDLE command. We need to reset
* the pipe then, so that ohci doesn't panic.
*/
USB_DPRINTF_L2(PRINT_MASK_ATTA, hidp->hid_log_handle,
"Failed while trying to set protocol:%d,"
"cr = %d cb_flags = 0x%x\n",
completion_reason, cb_flags, protocol);
}
USB_DPRINTF_L4(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_set_protocol: End");
}
/*
* hid_detach_cleanup:
* called by attach and detach for cleanup.
*/
static void
hid_detach_cleanup(dev_info_t *dip, hid_state_t *hidp)
{
int flags = hidp->hid_attach_flags;
int rval;
hid_power_t *hidpm;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_detach_cleanup: Begin");
if ((hidp->hid_attach_flags & HID_LOCK_INIT) == 0) {
goto done;
}
/*
* Disable the event callbacks first, after this point, event
* callbacks will never get called. Note we shouldn't hold
* mutex while unregistering events because there may be a
* competing event callback thread. Event callbacks are done
* with ndi mutex held and this can cause a potential deadlock.
*/
usb_unregister_event_cbs(dip, &hid_events);
mutex_enter(&hidp->hid_mutex);
hidpm = hidp->hid_pm;
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_detach_cleanup: hidpm=0x%p", (void *)hidpm);
if (hidpm && (hidp->hid_dev_state != USB_DEV_DISCONNECTED)) {
mutex_exit(&hidp->hid_mutex);
hid_pm_busy_component(hidp);
if (hid_is_pm_enabled(dip) == USB_SUCCESS) {
if (hidpm->hid_wakeup_enabled) {
/* First bring the device to full power */
(void) pm_raise_power(dip, 0,
USB_DEV_OS_FULL_PWR);
/* Disable remote wakeup */
rval = usb_handle_remote_wakeup(dip,
USB_REMOTE_WAKEUP_DISABLE);
if (rval != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ALL,
hidp->hid_log_handle,
"hid_detach_cleanup: "
"disble remote wakeup failed, "
"rval= %d", rval);
}
}
(void) pm_lower_power(dip, 0, USB_DEV_OS_PWR_OFF);
}
hid_pm_idle_component(hidp);
mutex_enter(&hidp->hid_mutex);
}
if (hidpm) {
freemsg(hidpm->hid_pm_pwrup);
kmem_free(hidpm, sizeof (hid_power_t));
hidp->hid_pm = NULL;
}
mutex_exit(&hidp->hid_mutex);
if (hidp->hid_report_descr != NULL) {
(void) hidparser_free_report_descriptor_handle(
hidp->hid_report_descr);
}
if (flags & HID_MINOR_NODES) {
ddi_remove_minor_node(dip, NULL);
}
mutex_destroy(&hidp->hid_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_detach_cleanup: End");
done:
usb_client_detach(dip, hidp->hid_dev_data);
usb_free_log_hdl(hidp->hid_log_handle);
ddi_soft_state_free(hid_statep, hidp->hid_instance);
ddi_prop_remove_all(dip);
}
/*
* hid_start_intr_polling:
* Allocate an interrupt request structure, initialize,
* and start interrupt transfers.
*/
static int
hid_start_intr_polling(hid_state_t *hidp)
{
usb_intr_req_t *req;
int rval = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_start_intr_polling: "
"dev_state=%s internal_str_flag=%d external_str_flag=%d ph=0x%p",
usb_str_dev_state(hidp->hid_dev_state), hidp->hid_internal_flag,
hidp->hid_external_flag, (void *)hidp->hid_interrupt_pipe);
if (HID_IS_OPEN(hidp) && (hidp->hid_interrupt_pipe != NULL)) {
/*
* initialize interrupt pipe request structure
*/
req = usb_alloc_intr_req(hidp->hid_dip, 0, USB_FLAGS_SLEEP);
req->intr_client_private = (usb_opaque_t)hidp;
req->intr_attributes = USB_ATTRS_SHORT_XFER_OK |
USB_ATTRS_AUTOCLEARING;
req->intr_len = hidp->hid_packet_size;
req->intr_cb = hid_interrupt_pipe_callback;
req->intr_exc_cb = hid_interrupt_pipe_exception_callback;
/*
* Start polling on the interrupt pipe.
*/
mutex_exit(&hidp->hid_mutex);
if ((rval = usb_pipe_intr_xfer(hidp->hid_interrupt_pipe, req,
USB_FLAGS_SLEEP)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_start_intr_polling failed: rval = %d",
rval);
usb_free_intr_req(req);
}
mutex_enter(&hidp->hid_mutex);
}
USB_DPRINTF_L4(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_start_intr_polling: done, rval = %d", rval);
return (rval);
}
/*
* hid_close_intr_pipe:
* close the interrupt pipe after draining all callbacks
*/
static void
hid_close_intr_pipe(hid_state_t *hidp)
{
USB_DPRINTF_L4(PRINT_MASK_CLOSE, hidp->hid_log_handle,
"hid_close_intr_pipe: Begin");
if (hidp->hid_interrupt_pipe) {
/*
* Close the interrupt pipe
*/
mutex_exit(&hidp->hid_mutex);
usb_pipe_close(hidp->hid_dip, hidp->hid_interrupt_pipe,
USB_FLAGS_SLEEP, NULL, NULL);
mutex_enter(&hidp->hid_mutex);
hidp->hid_interrupt_pipe = NULL;
}
USB_DPRINTF_L4(PRINT_MASK_CLOSE, hidp->hid_log_handle,
"hid_close_intr_pipe: End");
}
/*
* hid_mctl_receive:
* Handle M_CTL messages from upper stream. If
* we don't understand the command, free message.
*/
static int
hid_mctl_receive(register queue_t *q, register mblk_t *mp)
{
hid_state_t *hidp = (hid_state_t *)q->q_ptr;
struct iocblk *iocp;
int error = HID_FAILURE;
uchar_t request_type;
hid_req_t *hid_req_data = NULL;
hid_polled_input_callback_t hid_polled_input;
hid_vid_pid_t hid_vid_pid;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_mctl_receive");
iocp = (struct iocblk *)mp->b_rptr;
switch (iocp->ioc_cmd) {
case HID_SET_REPORT:
/* FALLTHRU */
case HID_SET_IDLE:
/* FALLTHRU */
case HID_SET_PROTOCOL:
request_type = USB_DEV_REQ_HOST_TO_DEV |
USB_DEV_REQ_RCPT_IF | USB_DEV_REQ_TYPE_CLASS;
break;
case HID_GET_REPORT:
/* FALLTHRU */
case HID_GET_IDLE:
/* FALLTHRU */
case HID_GET_PROTOCOL:
request_type = USB_DEV_REQ_DEV_TO_HOST |
USB_DEV_REQ_RCPT_IF | USB_DEV_REQ_TYPE_CLASS;
break;
case HID_GET_PARSER_HANDLE:
if (canputnext(RD(q))) {
freemsg(mp->b_cont);
mp->b_cont = hid_data2mblk(
(uchar_t *)&hidp->hid_report_descr,
sizeof (hidp->hid_report_descr));
if (mp->b_cont == NULL) {
/*
* can't allocate mblk, indicate
* that nothing is returned
*/
iocp->ioc_count = 0;
} else {
iocp->ioc_count =
sizeof (hidp->hid_report_descr);
}
qreply(q, mp);
return (HID_SUCCESS);
} else {
/* retry */
return (HID_ENQUEUE);
}
case HID_GET_VID_PID:
if (canputnext(RD(q))) {
freemsg(mp->b_cont);
hid_vid_pid.VendorId =
hidp->hid_dev_descr->idVendor;
hid_vid_pid.ProductId =
hidp->hid_dev_descr->idProduct;
mp->b_cont = hid_data2mblk(
(uchar_t *)&hid_vid_pid, sizeof (hid_vid_pid_t));
if (mp->b_cont == NULL) {
/*
* can't allocate mblk, indicate that nothing
* is being returned.
*/
iocp->ioc_count = 0;
} else {
iocp->ioc_count =
sizeof (hid_vid_pid_t);
}
qreply(q, mp);
return (HID_SUCCESS);
} else {
/* retry */
return (HID_ENQUEUE);
}
case HID_OPEN_POLLED_INPUT:
if (canputnext(RD(q))) {
freemsg(mp->b_cont);
/* Initialize the structure */
hid_polled_input.hid_polled_version =
HID_POLLED_INPUT_V0;
hid_polled_input.hid_polled_read = hid_polled_read;
hid_polled_input.hid_polled_input_enter =
hid_polled_input_enter;
hid_polled_input.hid_polled_input_exit =
hid_polled_input_exit;
hid_polled_input.hid_polled_input_handle =
(hid_polled_handle_t)hidp;
mp->b_cont = hid_data2mblk(
(uchar_t *)&hid_polled_input,
sizeof (hid_polled_input_callback_t));
if (mp->b_cont == NULL) {
/*
* can't allocate mblk, indicate that nothing
* is being returned.
*/
iocp->ioc_count = 0;
} else {
/* Call down into USBA */
(void) hid_polled_input_init(hidp);
iocp->ioc_count =
sizeof (hid_polled_input_callback_t);
}
qreply(q, mp);
return (HID_SUCCESS);
} else {
/* retry */
return (HID_ENQUEUE);
}
case HID_CLOSE_POLLED_INPUT:
/* Call down into USBA */
(void) hid_polled_input_fini(hidp);
iocp->ioc_count = 0;
qreply(q, mp);
return (HID_SUCCESS);
default:
hid_qreply_merror(q, mp, EINVAL);
return (HID_FAILURE);
}
/*
* These (device executable) commands require a hid_req_t.
* Make sure one is present
*/
if (mp->b_cont == NULL) {
hid_qreply_merror(q, mp, EINVAL);
return (error);
} else {
hid_req_data = (hid_req_t *)mp->b_cont->b_rptr;
if ((iocp->ioc_cmd == HID_SET_REPORT) &&
(hid_req_data->hid_req_wLength == 0)) {
hid_qreply_merror(q, mp, EINVAL);
return (error);
}
}
/*
* Check is version no. is correct. This
* is coming from the user
*/
if (hid_req_data->hid_req_version_no != HID_VERSION_V_0) {
hid_qreply_merror(q, mp, EINVAL);
return (error);
}
mutex_enter(&hidp->hid_mutex);
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_mctl_receive: dev_state=%s",
usb_str_dev_state(hidp->hid_dev_state));
switch (hidp->hid_dev_state) {
case USB_DEV_PWRED_DOWN:
/*
* get the device full powered. We get a callback
* which enables the WQ and kicks off IO
*/
hidp->hid_dev_state = USB_DEV_HID_POWER_CHANGE;
mutex_exit(&hidp->hid_mutex);
if (usb_req_raise_power(hidp->hid_dip, 0,
USB_DEV_OS_FULL_PWR, hid_power_change_callback,
hidp, 0) != USB_SUCCESS) {
/* we retry raising power in wsrv */
mutex_enter(&hidp->hid_mutex);
hidp->hid_dev_state = USB_DEV_PWRED_DOWN;
mutex_exit(&hidp->hid_mutex);
}
error = HID_ENQUEUE;
break;
case USB_DEV_HID_POWER_CHANGE:
mutex_exit(&hidp->hid_mutex);
error = HID_ENQUEUE;
break;
case USB_DEV_ONLINE:
if (HID_STREAMS_FLAG(q, hidp) != HID_STREAMS_DISMANTLING) {
/* Send a message down */
mutex_exit(&hidp->hid_mutex);
error = hid_mctl_execute_cmd(q, request_type,
hid_req_data, mp);
if (error == HID_FAILURE) {
hid_qreply_merror(q, mp, EIO);
}
} else {
mutex_exit(&hidp->hid_mutex);
hid_qreply_merror(q, mp, EIO);
}
break;
default:
mutex_exit(&hidp->hid_mutex);
hid_qreply_merror(q, mp, EIO);
break;
}
return (error);
}
/*
* hid_mctl_execute_cmd:
* Send the command to the device.
*/
static int
hid_mctl_execute_cmd(queue_t *q, int request_type, hid_req_t *hid_req_data,
mblk_t *mp)
{
int request_index;
struct iocblk *iocp;
hid_default_pipe_arg_t *def_pipe_arg;
hid_state_t *hidp = (hid_state_t *)q->q_ptr;
iocp = (struct iocblk *)mp->b_rptr;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_mctl_execute_cmd: iocp=0x%p", (void *)iocp);
request_index = hidp->hid_if_descr.bInterfaceNumber;
/*
* Set up the argument to be passed back to hid
* when the asynchronous control callback is
* executed.
*/
def_pipe_arg = kmem_zalloc(sizeof (hid_default_pipe_arg_t), 0);
if (def_pipe_arg == NULL) {
return (HID_FAILURE);
}
def_pipe_arg->hid_default_pipe_arg_queue = q;
def_pipe_arg->hid_default_pipe_arg_mctlmsg.ioc_cmd = iocp->ioc_cmd;
def_pipe_arg->hid_default_pipe_arg_mctlmsg.ioc_count = 0;
def_pipe_arg->hid_default_pipe_arg_mblk = mp;
/*
* Send the command down to USBA through default
* pipe.
*/
if (hid_send_async_ctrl_request(def_pipe_arg, hid_req_data,
request_type, iocp->ioc_cmd, request_index) != USB_SUCCESS) {
kmem_free(def_pipe_arg, sizeof (hid_default_pipe_arg_t));
return (HID_FAILURE);
}
return (HID_INPROGRESS);
}
/*
* hid_send_async_ctrl_request:
* Send an asynchronous control request to USBA. Since hid is a STREAMS
* driver, it is not allowed to wait in its entry points except for the
* open and close entry points. Therefore, hid must use the asynchronous
* USBA calls.
*/
static int
hid_send_async_ctrl_request(hid_default_pipe_arg_t *hid_default_pipe_arg,
hid_req_t *hid_request,
uchar_t request_type, int request_request,
ushort_t request_index)
{
queue_t *q = hid_default_pipe_arg->hid_default_pipe_arg_queue;
hid_state_t *hidp = (hid_state_t *)q->q_ptr;
usb_ctrl_req_t *ctrl_req;
int rval;
size_t length = 0;
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_send_async_ctrl_request: "
"rq_type=%d rq_rq=%d index=%d",
request_type, request_request, request_index);
mutex_enter(&hidp->hid_mutex);
hidp->hid_default_pipe_req++;
mutex_exit(&hidp->hid_mutex);
/*
* Note that ctrl_req->ctrl_data should be allocated by usba
* only for IN requests. OUT request(e.g SET_REPORT) can have a
* non-zero wLength value but ctrl_data would be allocated by
* client for them.
*/
if (hid_request->hid_req_wLength >= MAX_REPORT_DATA) {
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_req_wLength is exceeded");
return (USB_FAILURE);
}
if ((request_type & USB_DEV_REQ_DIR_MASK) == USB_DEV_REQ_DEV_TO_HOST) {
length = hid_request->hid_req_wLength;
}
if ((ctrl_req = usb_alloc_ctrl_req(hidp->hid_dip, length, 0)) == NULL) {
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"unable to alloc ctrl req. async trans failed");
mutex_enter(&hidp->hid_mutex);
hidp->hid_default_pipe_req--;
ASSERT(hidp->hid_default_pipe_req >= 0);
mutex_exit(&hidp->hid_mutex);
return (USB_FAILURE);
}
if ((request_type & USB_DEV_REQ_DIR_MASK) == USB_DEV_REQ_HOST_TO_DEV) {
ASSERT((length == 0) && (ctrl_req->ctrl_data == NULL));
}
ctrl_req->ctrl_bmRequestType = request_type;
ctrl_req->ctrl_bRequest = (uint8_t)request_request;
ctrl_req->ctrl_wValue = hid_request->hid_req_wValue;
ctrl_req->ctrl_wIndex = request_index;
ctrl_req->ctrl_wLength = hid_request->hid_req_wLength;
/* host to device: create a msg from hid_req_data */
if ((request_type & USB_DEV_REQ_DIR_MASK) == USB_DEV_REQ_HOST_TO_DEV) {
mblk_t *pblk = allocb(hid_request->hid_req_wLength, BPRI_HI);
if (pblk == NULL) {
usb_free_ctrl_req(ctrl_req);
return (USB_FAILURE);
}
bcopy(hid_request->hid_req_data, pblk->b_wptr,
hid_request->hid_req_wLength);
pblk->b_wptr += hid_request->hid_req_wLength;
ctrl_req->ctrl_data = pblk;
}
ctrl_req->ctrl_attributes = USB_ATTRS_AUTOCLEARING;
ctrl_req->ctrl_client_private = (usb_opaque_t)hid_default_pipe_arg;
ctrl_req->ctrl_cb = hid_default_pipe_callback;
ctrl_req->ctrl_exc_cb = hid_default_pipe_exception_callback;
if ((rval = usb_pipe_ctrl_xfer(hidp->hid_default_pipe,
ctrl_req, 0)) != USB_SUCCESS) {
mutex_enter(&hidp->hid_mutex);
hidp->hid_default_pipe_req--;
ASSERT(hidp->hid_default_pipe_req >= 0);
mutex_exit(&hidp->hid_mutex);
usb_free_ctrl_req(ctrl_req);
USB_DPRINTF_L2(PRINT_MASK_ALL, hidp->hid_log_handle,
"usb_pipe_ctrl_xfer() failed. rval = %d", rval);
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hid_create_pm_components:
* Create the pm components required for power management.
* For keyboard/mouse, the components is created only if the device
* supports a remote wakeup.
* For other hid devices they are created unconditionally.
*/
static void
hid_create_pm_components(dev_info_t *dip, hid_state_t *hidp)
{
hid_power_t *hidpm;
uint_t pwr_states;
USB_DPRINTF_L4(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_create_pm_components: Begin");
/* Allocate the state structure */
hidpm = kmem_zalloc(sizeof (hid_power_t), KM_SLEEP);
hidp->hid_pm = hidpm;
hidpm->hid_state = hidp;
hidpm->hid_raise_power = B_FALSE;
hidpm->hid_pm_capabilities = 0;
hidpm->hid_current_power = USB_DEV_OS_FULL_PWR;
switch (hidp->hid_if_descr.bInterfaceProtocol) {
case KEYBOARD_PROTOCOL:
case MOUSE_PROTOCOL:
hidpm->hid_pm_strategy = HID_PM_ACTIVITY;
if ((hid_is_pm_enabled(dip) == USB_SUCCESS) &&
(usb_handle_remote_wakeup(dip, USB_REMOTE_WAKEUP_ENABLE) ==
USB_SUCCESS)) {
USB_DPRINTF_L3(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_create_pm_components: Remote Wakeup Enabled");
if (usb_create_pm_components(dip, &pwr_states) ==
USB_SUCCESS) {
hidpm->hid_wakeup_enabled = 1;
hidpm->hid_pwr_states = (uint8_t)pwr_states;
}
}
break;
default:
hidpm->hid_pm_strategy = HID_PM_OPEN_CLOSE;
if ((hid_is_pm_enabled(dip) == USB_SUCCESS) &&
(usb_create_pm_components(dip, &pwr_states) ==
USB_SUCCESS)) {
hidpm->hid_wakeup_enabled = 0;
hidpm->hid_pwr_states = (uint8_t)pwr_states;
}
break;
}
USB_DPRINTF_L4(PRINT_MASK_PM, hidp->hid_log_handle,
"hid_create_pm_components: END");
}
/*
* hid_is_pm_enabled
* Check if the device is pm enabled. Always enable
* pm on the new SUN mouse
*/
static int
hid_is_pm_enabled(dev_info_t *dip)
{
hid_state_t *hidp = ddi_get_soft_state(hid_statep,
ddi_get_instance(dip));
if (strcmp(ddi_node_name(dip), "mouse") == 0) {
/* check for overrides first */
if (hid_pm_mouse ||
(ddi_prop_exists(DDI_DEV_T_ANY, dip,
(DDI_PROP_DONTPASS | DDI_PROP_NOTPROM),
"hid-mouse-pm-enable") == 1)) {
return (USB_SUCCESS);
}
/*
* Always enable PM for 1.05 or greater SUN mouse
* hidp->hid_dev_descr won't be NULL.
*/
if ((hidp->hid_dev_descr->idVendor ==
HID_SUN_MOUSE_VENDOR_ID) &&
(hidp->hid_dev_descr->idProduct ==
HID_SUN_MOUSE_PROD_ID) &&
(hidp->hid_dev_descr->bcdDevice >=
HID_SUN_MOUSE_BCDDEVICE)) {
return (USB_SUCCESS);
}
} else {
return (USB_SUCCESS);
}
return (USB_FAILURE);
}
/*
* hid_save_device_state
* Save the current device/driver state.
*/
static void
hid_save_device_state(hid_state_t *hidp)
{
struct iocblk *mctlmsg;
mblk_t *mp;
queue_t *q;
USB_DPRINTF_L4(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_save_device_state");
if (!(HID_IS_OPEN(hidp)))
return;
if (hidp->hid_internal_flag == HID_STREAMS_OPEN) {
/*
* Send MCTLs up indicating that the device
* will loose its state
*/
q = hidp->hid_internal_rq;
mutex_exit(&hidp->hid_mutex);
if (canputnext(q)) {
mp = allocb(sizeof (struct iocblk), BPRI_HI);
if (mp != NULL) {
mp->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)
mp->b_datap->db_base;
mctlmsg->ioc_cmd = HID_DISCONNECT_EVENT;
mctlmsg->ioc_count = 0;
putnext(q, mp);
}
}
mutex_enter(&hidp->hid_mutex);
}
if (hidp->hid_external_flag == HID_STREAMS_OPEN) {
/*
* Send MCTLs up indicating that the device
* will loose its state
*/
q = hidp->hid_external_rq;
mutex_exit(&hidp->hid_mutex);
if (canputnext(q)) {
mp = allocb(sizeof (struct iocblk), BPRI_HI);
if (mp != NULL) {
mp->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)
mp->b_datap->db_base;
mctlmsg->ioc_cmd = HID_DISCONNECT_EVENT;
mctlmsg->ioc_count = 0;
putnext(q, mp);
}
}
mutex_enter(&hidp->hid_mutex);
}
mutex_exit(&hidp->hid_mutex);
/* stop polling on the intr pipe */
usb_pipe_stop_intr_polling(hidp->hid_interrupt_pipe, USB_FLAGS_SLEEP);
mutex_enter(&hidp->hid_mutex);
}
/*
* hid_restore_device_state:
* Set original configuration of the device.
* Reopen intr pipe.
* Enable wrq - this starts new transactions on the control pipe.
*/
static void
hid_restore_device_state(dev_info_t *dip, hid_state_t *hidp)
{
int rval;
hid_power_t *hidpm;
struct iocblk *mctlmsg;
mblk_t *mp;
queue_t *q;
hid_pm_busy_component(hidp);
mutex_enter(&hidp->hid_mutex);
USB_DPRINTF_L4(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_restore_device_state: %s",
usb_str_dev_state(hidp->hid_dev_state));
hidpm = hidp->hid_pm;
mutex_exit(&hidp->hid_mutex);
/* First bring the device to full power */
(void) pm_raise_power(dip, 0, USB_DEV_OS_FULL_PWR);
mutex_enter(&hidp->hid_mutex);
if (hidp->hid_dev_state == USB_DEV_ONLINE) {
/*
* We failed the checkpoint, there is no need to restore
* the device state
*/
mutex_exit(&hidp->hid_mutex);
hid_pm_idle_component(hidp);
return;
}
mutex_exit(&hidp->hid_mutex);
/* Check if we are talking to the same device */
if (usb_check_same_device(dip, hidp->hid_log_handle, USB_LOG_L2,
PRINT_MASK_ALL, USB_CHK_BASIC|USB_CHK_CFG, NULL) != USB_SUCCESS) {
/* change the device state from suspended to disconnected */
mutex_enter(&hidp->hid_mutex);
hidp->hid_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&hidp->hid_mutex);
hid_pm_idle_component(hidp);
goto nodev;
}
hid_set_idle(hidp);
hid_set_protocol(hidp, SET_REPORT_PROTOCOL);
mutex_enter(&hidp->hid_mutex);
/* if the device had remote wakeup earlier, enable it again */
if (hidpm->hid_wakeup_enabled) {
mutex_exit(&hidp->hid_mutex);
if ((rval = usb_handle_remote_wakeup(hidp->hid_dip,
USB_REMOTE_WAKEUP_ENABLE)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA,
hidp->hid_log_handle,
"usb_handle_remote_wakeup failed (%d)", rval);
}
mutex_enter(&hidp->hid_mutex);
}
/*
* restart polling on the interrupt pipe only if the device
* was previously operational (open)
*/
if (HID_IS_OPEN(hidp)) {
if ((rval = hid_start_intr_polling(hidp)) != USB_SUCCESS) {
USB_DPRINTF_L3(PRINT_MASK_ATTA, hidp->hid_log_handle,
"hid_restore_device_state:"
"unable to restart intr pipe poll"
" rval = %d ", rval);
/*
* change the device state from
* suspended to disconnected
*/
hidp->hid_dev_state = USB_DEV_DISCONNECTED;
mutex_exit(&hidp->hid_mutex);
hid_pm_idle_component(hidp);
goto nodev;
}
if (hidp->hid_dev_state == USB_DEV_DISCONNECTED) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"device is being re-connected");
}
/* set the device state ONLINE */
hidp->hid_dev_state = USB_DEV_ONLINE;
/* inform upstream modules that the device is back */
if (hidp->hid_internal_flag == HID_STREAMS_OPEN) {
q = hidp->hid_internal_rq;
mutex_exit(&hidp->hid_mutex);
if (canputnext(q)) {
mp = allocb(sizeof (struct iocblk), BPRI_HI);
if (mp != NULL) {
mp->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)
mp->b_datap->db_base;
mctlmsg->ioc_cmd = HID_CONNECT_EVENT;
mctlmsg->ioc_count = 0;
putnext(q, mp);
}
}
/* enable write side q */
qenable(WR(q));
mutex_enter(&hidp->hid_mutex);
}
if (hidp->hid_external_flag == HID_STREAMS_OPEN) {
q = hidp->hid_external_rq;
mutex_exit(&hidp->hid_mutex);
if (canputnext(q)) {
mp = allocb(sizeof (struct iocblk), BPRI_HI);
if (mp != NULL) {
mp->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)
mp->b_datap->db_base;
mctlmsg->ioc_cmd = HID_CONNECT_EVENT;
mctlmsg->ioc_count = 0;
putnext(q, mp);
}
}
/* enable write side q */
qenable(WR(q));
mutex_enter(&hidp->hid_mutex);
}
} else {
/* set the device state ONLINE */
hidp->hid_dev_state = USB_DEV_ONLINE;
}
mutex_exit(&hidp->hid_mutex);
hid_pm_idle_component(hidp);
return;
nodev:
/*
* Notify applications about device removal. This only
* applies to an external (aka. physical) open. Not sure how to
* notify consconfig to close the internal minor node.
*/
mutex_enter(&hidp->hid_mutex);
if ((q = hidp->hid_external_rq) == NULL) {
mutex_exit(&hidp->hid_mutex);
return;
}
mutex_exit(&hidp->hid_mutex);
mp = allocb(sizeof (uchar_t), BPRI_HI);
if (mp != NULL) {
mp->b_datap->db_type = M_ERROR;
mp->b_rptr = mp->b_datap->db_base;
mp->b_wptr = mp->b_rptr + sizeof (char);
*mp->b_rptr = ENODEV;
putnext(q, mp);
}
}
/*
* hid_qreply_merror:
* Pass an error message up.
*/
static void
hid_qreply_merror(queue_t *q, mblk_t *mp, uchar_t errval)
{
mp->b_datap->db_type = M_ERROR;
if (mp->b_cont) {
freemsg(mp->b_cont);
mp->b_cont = NULL;
}
mp->b_rptr = mp->b_datap->db_base;
mp->b_wptr = mp->b_rptr + sizeof (char);
*mp->b_rptr = errval;
qreply(q, mp);
}
/*
* hid_data2mblk:
* Form an mblk from the given data
*/
static mblk_t *
hid_data2mblk(uchar_t *buf, int len)
{
mblk_t *mp = NULL;
if (len >= 0) {
mp = allocb(len, BPRI_HI);
if (mp) {
bcopy(buf, mp->b_datap->db_base, len);
mp->b_wptr += len;
}
}
return (mp);
}
/*
* hid_flush :
* Flush data already sent upstreams to client module.
*/
static void
hid_flush(queue_t *q)
{
/*
* Flush pending data already sent upstream
*/
if ((q != NULL) && (q->q_next != NULL)) {
(void) putnextctl1(q, M_FLUSH, FLUSHR);
}
}
static void
hid_pm_busy_component(hid_state_t *hid_statep)
{
ASSERT(!mutex_owned(&hid_statep->hid_mutex));
if (hid_statep->hid_pm != NULL) {
mutex_enter(&hid_statep->hid_mutex);
hid_statep->hid_pm->hid_pm_busy++;
USB_DPRINTF_L4(PRINT_MASK_PM, hid_statep->hid_log_handle,
"hid_pm_busy_component: %d",
hid_statep->hid_pm->hid_pm_busy);
mutex_exit(&hid_statep->hid_mutex);
if (pm_busy_component(hid_statep->hid_dip, 0) != DDI_SUCCESS) {
mutex_enter(&hid_statep->hid_mutex);
hid_statep->hid_pm->hid_pm_busy--;
USB_DPRINTF_L2(PRINT_MASK_PM,
hid_statep->hid_log_handle,
"hid_pm_busy_component failed: %d",
hid_statep->hid_pm->hid_pm_busy);
mutex_exit(&hid_statep->hid_mutex);
}
}
}
static void
hid_pm_idle_component(hid_state_t *hid_statep)
{
ASSERT(!mutex_owned(&hid_statep->hid_mutex));
if (hid_statep->hid_pm != NULL) {
if (pm_idle_component(hid_statep->hid_dip, 0) == DDI_SUCCESS) {
mutex_enter(&hid_statep->hid_mutex);
ASSERT(hid_statep->hid_pm->hid_pm_busy > 0);
hid_statep->hid_pm->hid_pm_busy--;
USB_DPRINTF_L4(PRINT_MASK_PM,
hid_statep->hid_log_handle,
"hid_pm_idle_component: %d",
hid_statep->hid_pm->hid_pm_busy);
mutex_exit(&hid_statep->hid_mutex);
}
}
}
/*
* hid_pwrlvl0:
* Functions to handle power transition for various levels
* These functions act as place holders to issue USB commands
* to the devices to change their power levels
*/
static int
hid_pwrlvl0(hid_state_t *hidp)
{
hid_power_t *hidpm;
int rval;
struct iocblk *mctlmsg;
mblk_t *mp_lowpwr, *mp_fullpwr;
queue_t *q;
hidpm = hidp->hid_pm;
switch (hidp->hid_dev_state) {
case USB_DEV_ONLINE:
/* Deny the powerdown request if the device is busy */
if (hidpm->hid_pm_busy != 0) {
return (USB_FAILURE);
}
if (HID_IS_OPEN(hidp)) {
q = hidp->hid_inuse_rq;
mutex_exit(&hidp->hid_mutex);
if (canputnext(q)) {
/* try to preallocate mblks */
mp_lowpwr = allocb(
(int)sizeof (struct iocblk), BPRI_HI);
mp_fullpwr = allocb(
(int)sizeof (struct iocblk), BPRI_HI);
if ((mp_lowpwr != NULL) &&
(mp_fullpwr != NULL)) {
/* stop polling */
usb_pipe_stop_intr_polling(
hidp->hid_interrupt_pipe,
USB_FLAGS_SLEEP);
/*
* Send an MCTL up indicating that
* we are powering off
*/
mp_lowpwr->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)
mp_lowpwr->b_datap->db_base;
mctlmsg->ioc_cmd = HID_POWER_OFF;
mctlmsg->ioc_count = 0;
putnext(q, mp_lowpwr);
/* save the full powr mblk */
mutex_enter(&hidp->hid_mutex);
hidpm->hid_pm_pwrup = mp_fullpwr;
} else {
/*
* Since we failed to allocate one
* or more mblks, we fail attempt
* to go into low power this time
*/
freemsg(mp_lowpwr);
freemsg(mp_fullpwr);
mutex_enter(&hidp->hid_mutex);
return (USB_FAILURE);
}
} else {
/*
* Since we can't send an mblk up,
* we fail this attempt to go to low power
*/
mutex_enter(&hidp->hid_mutex);
return (USB_FAILURE);
}
}
mutex_exit(&hidp->hid_mutex);
/* Issue USB D3 command to the device here */
rval = usb_set_device_pwrlvl3(hidp->hid_dip);
ASSERT(rval == USB_SUCCESS);
mutex_enter(&hidp->hid_mutex);
hidp->hid_dev_state = USB_DEV_PWRED_DOWN;
hidpm->hid_current_power = USB_DEV_OS_PWR_OFF;
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
case USB_DEV_PWRED_DOWN:
default:
break;
}
return (USB_SUCCESS);
}
/* ARGSUSED */
static int
hid_pwrlvl1(hid_state_t *hidp)
{
int rval;
/* Issue USB D2 command to the device here */
rval = usb_set_device_pwrlvl2(hidp->hid_dip);
ASSERT(rval == USB_SUCCESS);
return (USB_FAILURE);
}
/* ARGSUSED */
static int
hid_pwrlvl2(hid_state_t *hidp)
{
int rval;
rval = usb_set_device_pwrlvl1(hidp->hid_dip);
ASSERT(rval == USB_SUCCESS);
return (USB_FAILURE);
}
static int
hid_pwrlvl3(hid_state_t *hidp)
{
hid_power_t *hidpm;
int rval;
struct iocblk *mctlmsg;
mblk_t *mp;
queue_t *q;
hidpm = hidp->hid_pm;
switch (hidp->hid_dev_state) {
case USB_DEV_HID_POWER_CHANGE:
case USB_DEV_PWRED_DOWN:
/* Issue USB D0 command to the device here */
rval = usb_set_device_pwrlvl0(hidp->hid_dip);
ASSERT(rval == USB_SUCCESS);
if (HID_IS_OPEN(hidp)) {
/* restart polling on intr pipe */
rval = hid_start_intr_polling(hidp);
if (rval != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_EVENTS,
hidp->hid_log_handle,
"unable to restart intr polling rval = %d",
rval);
return (USB_FAILURE);
}
/* Send an MCTL up indicating device in full power */
q = hidp->hid_inuse_rq;
mp = hidpm->hid_pm_pwrup;
hidpm->hid_pm_pwrup = NULL;
mutex_exit(&hidp->hid_mutex);
if (canputnext(q)) {
mp->b_datap->db_type = M_CTL;
mctlmsg = (struct iocblk *)
mp->b_datap->db_base;
mctlmsg->ioc_cmd = HID_FULL_POWER;
mctlmsg->ioc_count = 0;
putnext(q, mp);
} else {
freemsg(mp);
}
mutex_enter(&hidp->hid_mutex);
}
hidp->hid_dev_state = USB_DEV_ONLINE;
hidpm->hid_current_power = USB_DEV_OS_FULL_PWR;
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
case USB_DEV_ONLINE:
return (USB_SUCCESS);
default:
USB_DPRINTF_L2(PRINT_MASK_EVENTS, hidp->hid_log_handle,
"hid_pwrlvl3: Improper State");
return (USB_FAILURE);
}
}
/*
* hid_polled_input_init :
* This routine calls down to the lower layers to initialize any state
* information. This routine initializes the lower layers for input.
*/
static int
hid_polled_input_init(hid_state_t *hidp)
{
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_polled_input_init");
/*
* Call the lower layers to intialize any state information
* that they will need to provide the polled characters.
*/
if (usb_console_input_init(hidp->hid_dip, hidp->hid_interrupt_pipe,
&hidp->hid_polled_raw_buf,
&hidp->hid_polled_console_info) != USB_SUCCESS) {
/*
* If for some reason the lower layers cannot initialized, then
* bail.
*/
(void) hid_polled_input_fini(hidp);
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hid_polled_input_fini:
* This routine is called when we are done using this device as an input
* device.
*/
static int
hid_polled_input_fini(hid_state_t *hidp)
{
USB_DPRINTF_L4(PRINT_MASK_ALL, hidp->hid_log_handle,
"hid_polled_input_fini");
/*
* Call the lower layers to free any state information
* only if polled input has been initialised.
*/
if ((hidp->hid_polled_console_info) &&
(usb_console_input_fini(hidp->hid_polled_console_info) !=
USB_SUCCESS)) {
return (USB_FAILURE);
}
hidp->hid_polled_console_info = NULL;
return (USB_SUCCESS);
}
/*
* hid_polled_input_enter:
* This is the routine that is called in polled mode to save the USB
* state information before using the USB keyboard as an input device.
* This routine, and all of the routines that it calls, are responsible
* for saving any state information so that it can be restored when
* polling mode is over.
*/
static int
/* ARGSUSED */
hid_polled_input_enter(hid_polled_handle_t hid_polled_inputp)
{
hid_state_t *hidp = (hid_state_t *)hid_polled_inputp;
/*
* Call the lower layers to tell them to save any state information.
*/
(void) usb_console_input_enter(hidp->hid_polled_console_info);
return (USB_SUCCESS);
}
/*
* hid_polled_read :
* This is the routine that is called in polled mode when it wants to read
* a character. We will call to the lower layers to see if there is any
* input data available. If there is USB scancodes available, we will
* give them back.
*/
static int
hid_polled_read(hid_polled_handle_t hid_polled_input, uchar_t **buffer)
{
hid_state_t *hidp = (hid_state_t *)hid_polled_input;
uint_t num_bytes;
/*
* Call the lower layers to get the character from the controller.
* The lower layers will return the number of characters that
* were put in the raw buffer. The address of the raw buffer
* was passed down to the lower layers during hid_polled_init.
*/
if (usb_console_read(hidp->hid_polled_console_info,
&num_bytes) != USB_SUCCESS) {
return (0);
}
_NOTE(NO_COMPETING_THREADS_NOW);
*buffer = hidp->hid_polled_raw_buf;
_NOTE(COMPETING_THREADS_NOW);
/*
* Return the number of characters that were copied into the
* polled buffer.
*/
return (num_bytes);
}
/*
* hid_polled_input_exit :
* This is the routine that is called in polled mode when it is giving up
* control of the USB keyboard. This routine, and the lower layer routines
* that it calls, are responsible for restoring the controller state to the
* state it was in before polled mode.
*/
static int
hid_polled_input_exit(hid_polled_handle_t hid_polled_inputp)
{
hid_state_t *hidp = (hid_state_t *)hid_polled_inputp;
/*
* Call the lower layers to restore any state information.
*/
(void) usb_console_input_exit(hidp->hid_polled_console_info);
return (0);
}