/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright (c) 2014, Joyent, Inc. All rights reserved.
*/
/*
* UGEN: USB Generic Driver support code
*
* This code provides entry points called by the ugen driver or other
* drivers that want to export a ugen interface
*
* The "Universal Generic Driver" (UGEN) for USB devices provides interfaces
* to talk to USB devices. This is very useful for Point of Sale sale
* devices and other simple devices like USB scanner, USB palm pilot.
* The UGEN provides a system call interface to USB devices enabling
* a USB device vendor to write an application for his
* device instead of writing a driver. This facilitates the vendor to write
* device management s/w quickly in userland.
*
* with the device.
*
* XXX Theory of Operations
*/
#include <sys/sysmacros.h>
#include "sys/usb/clients/ugen/usb_ugen.h"
#include "sys/usb/usba/usba_ugen.h"
#include "sys/usb/usba/usba_ugend.h"
/* Debugging information */
/* default endpoint descriptor */
/* tunables */
int ugen_enable_pm = 0;
/* local function prototypes */
static int ugen_cleanup(ugen_state_t *);
static int ugen_cpr_suspend(ugen_state_t *);
static void ugen_cpr_resume(ugen_state_t *);
static void ugen_restore_state(ugen_state_t *);
static int ugen_strategy(struct buf *);
static void ugen_minphys(struct buf *);
static void ugen_pm_init(ugen_state_t *);
static void ugen_pm_destroy(ugen_state_t *);
static void ugen_pm_busy_component(ugen_state_t *);
static void ugen_pm_idle_component(ugen_state_t *);
/* endpoint xfer and status management */
static int ugen_epxs_init(ugen_state_t *);
static void ugen_epxs_destroy(ugen_state_t *);
static int ugen_epxs_minor_nodes_create(ugen_state_t *,
static int ugen_epxs_check_open_nodes(ugen_state_t *);
static void ugen_epx_shutdown(ugen_state_t *);
/* device status management */
static int ugen_ds_init(ugen_state_t *);
static void ugen_ds_destroy(ugen_state_t *);
static void ugen_ds_change(ugen_state_t *);
static int ugen_ds_minor_nodes_create(ugen_state_t *);
static void ugen_ds_poll_wakeup(ugen_state_t *);
/* utility functions */
static void ugen_minor_node_table_create(ugen_state_t *);
static void ugen_minor_node_table_destroy(ugen_state_t *);
static void ugen_minor_node_table_shrink(ugen_state_t *);
static int ugen_cr2lcstat(int);
static void ugen_free_devt(ugen_state_t *);
/*
* usb_ugen entry points
*
* usb_ugen_get_hdl:
* allocate and initialize handle
*/
{
KM_SLEEP);
int rval;
/* masks may not overlap */
return (NULL);
}
0)) != USB_SUCCESS) {
"usb_ugen_attach: usb_get_dev_data failed, rval=%d", rval);
return (NULL);
}
/* Initialize state structure for this instance */
char *name;
&ugen_errmask, &ugen_instance_debug, 0);
} else {
&ugen_errmask, &ugen_instance_debug, 0);
}
if (limit == 0) {
return (NULL);
}
if (limit == 0) {
return (NULL);
}
"usb_ugen_get_hdl: instance shift=%d instance limit=%d",
"usb_ugen_get_hdl: bits shift=%d bits limit=%d",
return ((usb_ugen_hdl_t)hdl);
}
/*
* usb_ugen_release_hdl:
* deallocate a handle
*/
void
{
if (usb_ugen_hdl_impl) {
if (ugenp) {
ugenp->ug_dev_data);
}
if (usb_ugen_hdl_impl->hdl_log_name) {
}
}
}
/*
* usb_ugen_attach()
*/
int
{
if (usb_ugen_hdl == NULL) {
return (USB_FAILURE);
}
"usb_ugen_attach: cmd=%d", cmd);
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (USB_SUCCESS);
default:
"usb_ugen_attach: unknown command");
return (USB_FAILURE);
}
/* Get maximum bulk transfer size supported by the HCD */
"usb_ugen_attach: Getting max bulk xfer sz failed");
goto fail;
}
/* table for mapping 48 bit minor codes to 9 bit index (for ugen) */
/* prepare device status node handling */
"usb_ugen_attach: preparing dev status failed");
goto fail;
}
/* prepare all available xfer and status endpoints nodes */
"usb_ugen_attach: preparing endpoints failed");
goto fail;
}
/* reduce table size if not all entries are used */
/* we are ready to go */
/* prepare PM */
}
/*
* if ugen driver, kill all child nodes otherwise set cfg fails
* if requested
*/
if (usb_owns_device(dip) &&
/* save cfgidx so we can restore on detach */
(void) ddi_remove_child(cdip, 0);
}
}
return (DDI_SUCCESS);
fail:
if (ugenp) {
"attach fail");
(void) ugen_cleanup(ugenp);
}
return (DDI_FAILURE);
}
/*
* usb_ugen_detach()
*/
int
{
if (usb_ugen_hdl) {
"usb_ugen_detach cmd %d", cmd);
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
break;
default:
break;
}
}
return (rval);
}
/*
* ugen_cleanup()
*/
static int
{
/* shutdown all endpoints */
/*
* 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. PM should power
* down while we are detaching
*
* The following ensures that any other driver
* activity must have drained (paranoia)
*/
USB_WAIT, 0);
/* dismantle in reverse order */
/* restore to initial configuration */
if (usb_owns_device(dip) &&
} else {
}
}
return (USB_SUCCESS);
}
/*
* ugen_cpr_suspend
*/
static int
{
int i;
int prev_state;
"ugen_cpr_suspend:");
switch (ugenp->ug_dev_state) {
case USB_DEV_ONLINE:
case USB_DEV_DISCONNECTED:
"ugen_cpr_suspend:");
if (ugenp->ug_open_count) {
/* drain outstanding cmds */
for (i = 0; i < ugen_busy_loop; i++) {
if (ugenp->ug_pending_cmds == 0) {
break;
}
}
/* if still outstanding cmds, fail suspend */
if (ugenp->ug_pending_cmds) {
"ugen_cpr_suspend: pending %d",
rval = USB_FAILURE;
break;
}
USB_WAIT, 0);
/* close all pipes */
}
/* wakeup devstat reads and polls */
rval = USB_SUCCESS;
break;
case USB_DEV_SUSPENDED:
default:
break;
}
return (rval);
}
/*
* ugen_cpr_resume
*/
static void
{
"ugen_cpr_resume:");
}
/*
* usb_ugen_disconnect_ev_cb:
*/
int
{
if (usb_ugen_hdl_impl == NULL) {
return (USB_FAILURE);
}
"usb_ugen_disconnect_ev_cb:");
/* get exclusive access */
if (ugenp->ug_open_count) {
/* close all pipes */
(void) ugen_epx_shutdown(ugenp);
}
/* wakeup devstat reads and polls */
return (USB_SUCCESS);
}
/*
* usb_ugen_reconnect_ev_cb:
*/
int
{
"usb_ugen_reconnect_ev_cb:");
return (USB_SUCCESS);
}
/*
* ugen_restore_state:
* Check for same device; if a different device is attached, set
* the device status to disconnected.
* If we were open, then set to UNAVAILABLE until all endpoints have
* be closed.
*/
static void
{
"ugen_restore_state");
/* first raise power */
}
/* Check if we are talking to the same device */
USB_FAILURE) {
/* wakeup devstat reads and polls */
}
return;
}
/*
* get exclusive access, we don't want to change state in the
* middle of some other actions
*/
switch (ugenp->ug_dev_state) {
case USB_DEV_DISCONNECTED:
break;
case USB_DEV_SUSPENDED:
break;
}
"ugen_restore_state: state=%d, opencount=%d",
/* wakeup devstat reads and polls */
}
}
/*
* usb_ugen_open:
*/
/* ARGSUSED */
int
{
int rval;
int minor_node_type;
if (usb_ugen_hdl == NULL) {
return (EINVAL);
}
return (EINVAL);
}
/* first check for legal open flags */
"usb_ugen_open: check failed, rval=%d", rval);
return (rval);
}
/* exclude other threads including other opens */
USB_WAIT_SIG, 0) <= 0) {
"usb_ugen_open: interrupted");
return (EINTR);
}
/* always allow open of dev stat node */
if (minor_node_type != UGEN_MINOR_DEV_STAT_NODE) {
/* if we are not online or powered down, fail open */
switch (ugenp->ug_dev_state) {
case USB_DEV_ONLINE:
break;
case USB_DEV_DISCONNECTED:
goto done;
case USB_DEV_SUSPENDED:
default:
goto done;
}
}
/* open node depending on type */
switch (minor_node_type) {
case UGEN_MINOR_EP_XFER_NODE:
}
if (rval == 0) {
ugenp->ug_open_count++;
} else {
}
}
break;
case UGEN_MINOR_EP_STAT_NODE:
if (rval == 0) {
ugenp->ug_open_count++;
}
break;
case UGEN_MINOR_DEV_STAT_NODE:
break;
default:
break;
}
done:
"usb_ugen_open: minor=0x%x rval=%d state=%d cnt=%d",
return (rval);
}
/*
* usb_ugen_close()
*/
/* ARGSUSED */
int
{
int minor_node_type;
if (usb_ugen_hdl == NULL) {
return (EINVAL);
}
return (EINVAL);
}
/* exclude other threads, including other opens */
USB_WAIT_SIG, 0) <= 0) {
"usb_ugen_close: interrupted");
return (EINTR);
}
/* close node depending on type */
switch (minor_node_type) {
case UGEN_MINOR_EP_XFER_NODE:
}
break;
case UGEN_MINOR_EP_STAT_NODE:
break;
case UGEN_MINOR_DEV_STAT_NODE:
break;
default:
return (EINVAL);
}
if (minor_node_type != UGEN_MINOR_DEV_STAT_NODE) {
if ((--ugenp->ug_open_count == 0) &&
(ugenp->ug_dev_state ==
/* wakeup devstat reads and polls */
}
}
"usb_ugen_close: minor=0x%x state=%d cnt=%d",
if (ugenp->ug_open_count == 0) {
}
return (0);
}
/*
* usb_ugen_read/write()
*/
/*ARGSUSED*/
int
{
if (usb_ugen_hdl == NULL) {
return (EINVAL);
}
return (EINVAL);
}
return (physio(ugen_strategy,
}
/*ARGSUSED*/
int
{
if (usb_ugen_hdl == NULL) {
return (EINVAL);
}
return (EINVAL);
}
return (physio(ugen_strategy,
}
/*
* usb_ugen_poll
*/
int
{
int minor_node_type;
if (usb_ugen_hdl == NULL) {
return (EINVAL);
}
return (EINVAL);
}
"usb_ugen_poll: "
"dev=0x%lx events=0x%x anyyet=0x%x rev=0x%p type=%d "
"devstat=0x%x devstate=0x%x",
*reventsp = 0;
switch (minor_node_type) {
case UGEN_MINOR_EP_XFER_NODE:
/* if interrupt IN ep and there is data, set POLLIN */
/*
* if we are not polling, force another
* read to kick off polling
*/
UGEN_EP_STATE_INTR_IN_POLLING_ON) == 0)) {
}
}
/*
* if we are not polling, force another
* read to kick off polling
*/
UGEN_EP_STATE_ISOC_IN_POLLING_ON) == 0)) {
}
}
} else {
/* no poll on other ep nodes */
}
break;
case UGEN_MINOR_DEV_STAT_NODE:
}
break;
case UGEN_MINOR_EP_STAT_NODE:
default:
break;
}
} else {
}
}
"usb_ugen_poll end: reventsp=0x%x", *reventsp);
return (0);
}
/*
* ugen_strategy
*/
static int
{
int rval = 0;
return (EINVAL);
}
ugenp->ug_pending_cmds++;
switch (minor_node_type) {
case UGEN_MINOR_EP_XFER_NODE:
break;
case UGEN_MINOR_EP_STAT_NODE:
break;
case UGEN_MINOR_DEV_STAT_NODE:
break;
default:
break;
}
ugenp->ug_pending_cmds--;
"ugen_strategy: "
"bp=0x%p cnt=%lu resid=%lu err=%d minor=0x%x rval=%d #cmds=%d",
if (rval) {
}
}
return (0);
}
/*
* ugen_minphys:
*/
static void
{
"ugen_phys: bp=0x%p dev=0x%lx index=%d type=0x%x",
switch (minor_node_type) {
case UGEN_MINOR_EP_XFER_NODE:
switch (UGEN_XFER_TYPE(epp)) {
case USB_EP_ATTR_BULK:
}
break;
case USB_EP_ATTR_INTR:
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_ISOCH:
default:
break;
}
break;
case UGEN_MINOR_EP_STAT_NODE:
case UGEN_MINOR_DEV_STAT_NODE:
default:
break;
}
}
/*
* Get bmAttributes and bAddress of the endpoint which is going to
* be opened
*/
static int
{
int ep;
bEndpointAddress) == epidx) {
return (USB_SUCCESS);
}
}
return (USB_FAILURE);
}
/*
* check whether flag is appropriate for node type
*/
static int
{
int rval = 0;
"ugen_check_open_flags: "
"dev=0x%lx, type=0x%x flag=0x%x idx=%" PRIu64,
switch (minor_node_type) {
case UGEN_MINOR_EP_XFER_NODE:
/*
* Endpoints in two altsetting happen to have the same
* bEndpointAddress, but they are different type, e.g,
* one is BULK and the other is ISOC. They use the same
* slot of ug_ep array. It's OK after switch_alt, because
* after alt switch, ep info is updated to the new endpoint.
* But it's not right here to use the other EP's info for
* checking.
*/
&bAddress)) != USB_SUCCESS) {
return (ENODEV);
}
} else {
}
"ugen_check_open_flags: epp = %p,"
"epp type = %d, bmAttr =0x%x, bAddr = 0x%02x", (void *)epp,
switch (bmAttribute & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
/* read and write must be set, ndelay not allowed */
}
break;
case USB_EP_ATTR_ISOCH:
/* read and write must be set */
}
break;
case USB_EP_ATTR_BULK:
/* ndelay not allowed */
break;
}
/*FALLTHRU*/
case USB_EP_ATTR_INTR:
/* check flag versus direction */
}
((bAddress & USB_EP_DIR_IN) == 0)) {
}
break;
default:
break;
}
break;
case UGEN_MINOR_DEV_STAT_NODE:
/* only reads are supported */
}
break;
case UGEN_MINOR_EP_STAT_NODE:
break;
default:
break;
}
return (rval);
}
/*
* endpoint management
*
* create/initialize all endpoint xfer/stat structures
*/
static int
{
"ugen_epxs_init:");
/* initialize each ep's mutex first */
}
/* init default ep as it does not have a descriptor */
"creating default endpoint failed");
return (USB_FAILURE);
}
/*
* walk all endpoints of all alternates of all interfaces of
* all cfs
*/
ep++) {
USB_SUCCESS) {
return (USB_FAILURE);
}
}
}
}
}
return (USB_SUCCESS);
}
/*
* initialize one endpoint structure
*/
static int
{
int ep_index;
/* is this the default endpoint */
"ugen_epxs_data_init: "
"cfgval=%d cfgidx=%d iface=%d alt=%d ep_index=%d",
/* initialize if not yet init'ed */
}
/* create minor nodes for all alts */
}
/*
* undo all endpoint initializations
*/
static void
{
int i;
for (i = 0; i < UGEN_N_ENDPOINTS; i++) {
}
}
static void
{
if (epp) {
"ugen_epxs_destroy: addr=0x%x",
}
}
}
/*
* create endpoint status and xfer minor nodes
*
* The actual minor node needs more than 18 bits. We create a table
* and store the full minor node in this table and use the
* index in the table as minor node. This allows 256 minor nodes
* and 1024 instances
*/
static int
{
int minor_index;
UGEN_OWNS_DEVICE : 0);
int ep_index =
int ep_addr =
int ep_type =
int ep_dir =
"ugen_epxs_minor_nodes_create: "
"cfgval=%d cfgidx=%d if=%d alt=%d ep=0x%x",
return (USB_FAILURE);
}
/* create stat and xfer minor node */
iface << UGEN_MINOR_IF_SHIFT |
alt << UGEN_MINOR_ALT_SHIFT |
if (minor_index < 0) {
"too many minor nodes, "
"cannot create %d.%d.%d.%x",
/* carry on regardless */
return (USB_SUCCESS);
}
if (ep_type == USB_EP_ATTR_CONTROL) {
type = "cntrl";
} else {
}
/*
* xfer ep node name:
* vid.pid.[in|out|cntrl].[<cfg>.][if<iface>.][<alt>.]<ep addr>
*/
if ((ep_addr == 0) && owns_device) {
}
return (USB_FAILURE);
}
if (minor_index < 0) {
"too many minor nodes, "
"cannot create %d.%d.%d.%x stat",
/* carry on regardless */
return (USB_SUCCESS);
}
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* close all non-default pipes and drain default pipe
*/
static void
{
int i;
"ugen_epx_shutdown:");
for (i = 0; i < UGEN_N_ENDPOINTS; i++) {
USB_WAIT, 0);
} else {
}
}
}
/*
* find cfg index corresponding to cfg value
*/
static int
{
int cfgidx;
return (cfgidx);
}
}
return (0);
}
/*
* check if any node is open
*/
static int
{
int i;
for (i = 1; i < UGEN_N_ENDPOINTS; i++) {
"ugen_epxs_check_open_nodes: epp=%d, ep_state=0x%x",
return (USB_SUCCESS);
}
}
return (USB_FAILURE);
}
/*
* check if we can switch alternate
*/
static int
{
int i;
for (i = 1; i < UGEN_N_ENDPOINTS; i++) {
"ugen_epxs_check_alt_switch: epp=%d, ep_state=0x%x",
/*
* if the endpoint is open and part of this cfg and interface
* then we cannot switch alternates
*/
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
}
/*
* implicit switch to new cfg and alt
* If a crummy device fails usb_get_cfg or usb_get_alt_if, we carry on
* regardless so at least the device can be opened.
*/
static int
{
int switched = 0;
"ugen_epxs_switch_cfg_alt: old cfgidx=%d, if=%d alt=%d",
"new cfgidx=%d, if=%d alt=%d ep_state=0x%x",
/* no need to switch if there is only 1 cfg, 1 iface and no alts */
(ugenp->ug_dev_data->
"no need for switching: n_cfg=%d n_alt=%d",
ugenp->ug_dev_data->
return (rval);
}
/* no switch for default endpoint */
return (rval);
}
USB_FLAGS_SLEEP) == USB_SUCCESS) {
if (new_cfgidx != cur_cfgidx) {
/*
* we can't change config if any node
* is open
*/
if (ugen_epxs_check_open_nodes(ugenp) ==
USB_SUCCESS) {
return (USB_BUSY);
}
/*
* we are going to do this synchronously to
* keep it simple.
* This should never hang forever.
*/
NULL)) != USB_SUCCESS) {
"implicit set cfg (%" PRId64
") failed (%d)",
return (rval);
}
switched++;
}
}
/*
* implicitly switch to new alternate if
* - we have not switched configuration (if we
* we switched config, the alternate must be 0)
* - n_alts is > 1
* - if the device supports get_alternate iface
*/
USB_FLAGS_SLEEP) == USB_SUCCESS))) {
new_cfgidx) != USB_SUCCESS) {
return (USB_BUSY);
}
USB_SUCCESS) {
"implicit set new alternate "
return (rval);
}
}
}
return (rval);
}
/*
* update endpoint descriptor in ugen_ep structure after
* switching configuration or alternate
*/
static void
{
int ep;
bEndpointAddress) ==
bEndpointAddress)) {
break;
}
}
}
/*
* Xfer endpoint management
*
* open an endpoint for xfers
*
* Return values: errno
*/
static int
{
int rval;
"ugen_epx_open: minor=0x%x flag=0x%x ep_state=0x%x",
/* implicit switch to new cfg & alt */
return (EBUSY);
}
USB_SUCCESS) {
}
return (usb_rval2errno(rval));
}
/*
* close an endpoint for xfers
*/
static void
{
}
/*
* open pipe for this endpoint
* If the pipe is an interrupt IN pipe, start polling immediately
*/
static int
{
"ugen_epx_open_pipe: epp=0x%p flag=%d state=0x%x",
/* if default pipe, just copy the handle */
} else {
/* open pipe */
if (rval == USB_SUCCESS) {
(usb_opaque_t)epp);
/*
* if interrupt IN pipe, and one xfer mode
* has not been set, start polling immediately
*/
(!(epp->ep_one_xfer)) &&
if ((rval = ugen_epx_intr_IN_start_polling(
} else {
/* allow for about 1 sec of data */
epp->ep_buf_limit =
}
}
/* set ep_buf_limit for isoc IN pipe */
max_size =
/*
* wMaxPacketSize bits 10..0 specifies maximum
* packet size, which can hold 1024 bytes. If
* bits 12..11 is non zero, max_size will be
* greater than 1024 and the endpoint is a
* high-bandwidth endpoint.
*/
if (max_size <= 1024) {
/*
* allowing about 1s data of highspeed and 8s
* data of full speed device
*/
max_size * 8;
} else {
/*
* allow for about 333 ms data for high-speed
* high-bandwidth data
*/
epp->ep_buf_limit =
}
epp->ep_isoc_in_inited = 0;
}
}
}
if (rval != USB_SUCCESS) {
}
return (rval);
}
/*
* close an endpoint pipe
*/
static void
{
"ugen_epx_close_pipe: epp=0x%p", (void *)epp);
/* free isoc pipe private data ep_isoc_info.isoc_pkt_descr. */
int len;
int n_pkt;
}
len);
}
epp->ep_isoc_in_inited = 0;
}
} else {
}
}
}
/*
* start endpoint xfer
*
* We first serialize at endpoint level for only one request at the time
*
* Return values: errno
*/
static int
{
int rval = 0;
/* single thread per endpoint, one request at the time */
0) {
return (EINTR);
}
switch (ugenp->ug_dev_state) {
case USB_DEV_ONLINE:
break;
case USB_DEV_DISCONNECTED:
break;
case USB_DEV_SUSPENDED:
break;
default:
break;
}
#ifndef __lock_lint
#endif
if (rval) {
return (rval);
}
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
} else {
}
break;
case USB_EP_ATTR_ISOCH:
} else {
}
break;
default:
}
/* if the xfer could not immediately be completed, block here */
"ugen_epx_req: interrupted ep=0x%" PRIx64,
/*
* blow away the request except for dflt pipe
* (this is prevented in USBA)
*/
}
break;
}
"ugen_epx_req: wakeup");
}
}
/* always set lcmd_status if there was a failure */
if ((rval != USB_SUCCESS) &&
}
"ugen_epx_req: done");
return (usb_rval2errno(rval));
}
/*
* handle control xfers
*/
static int
{
int rval;
"ugen_epx_ctrl_req: epp=0x%p state=0x%x bp=0x%p",
/* is this a read following a write with setup data? */
}
} else {
}
return (USB_SUCCESS);
}
/* discard old data if any */
}
/* allocate and initialize request */
return (USB_NO_RESOURCES);
}
/* assume an LE data stream */
/*
* is this a legal request? No accesses to device are
* allowed if we don't own the device
*/
goto fail;
}
/* filter out set_cfg and set_if standard requests */
switch (reqp->ctrl_bRequest) {
case USB_REQ_SET_CFG:
case USB_REQ_SET_IF:
goto fail;
default:
break;
}
}
/* is this from host to device? */
goto fail;
}
goto fail;
}
}
/* submit the request */
if (rval != USB_SUCCESS) {
goto fail;
}
done:
return (USB_SUCCESS);
fail:
return (rval);
}
/*
* callback for control requests, normal and exception completion
*/
static void
{
}
"ugen_epx_ctrl_req_cb:\n\t"
"epp=0x%p state=0x%x ph=0x%p reqp=0x%p cr=%d cb=0x%x",
/* save any data for the next read */
switch (reqp->ctrl_completion_reason) {
case USB_CR_OK:
break;
case USB_CR_PIPE_RESET:
break;
default:
}
break;
}
}
"ugen_epx_ctrl_req_cb: done");
}
/*
* handle bulk xfers
*/
static int
{
int rval;
"ugen_epx_bulk_req: epp=0x%p state=0x%x bp=0x%p",
return (USB_NO_RESOURCES);
}
/*
* the transfer count is limited in minphys with what the HCD can
* do
*/
/* copy data into bp for OUT pipes */
} else {
}
USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
} else {
}
return (rval);
}
/*
* normal and exception bulk request callback
*/
static void
{
"ugen_epx_bulk_req_cb: ph=0x%p reqp=0x%p cr=%d cb=0x%x",
/* epp might be NULL if we are closing the pipe */
if (epp) {
if (len) {
}
} else {
}
}
switch (reqp->bulk_completion_reason) {
case USB_CR_OK:
break;
case USB_CR_PIPE_RESET:
break;
default:
}
}
}
}
/*
* handle intr IN xfers
*/
static int
{
int len = 0;
"ugen_epx_intr_IN_req: epp=0x%p state=0x%x bp=0x%p",
/* can we satisfy this read? */
}
/*
* if polling not active, restart, and return failure
* immediately unless one xfer mode has been requested
* if there is some data, return a short read
*/
if (len == 0) {
if (!epp->ep_one_xfer) {
rval = USB_FAILURE;
if (epp->ep_lcmd_status ==
}
}
epp) != USB_SUCCESS) {
}
if (epp->ep_one_xfer) {
}
goto done;
goto done;
}
}
/*
* if there is data or FNDELAY, return available data
*/
} else {
}
} else {
/* otherwise just wait for data */
}
done:
}
if (*wait) {
}
"ugen_epx_intr_IN_req end: rval=%d bcount=%lu len=%d data=0x%p",
return (rval);
}
/*
* Start polling on interrupt endpoint, synchronously
*/
static int
{
/*
* if polling is being stopped, we restart polling in the
* interrrupt callback again
*/
return (rval);
}
"ugen_epx_intr_IN_start_polling: epp=0x%p state=0x%x",
if (epp->ep_one_xfer) {
} else {
}
uflag)) != USB_SUCCESS) {
"ugen_epx_intr_IN_start_polling: failed %d", rval);
}
if (rval != USB_SUCCESS) {
}
} else {
rval = USB_SUCCESS;
}
return (rval);
}
/*
* stop polling on an interrupt endpoint, asynchronously
*/
static void
{
"ugen_epx_intr_IN_stop_polling: epp=0x%p state=0x%x",
}
}
/*
* poll management
*/
static void
{
}
}
/*
* callback functions for interrupt IN pipe
*/
static void
{
/* pipe is closing */
goto done;
}
"ugen_epx_intr_IN_req_cb:\n\t"
"epp=0x%p state=0x%x ph=0x%p reqp=0x%p cr=%d cb=0x%x len=%ld",
/* coalesce the data into one mblk */
} else {
"msgpullup failed, discard data");
}
"setting ep_data");
}
switch (reqp->intr_completion_reason) {
case USB_CR_OK:
break;
case USB_CR_PIPE_RESET:
case USB_CR_STOPPED_POLLING:
break;
default:
"ugen_exp_intr_cb_req: lcmd_status=0x%x",
break;
}
/* any non-zero completion reason stops polling */
if ((reqp->intr_completion_reason) ||
(epp->ep_one_xfer)) {
}
/* is there a poll pending? should we stop polling? */
"ugen_epx_intr_IN_req_cb: data len=0x%lx",
/* if there is no space left, stop polling */
epp->ep_buf_limit)) {
}
}
/* can we satisfy the read now */
}
}
done:
}
/*
* handle intr OUT xfers
*/
static int
{
"ugen_epx_intr_OUT_req: epp=0x%p state=0x%x bp=0x%p",
return (USB_NO_RESOURCES);
}
/* copy data from bp */
USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
} else {
}
return (rval);
}
/*
* callback functions for interrupt OUT pipe
*/
static void
{
"ugen_epx_intr_OUT_req_cb: ph=0x%p reqp=0x%p cr=%d cb=0x%x",
/* epp might be NULL if we are closing the pipe */
if (epp) {
int len;
switch (reqp->intr_completion_reason) {
case USB_CR_OK:
break;
case USB_CR_PIPE_RESET:
break;
default:
}
}
}
}
/*
* handle isoc IN xfers
*/
static int
{
"ugen_epx_isoc_IN_req: epp=0x%p state=0x%x bp=0x%p",
/* check if the isoc in pkt info has been initialized */
rval = USB_FAILURE;
goto done;
}
/* For OUT endpoint, return pkts transfer status of last request */
return (rval);
}
n_pkt * sizeof (ugen_isoc_pkt_descr_t));
return (USB_SUCCESS);
}
/* read length should be the sum of pkt descrs and data length */
goto done;
}
/* can we satisfy this read? */
/*
* every msg block in ep_data must be the size of
* pkts_len(payload length) + pkt descrs len
*/
}
/*
* if polling not active, restart
* if there is some data, return the data
*/
if (len == 0) {
rval = USB_FAILURE;
epp)) != USB_SUCCESS) {
}
goto done;
goto done;
}
}
/*
* if there is data or FNDELAY, return available data
*/
/* can fulfill this read request */
} else {
/* otherwise just wait for data */
}
done:
/* data have been read */
/* remove the just read msg block */
if (mp) {
} else {
}
}
if (*wait) {
}
"ugen_epx_isoc_IN_req end: rval=%d bcount=%lu len=%d data=0x%p",
return (rval);
}
/*
* Start polling on isoc endpoint, asynchronously
*/
static int
{
/*
* if polling is being stopped, we restart polling in the
* isoc callback again
*/
return (rval);
}
"ugen_epx_isoc_IN_start_polling: epp=0x%p state=0x%x",
USB_FLAGS_NOSLEEP)) == NULL) {
"ugen_epx_isoc_IN_start_polling: alloc isoc "
"req failed");
return (USB_NO_RESOURCES);
}
/*
* isoc_pkts_length was defined to be ushort_t. This
* has been obsoleted by usb high speed isoc support.
* It is set here just for compatibility reason
*/
reqp->isoc_pkts_length = 0;
}
USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
"ugen_epx_isoc_IN_start_polling: failed %d", rval);
}
if (rval != USB_SUCCESS) {
}
} else {
rval = USB_SUCCESS;
}
return (rval);
}
/*
* stop polling on an isoc endpoint, asynchronously
*/
static void
{
"ugen_epx_isoc_IN_stop_polling: epp=0x%p state=0x%x",
}
}
/*
* poll management
*/
static void
{
}
}
/*
* callback functions for isoc IN pipe
*/
static void
{
/* pipe is closing */
goto done;
}
"ugen_epx_isoc_IN_req_cb: "
"epp=0x%p state=0x%x ph=0x%p reqp=0x%p cr=%d cb=0x%x len=%ld "
/* Too many packet errors during isoc transfer of this request */
"too many errors(%d) in this req, stop polling",
}
/* Data OK */
for (i = 0; i < n_pkt; i++) {
"pkt %d: len=%d status=%d actual_len=%d", i,
/* translate cr to ugen lcstat */
pkt_descr[i].isoc_pkt_status =
}
/* construct data buffer: pkt descriptors + payload */
"alloc msgblk failed, discard data");
} else {
/* pkt descrs first */
sizeof (ugen_isoc_pkt_descr_t) * n_pkt);
/* payload follows */
/* concatenate data bytes in mp2 */
/*
* now we get the required data:
* pkt descrs + payload
*/
} else {
"msgpullup status blk failed, "
"discard data");
}
}
"ISOC ep%x coalesce ep_data",
/* add mp1 to the tail of ep_data */
"setting ep_data");
}
}
switch (reqp->isoc_completion_reason) {
case USB_CR_OK:
break;
case USB_CR_PIPE_RESET:
case USB_CR_STOPPED_POLLING:
case USB_CR_PIPE_CLOSING:
break;
default:
"ugen_exp_isoc_cb_req: error lcmd_status=0x%x ",
break;
}
/* any non-zero completion reason signifies polling has stopped */
if (reqp->isoc_completion_reason) {
}
/* is there a poll pending? should we stop polling? */
"ugen_epx_isoc_IN_req_cb: data len=0x%lx, limit=0x%lx",
epp->ep_buf_limit);
/*
* Since isoc is unreliable xfer, if buffered data size exceeds
* the limit, we just discard and free data in the oldest mblk
*/
/* exceed buf lenth limit, remove the oldest one */
"ugen_epx_isoc_IN_req_cb: overflow!");
}
}
}
/* can we satisfy the read now */
}
}
done:
}
/*
* handle isoc OUT xfers or init isoc IN polling
*/
static int
{
char *p;
"ugen_epx_isoc_OUT_req: epp=0x%p state=0x%x bp=0x%p",
goto done;
}
/* LINTED E_BAD_PTR_CAST_ALIGN */
sizeof (int);
if ((n_pkt == 0) ||
"Invalid params: bcount=%lu, head_len=%d, pktcnt=%d",
goto done;
}
p += sizeof (int); /* points to pkt_descrs */
goto done;
}
p += sizeof (ugen_isoc_pkt_descr_t) * n_pkt;
/* total packet payload length */
}
/*
* write length may either be header length for isoc IN endpoint or
* the sum of header and data pkts length for isoc OUT endpoint
*/
"invalid length: bcount=%lu, head_len=%d, pkts_len = %d,"
goto done;
}
/* Set parameters for READ */
/* must be isoc IN endpoint */
n_pkt);
"write length invalid for OUT ep%x",
goto done;
}
if (epp->ep_isoc_in_inited) {
n_pkt);
"isoc IN polling fail: already inited, need to"
"close the ep before initing again");
goto done;
}
/* save pkts info for the READ */
epp)) != USB_SUCCESS) {
n_pkt);
"isoc IN start polling failed");
goto done;
}
epp->ep_isoc_in_inited++;
"isoc IN ep inited");
goto done;
}
/* must be isoc OUT endpoint */
"write length invalid for an IN ep%x",
goto done;
}
/* OUT endpoint, free previous info if there's any */
sizeof (ugen_isoc_pkt_descr_t) *
}
/* save pkts info for the WRITE */
"alloc isoc out req failed");
goto done;
}
}
/* copy data from bp */
USB_FLAGS_NOSLEEP)) != USB_SUCCESS) {
"isoc out xfer failed");
} else {
}
done:
"ugen_epx_isoc_OUT_req end: rval=%d bcount=%lu xfer_len=%d",
return (rval);
}
/*
* callback functions for isoc OUT pipe
*/
static void
{
"ugen_epx_isoc_OUT_req_cb: ph=0x%p reqp=0x%p cr=%d cb=0x%x",
/* epp might be NULL if we are closing the pipe */
if (epp) {
int len, i;
int headlen;
sizeof (ugen_isoc_pkt_descr_t);
switch (reqp->isoc_completion_reason) {
case USB_CR_OK:
for (i = 0; i < reqp->isoc_pkts_count; i++) {
pktdesc[i].isoc_pkt_status =
}
/* save the status info */
(sizeof (ugen_isoc_pkt_descr_t) *
break;
case USB_CR_PIPE_RESET:
break;
default:
}
}
}
}
/*
* Endpoint status node management
*
*
* Return values: errno
*/
static int
{
"ugen_eps_open: dev=0x%lx flag=0x%x state=0x%x",
/* only one open at the time */
rval = 0;
}
return (rval);
}
/*
* close endpoint status
*/
static void
{
"ugen_eps_close: dev=0x%lx flag=0x%x state=0x%x",
}
/*
* return status info
*
* Return values: errno
*/
static int
{
"ugen_eps_req: bp=0x%p lcmd_status=0x%x bcount=%lu",
if (len) {
}
} else {
"ugen_eps_req: control=0x%x",
"ugen_eps_req: cannot change one xfer mode if "
"endpoint is open");
return (EINVAL);
}
} else {
"ugen_eps_req: not an interrupt endpoint");
return (EINVAL);
}
}
return (0);
}
/*
* device status node management
*/
static int
{
/* Create devstat minor node for this instance */
"ugen_create_dev_stat_minor_nodes failed");
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
static void
{
}
/*
* open devstat minor node
*
* Return values: errno
*/
static int
{
/*
* first read on device node should return status
*/
return (0);
} else {
return (EBUSY);
}
}
static void
{
}
/*
* request for devstat
*
* Return values: errno
*/
static int
{
"ugen_ds_req: bp=0x%p", (void *)bp);
UGEN_DEV_STATUS_CHANGED) == 0) {
return (EINTR);
}
}
0) {
return (0);
}
switch (ugenp->ug_dev_state) {
case USB_DEV_ONLINE:
break;
case USB_DEV_DISCONNECTED:
break;
case USB_DEV_SUSPENDED:
break;
default:
break;
}
"ugen_ds_req: dev_state=0x%x dev_stat=0x%x",
return (0);
}
static void
{
"ugen_ds_change:");
}
/*
* poll management
*/
static void
{
"ugen_ds_poll_wakeup:");
}
}
/*
* minor node management:
*/
static int
{
int minor_index;
UGEN_OWNS_DEVICE : 0);
"ugen_ds_minor_nodes_create: idx shift=%d inst shift=%d",
return (USB_FAILURE);
}
/* create devstat minor node */
if (owns_device) {
} else {
}
if (minor_index < 0) {
"too many minor nodes");
return (USB_FAILURE);
}
"minor=0x%x minor_index=%d name=%s",
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* utility functions:
*
* conversion from completion reason to USB_LC_STAT_*
*/
static struct ugen_cr2lcstat_entry {
int cr;
int lcstat;
} ugen_cr2lcstat_table[] = {
{ USB_CR_OK, USB_LC_STAT_NOERROR },
{ USB_CR_CRC, USB_LC_STAT_CRC },
};
sizeof (struct ugen_cr2lcstat_entry))
static int
{
int i;
for (i = 0; i < UGEN_CR2LCSTAT_TABLE_SIZE; i++) {
return (ugen_cr2lcstat_table[i].lcstat);
}
}
return (USB_LC_STAT_UNSPECIFIED_ERR);
}
/*
* create and lookup minor index
*/
static int
{
int i;
/* check if already in the table */
return (-1);
}
}
if (ugenp->ug_minor_node_table_index <
"ugen_minor_index_create: %d: 0x%lx",
(unsigned long)minor);
return (ugenp->ug_minor_node_table_index++);
} else {
return (-1);
}
}
static ugen_minor_t
{
"ugen_devt2minor: minorindex=%lu, minor=0x%" PRIx64,
}
static int
{
(idx > 0)) {
return (USB_SUCCESS);
}
"ugen_is_valid_minor_node: invalid minorindex=%d", idx);
return (USB_FAILURE);
}
static void
{
/* allocate the max table size needed, we reduce later */
}
static void
{
/* reduce the table size to save some memory */
}
}
static void
{
if (ugenp->ug_minor_node_table) {
}
}
static void
{
uint_t i, j;
for (i = 0; i < UGEN_MINOR_NODE_SIZE; i++) {
if ((1 << i) & mask) {
break;
}
}
for (j = i; j < UGEN_MINOR_NODE_SIZE; j++) {
if (((1 << j) & mask) == 0) {
break;
}
}
*limit = (i == j) ? 0 : 1 << (j - i);
*shift = i;
}
/*
* power management:
*
* ugen_pm_init:
* Initialize power management and remote wakeup functionality.
* No mutex is necessary in this function as it's called only by attach.
*/
static void
{
"ugen_pm_init:");
/* Allocate the state structure */
/*
* If remote wakeup is not available you may not want to do
* power management.
*/
"ugen_pm_init: "
"created PM components");
if (pm_raise_power(dip, 0,
USB_DEV_OS_FULL_PWR) != DDI_SUCCESS) {
"ugen_pm_init: "
"raising power failed");
}
} else {
"ugen_pm_init: "
"create_pm_comps failed");
}
} else {
"failure enabling remote wakeup");
}
"ugen_pm_init: end");
}
/*
* ugen_pm_destroy:
* Shut down and destroy power management and remote wakeup functionality.
*/
static void
{
"ugen_pm_destroy:");
int rval;
USB_REMOTE_WAKEUP_DISABLE)) != USB_SUCCESS) {
"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.
*/
} else {
}
}
}
/*
* ugen_power :
* Power entry point, the workhorse behind pm_raise_power, pm_lower_power,
* usb_req_raise_power and usb_req_lower_power.
*/
/*ARGSUSED*/
int
{
if (usb_ugen_hdl == NULL) {
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
"usb_ugen_power: level=%d", level);
USB_WAIT, 0);
/*
* 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
*/
switch (ugenp->ug_dev_state) {
case USB_DEV_ONLINE:
break;
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
default:
"ugen_power: disconnected/suspended "
rval = USB_SUCCESS;
goto done;
}
/* Check if we are transitioning to a legal power level */
"ugen_power: illegal power level=%d "
goto done;
}
switch (level) {
case USB_DEV_OS_PWR_OFF :
switch (ugenp->ug_dev_state) {
case USB_DEV_ONLINE:
/* Deny the powerdown request if the device is busy */
break;
}
/* Issue USB D3 command to the device here */
break;
default:
rval = USB_SUCCESS;
break;
}
break;
case USB_DEV_OS_FULL_PWR :
/*
* PM framework tries to put us in full power during system
* shutdown.
*/
switch (ugenp->ug_dev_state) {
break;
default:
/* wakeup devstat reads and polls */
break;
}
break;
default:
/* Levels 1 and 2 are not supported to keep it simple. */
"ugen_power: power level %d not supported", level);
break;
}
done:
return (rval);
}
static void
{
"ugen_pm_busy_component failed: %d",
}
}
}
static void
{
"ugen_pm_idle_component: %d",
}
}
}
/*
* devt lookup support
* In ugen_strategy and ugen_minphys, we only have the devt and need
* the ugen_state pointer. Since we don't know instance mask, we can't
* easily derive a softstate pointer. Therefore, we use a list
*/
static void
{
sizeof (ugen_devt_list_entry_t), KM_SLEEP);
e->list_state = ugenp;
t = ugen_devt_list.list_next;
/* check if the entry is already in the list */
while (t) {
t = t->list_next;
}
/* add to the head of the list */
if (ugen_devt_list.list_next) {
}
ugen_devt_list.list_next = e;
}
static ugen_state_t *
{
return (ugenp);
}
index++;
}
t = ugen_devt_list.list_next;
while (t) {
ugenp = t->list_state;
return (ugenp);
}
t = t->list_next;
}
return (ugenp);
}
static void
{
prev = &ugen_devt_list;
if (e->list_next) {
}
kmem_free(e, sizeof (ugen_devt_list_entry_t));
} else {
prev = e;
}
}
}