wusb_ca.c revision ff0e937b36dcde1a47ff7b00aa76a491c0dc07a8
/*
* 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.
*/
/*
* WUSB cable association driver
*
* This driver implements the cable association mechanism defined in
* "Association Models Supplement to the Certified Wireless Universal
* Serial Bus Specification 1.0". Cable association compliant devices,
* i.e. with bInterfaceClass=0xEF, bInterfaceSubClass=0x03 and
* bInterfaceProtocol=0x01(compatible names: "usbif,classef.3.1") will
* be supported by this driver.
*
* The Cable Association Model uses the USB Cable-Based Association
* Framework (CBAF). The basic operation under this framework is:
*
* - The user connects the device to the host using a USB cable.
*
* - The host detects that the device supports the CBAF and it is capable
* of configuring WUSB CC(Connection Context).
*
* - The host sends its CHID to the device, along with other information
*
* - If the device has a valid CC with a matching CHID, the device will
* respond to the host with the CDID from the CC.
*
* - As of this driver implementation, no matter if the CDID returned
* from the device matches the CDID in the host's copy of CC, we choose
* to skip further explicit user conditioning, generate a new CC and send
* it to the device.
*
* - Upon receiving the CC, the device must store the CC in non-volatile
* memory, replacing any existing CC with a matching CHID if it exists.
*
* - First time association is complete: Host has securely transferred the CC
* to the device
*
* CBAF requires device to use the default control endpoint to exchange
* requests and data with host. Three control requests are defined by spec
* and supported by this driver:
* - GET_ASSOCIATION_INFORMATION
* - GET_ASSOCIATION_REQUEST
* - SET_ASSOCIATION_RESPONSE
*
*/
#define DEBUG
#endif
#define USBDRV_MAJOR_VER 2
#define USBDRV_MINOR_VER 0
/*
* Function Prototypes
*/
static int wusb_ca_disconnect_callback(dev_info_t *);
static int wusb_ca_reconnect_callback(dev_info_t *);
static int wusb_ca_cpr_suspend(dev_info_t *);
static void wusb_ca_cpr_resume(dev_info_t *);
static int wusb_ca_pm_busy_component(wusb_ca_state_t *);
static void wusb_ca_pm_idle_component(wusb_ca_state_t *);
static int wusb_ca_power(dev_info_t *, int, int);
static void wusb_ca_init_power_mgmt(wusb_ca_state_t *);
static void wusb_ca_destroy_power_mgmt(wusb_ca_state_t *);
static void wusb_ca_release_access(wusb_ca_state_t *);
static int wusb_ca_check_same_device(wusb_ca_state_t *);
/* _NOTE is an advice for locklint. Locklint checks lock use for deadlocks. */
/* module loading stuff */
struct cb_ops wusb_ca_cb_ops = {
wusb_ca_open, /* open */
wusb_ca_close, /* close */
nulldev, /* strategy */
nulldev, /* print */
nulldev, /* dump */
nodev, /* read */
nodev, /* write */
wusb_ca_ioctl, /* ioctl */
nulldev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* streamtab */
};
static struct dev_ops wusb_ca_ops = {
DEVO_REV, /* devo_rev, */
0, /* refcnt */
wusb_ca_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
wusb_ca_attach, /* attach */
wusb_ca_detach, /* detach */
nodev, /* reset */
&wusb_ca_cb_ops, /* driver operations */
NULL, /* bus operations */
wusb_ca_power, /* power */
ddi_quiesce_not_needed, /* quiesce */
};
static struct modldrv wusb_ca_modldrv = {
"WUSB Cable Association driver",
};
static struct modlinkage modlinkage = {
};
/* local variables */
/* Soft state structures */
#define WUSB_CA_INITIAL_SOFT_SPACE 1
static void *wusb_ca_statep;
/*
* Module-wide initialization routine.
*/
int
_init(void)
{
int rval;
sizeof (wusb_ca_state_t), WUSB_CA_INITIAL_SOFT_SPACE)) != 0) {
return (rval);
}
}
return (rval);
}
/*
* Module-wide tear-down routine.
*/
int
_fini(void)
{
int rval;
return (rval);
}
return (rval);
}
int
{
}
/*
* wusb_ca_info:
* Get minor number, soft state structure, etc.
*/
/*ARGSUSED*/
static int
{
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* wusb_ca_attach:
* Attach or resume.
*
* For attach, initialize state and device, including:
* state variables, locks, device node
* device registration with system
* power management, hotplugging
* For resume, restore device and state
*/
static int
{
char *devinst;
int devinstlen;
int status;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
/*
* Always return success to work around enumeration failures.
* This works around an issue where devices which are present
* before a suspend and absent upon resume could cause a system
* panic on resume.
*/
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
return (DDI_FAILURE);
}
"Attach: enter for attach");
USB_SUCCESS) {
"attach: usb_client_attach failed, error code:%d", status);
goto fail;
}
USB_PARSE_LVL_ALL, 0)) != USB_SUCCESS) {
"attach: usb_get_dev_data failed, error code:%d", status);
goto fail;
}
/* create minor node */
"wusb_ca", 0) != DDI_SUCCESS) {
"attach: Error creating minor node");
goto fail;
}
/* Put online before PM init as can get power managed afterward. */
/* initialize power management */
goto fail;
}
/* Report device */
return (DDI_SUCCESS);
fail:
if (wusb_cap) {
}
return (DDI_FAILURE);
}
/*
* wusb_ca_detach:
* detach or suspend driver instance
*
* Note: in detach, only contention threads is from pm and disconnnect.
*/
static int
{
int rval = DDI_FAILURE;
switch (cmd) {
case DDI_DETACH:
"Detach: enter for detach");
break;
case DDI_SUSPEND:
"Detach: enter for suspend");
default:
break;
}
}
/*
* wusb_ca_cleanup:
* clean up the driver state for detach
*/
static int
{
"Cleanup: enter");
if (wusb_cap->wusb_ca_locks_initialized) {
/* This must be done 1st to prevent more events from coming. */
/*
* At this point, no new activity can be initiated. The driver
* has disabled hotplug callbacks. The Solaris framework has
* disabled new opens on a device being detached, and does not
* allow detaching an open device.
*
* The following ensures that all driver activity has drained.
*/
/* All device activity has died down. */
/* start dismantling */
}
}
return (USB_SUCCESS);
}
/*ARGSUSED*/
static int
{
int rval = 0;
return (ENXIO);
}
"open: enter");
/*
* Keep it simple: one client at a time.
* Exclusive open only
*/
return (EBUSY);
}
/*
* This is in place so that a disconnect or CPR doesn't interfere with
* pipe opens.
*/
return (EINTR);
}
"open: Error raising power");
goto done;
}
/* Fail if device is no longer ready. */
goto done;
}
done:
if (rval != 0) {
} else {
/* Device is idle until it is used. */
}
return (rval);
}
/*ARGSUSED*/
static int
{
"close: enter");
/* Perform device session cleanup here. */
"close: cleaning up...");
/*
* when they are closed. We can't reset default pipe, but we
* can wait for all requests on it from this dip to drain.
*/
USB_FLAGS_SLEEP, NULL, 0);
return (0);
}
/*
* ioctl for cable association operations.
*/
/*ARGSUSED*/
static int
{
"ioctl enter");
switch (cmd) {
case CBAF_IOCTL_GET_ASSO_INFO:
break;
case CBAF_IOCTL_GET_ASSO_REQS:
break;
case CBAF_IOCTL_SET_HOST_INFO:
break;
break;
break;
case CBAF_IOCTL_SET_FAILURE:
break;
default:
}
return (*rval_p);
}
/*
* wusb_ca_disconnect_callback:
* Called when device hotplug-removed.
* Close pipes. (This does not attempt to contact device.)
* Set state to DISCONNECTED
*/
static int
{
"disconnect: enter");
/*
* Save any state of device or IO in progress required by
* wusb_ca_restore_device_state for proper device "thawing" later.
*/
return (USB_SUCCESS);
}
/*
* wusb_ca_reconnect_callback:
* Called with device hotplug-inserted
* Restore state
*/
static int
{
"reconnect: enter");
return (USB_SUCCESS);
}
/*
* wusb_ca_restore_device_state:
* Called during hotplug-reconnect and resume.
* reenable power management
* Verify the device is the same as before the disconnect/suspend.
* Restore device state
* Thaw any IO which was frozen.
* Quiesce device. (Other routines will activate if thawed IO.)
* Set device online.
* Leave device disconnected if there are problems.
*/
static void
{
"wusb_ca_restore_device_state: enter");
"wusb_ca_restore_device_state: Error raising power");
goto fail;
}
/* Check if we are talking to the same device */
goto fail;
}
if ((wusb_cap->wusb_ca_pm) &&
/* Failure here means device disappeared again. */
USB_SUCCESS) {
"device may or may not be accessible. "
"Please verify reconnection");
}
}
"wusb_ca_restore_device_state: end");
return;
fail:
/* change the device state from suspended to disconnected */
}
/*
* wusb_ca_cpr_suspend:
* Clean up device.
* Wait for any IO to finish, then close pipes.
* Quiesce device.
*/
static int
{
"suspend enter");
/* Serialize to prevent races with detach, open, device access. */
"suspend: Error raising power");
return (USB_FAILURE);
}
/*
* Set dev_state to suspended so other driver threads don't start any
* new I/O.
*/
/* Don't suspend if the device is open. */
"suspend: Device is open. Can't suspend");
return (USB_FAILURE);
}
"suspend: success");
return (USB_SUCCESS);
}
/*
* wusb_ca_cpr_resume:
*
* wusb_ca_restore_device_state marks success by putting device back online
*/
static void
{
"resume: enter");
/*
* A pm_raise_power in wusb_ca_restore_device_state will bring
* the power-up state of device into synch with the system.
*/
}
static int
{
int rval = DDI_SUCCESS;
DDI_SUCCESS) {
(void) pm_raise_power(
} else {
}
}
return (rval);
}
static void
{
DDI_SUCCESS) {
}
"wusb_ca_pm_idle_component: %d",
}
}
/*
* wusb_ca_power :
* Power entry point, the workhorse behind pm_raise_power, pm_lower_power,
* usb_req_raise_power and usb_req_lower_power.
*/
/* ARGSUSED */
static int
{
int rval = USB_SUCCESS;
"wusb_ca_power: enter: level = %d", level);
/*
* If we are disconnected/suspended, return success. Note that if we
* return failure, bringing down the system will hang when PM tries
* to power up all devices
*/
"wusb_ca_power: disconnected/suspended "
rval = USB_SUCCESS;
goto done;
}
goto done;
}
/* Check if we are transitioning to a legal power level */
"wusb_ca_power: illegal power level = %d "
goto done;
}
switch (level) {
case USB_DEV_OS_PWR_OFF :
/* fail attempt to go to low power if busy */
if (pm->wusb_ca_pm_busy) {
goto done;
}
}
break;
case USB_DEV_OS_FULL_PWR :
/*
* PM framework tries to put us in full power during system
* shutdown. Handle USB_DEV_PWRED_DOWN only.
*/
}
break;
/* Levels 1 and 2 are not supported by this driver to keep it simple. */
default:
"wusb_ca_power: power level %d not supported", level);
break;
}
done:
/* Generally return success to make PM succeed */
}
/*
* wusb_ca_init_power_mgmt:
* Initialize power management and remote wakeup functionality.
* No mutex is necessary in this function as it's called only by attach.
*/
static void
{
"init_power_mgmt enter");
/* Allocate the state structure */
&pwr_states) == USB_SUCCESS) {
"wusb_ca_init_power_mgmt: created PM components");
} else {
"wusb_ca_init_power_mgmt: create_pm_compts failed");
}
/*
* If remote wakeup is not available you may not want to do
* power management.
*/
} else {
"wusb_ca_init_power_mgmt: failure enabling remote wakeup");
}
"wusb_ca_init_power_mgmt: end");
}
/*
* wusb_ca_destroy_power_mgmt:
* Shut down and destroy power management and remote wakeup functionality.
*/
static void
{
"destroy_power_mgmt enter");
if (!wusb_capm) {
return;
}
(void) wusb_ca_pm_busy_component(wusb_cap);
int rval;
if (wusb_capm->wusb_ca_remote_wakeup) {
"wusb_ca_destroy_power_mgmt: "
"Error disabling rmt wakeup: rval = %d",
rval);
}
}
/*
* Since remote wakeup is disabled now,
* no one can raise power
* and get to device once power is lowered here.
*/
}
/*
* wusb_ca_serialize_access:
* Get the serial synchronization object before returning.
*
* Arguments:
* wusb_cap - Pointer to wusb_ca state structure
* waitsig - Set to:
* WUSB_CA_SER_SIG - to wait such that a signal can interrupt
* WUSB_CA_SER_NOSIG - to wait such that a signal cannot interrupt
*/
static int
{
int rval = 1;
while (wusb_cap->wusb_ca_serial_inuse) {
if (waitsig == WUSB_CA_SER_SIG) {
} else {
}
}
return (rval);
}
/*
* wusb_ca_release_access:
* Release the serial synchronization object.
*/
static void
{
}
/*
* wusb_ca_check_same_device:
* Check if the device connected to the port is the same as
* the previous device that was in the port. The previous device is
* represented by the dip on record for the port. Print a message
* if the device is different. Can block.
*
* return values:
* USB_SUCCESS: same device
* USB_INVALID_VERSION not same device
* USB_FAILURE: Failure processing request
*/
static int
{
int rval;
char *buf;
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_DEV, /* wValue */
0, /* wIndex */
USB_DEV_DESCR_SIZE, /* wLength */
0 /* request attributes */
};
/* get the "new" device descriptor */
if (rval != USB_SUCCESS) {
"wusb_ca_check_same_device: "
"getting device descriptor failed "
"rval=%d, cr=%d, cb=0x%x\n",
return (USB_FAILURE);
}
sizeof (usb_dev_descr_t));
/* Always check the device descriptor length. */
/* Always check the device descriptor. */
} else if (bcmp(orig_usb_dev_descr,
(char *)&usb_dev_descr, USB_DEV_DESCR_SIZE) != 0) {
}
/* if requested & this device has a serial number check and compare */
USB_MAXSTRINGLEN) == USB_SUCCESS) {
match =
}
}
"Device is not identical to the "
"previous one this port.\n"
"Please disconnect and reconnect");
return (USB_INVALID_VERSION);
}
return (USB_SUCCESS);
}
/* get association info */
int
{
int rval;
if (rval != USB_SUCCESS) {
return (EIO);
}
"empty pdata");
return (EIO);
}
if (rval <= 0) {
"parse data");
return (EIO);
}
if (rval != 0) {
"ddi_copyout");
return (EIO);
}
return (0);
}
/* get request array */
int
{
if (rval != 0) {
"ddi_copyin");
return (EIO);
}
if (rval != USB_SUCCESS) {
return (EIO);
}
"empty pdata");
return (EIO);
}
reqs_size = sizeof (wusb_cbaf_asso_req_t) *
for (i = 0; i < ca_info.NumAssociationRequests; i++) {
&(ca_reqs[i]), sizeof (wusb_cbaf_asso_req_t));
if (rval <= 0) {
"parse data");
return (EIO);
}
}
if (rval != 0) {
"ddi_copyout");
return (EIO);
}
return (0);
}
/* set host info */
int
{
int rval;
if (rval != 0) {
"ddi_copyin");
return (EIO);
}
"trans host info");
return (EIO);
}
if (rval != USB_SUCCESS) {
return (EIO);
}
return (0);
}
/* get device info */
int
{
int rval;
if (rval != USB_SUCCESS) {
return (EIO);
}
"empty pdata");
return (EIO);
}
"trans to device_info");
return (EIO);
}
sizeof (device_info), flag);
if (rval != 0) {
"ddi_copyout");
return (EIO);
}
return (0);
}
/* set connection to device */
int
{
int rval;
if (rval != 0) {
"ddi_copyin");
return (EIO);
}
"trans cc data");
return (EIO);
}
if (rval != USB_SUCCESS) {
return (EIO);
}
return (0);
}
/* set failure */
int
{
int rval;
if (rval != 0) {
"ddi_copyin");
return (EIO);
}
"trans cc fail");
return (EIO);
}
if (rval != USB_SUCCESS) {
return (EIO);
}
return (0);
}
static mblk_t *
{
return (NULL);
}
return (pdata);
}
static mblk_t *
{
return (NULL);
}
return (pdata);
}
static mblk_t *
{
return (NULL);
}
return (pdata);
}
static int
{
int i, plen;
void *paddr;
char *mode;
for (i = 0; i < 5; i++) {
sizeof (item))) <= 0) {
"parse item[%d] failed", i);
return (-1);
}
ptr += 4;
case attrLength:
mode = "l";
break;
case attrCDID:
mode = "16c";
break;
case attrBandGroups:
mode = "s";
break;
case attrLangID:
mode = "s";
break;
case attrDeviceFriendlyName:
mode = "l";
plen = 64 * sizeof (char);
break;
default:
return (-1);
}
return (-1);
}
}
return (0);
}