hubdi.c revision 6f6c7d2b51705d612c5f11ed385afd87c89c1a12
/*
* 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
*/
/*
*/
/*
* USBA: Solaris USB Architecture support for the hub
* including root hub
* Most of the code for hubd resides in this file and
* is shared between the HCD root hub support and hubd
*/
#define USBA_FRAMEWORK
#include <sys/kobj_lex.h>
/*
* External functions
*/
extern boolean_t consconfig_console_is_ready(void);
/*
* Prototypes for static functions
*/
static int usba_hubdi_bus_ctl(
void *arg,
void *result);
static int usba_hubdi_map_fault(
char *eventname,
void *bus_impldata),
void *arg,
dev_info_t **child);
void *arg);
static void hubd_register_cpr_callback(hubd_t *);
static void hubd_unregister_cpr_callback(hubd_t *);
/*
* Busops vector for USB HUB's
*/
struct bus_ops usba_hubdi_busops = {
nullbusmap, /* bus_map */
NULL, /* bus_get_intrspec */
NULL, /* bus_add_intrspec */
NULL, /* bus_remove_intrspec */
usba_hubdi_map_fault, /* bus_map_fault */
ddi_dma_map, /* bus_dma_map */
ddi_dma_mctl, /* bus_dma_ctl */
usba_hubdi_bus_ctl, /* bus_ctl */
ddi_bus_prop_op, /* bus_prop_op */
NULL, /* bus_post_event */
NULL, /* bus_intr_ctl */
hubd_bus_config, /* bus_config */
hubd_bus_unconfig, /* bus_unconfig */
NULL, /* bus_fm_init */
NULL, /* bus_fm_fini */
NULL, /* bus_fm_access_enter */
NULL, /* bus_fm_access_exit */
hubd_bus_power /* bus_power */
};
#define USB_HUB_INTEL_VID 0x8087
#define USB_HUB_INTEL_PID 0x0020
/*
* local variables
*/
static usba_list_entry_t usba_hubdi_list;
extern int modrootloaded;
/*
* initialize private data
*/
void
{
&hubdi_errmask, NULL, 0);
"usba_hubdi_initialization");
}
void
{
"usba_hubdi_destroy");
}
/*
* Called by an HUB to attach an instance of the driver
* make this instance known to USBA
* the HUB should initialize usba_hubdi structure prior
* to calling this interface
*/
int
{
/*
* add this hubdi instance to the list of known hubdi's
*/
return (DDI_SUCCESS);
}
/*
* Called by an HUB to detach an instance of the driver
*/
int
{
return (DDI_SUCCESS);
}
/*
* misc bus routines currently not used
*/
/*ARGSUSED*/
static int
{
return (DDI_FAILURE);
}
/*
* root hub support. the root hub uses the same devi as the HCD
*/
int
{
"root-hub") != NDI_SUCCESS) {
return (USB_FAILURE);
}
/*
* create and initialize a usba_device structure
*/
KM_SLEEP);
KM_SLEEP);
usba_device->usb_cfg_array_len[0] =
sizeof (root_hub_config_descriptor);
KM_SLEEP);
/*
* The bDeviceProtocol field of root hub device specifies,
* whether root hub is a High or Full speed usb device.
*/
} else {
}
/*
* For the root hub the default pipe is not yet open
*/
goto fail;
}
/*
* kill off all OBP children, they may not be fully
* enumerated
*/
while (child) {
(void) ddi_remove_child(child, 0);
}
/*
* "attach" the root hub driver
*/
goto fail;
}
return (USB_SUCCESS);
fail:
if (ph) {
}
if (root_hubd) {
}
return (USB_FAILURE);
}
int
{
/* was root hub attached? */
if (!(usba_is_root_hub(dip))) {
/* return success anyway */
return (USB_SUCCESS);
}
/*
* usba_hubdi_detach also closes the default pipe
* and removes properties so there is no need to
* do it here
*/
if (DEVI_IS_ATTACHING(dip)) {
"failure to unbind root hub after attach failure");
}
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* Actual Hub Driver support code:
* shared by root hub and non-root hubs
*/
/* Debugging support */
static uint_t hubdi_bus_config_debug = 0;
/*
* local variables:
*
* Amount of time to wait between resetting the port and accessing
* the device. The value is in microseconds.
*/
/*
* enumeration retry
*/
#define HUBD_PORT_RETRY 5
/*
* Stale hotremoved device cleanup delay
*/
#define HUBD_STALE_DIP_CLEANUP_DELAY 5000000
/*
* retries for USB suspend and resume
*/
#define HUBD_SUS_RES_RETRY 2
void *hubd_statep;
/*
* prototypes
*/
static void hubd_hotplug_thread(void *arg);
static void hubd_reset_thread(void *arg);
int iteration);
static ndi_event_definition_t hubd_ndi_event_defs[] = {
};
#define HUBD_N_NDI_EVENTS \
(sizeof (hubd_ndi_event_defs) / sizeof (ndi_event_definition_t))
static ndi_event_set_t hubd_ndi_events = {
/* events received from parent */
static usb_event_t hubd_events = {
};
/*
* hubd_get_soft_state() returns the hubd soft state
*
* WUSB support extends this function to support wire adapter class
* devices. The hubd soft state for the wire adapter class device
* would be stored in usb_root_hubd field of the usba_device structure,
* just as the USB host controller drivers do.
*/
hubd_t *
{
return (NULL);
}
return (usba_device->usb_root_hubd);
} else {
}
}
/*
* PM support functions:
*/
/*ARGSUSED*/
static void
{
}
}
}
/*ARGSUSED*/
static void
{
}
}
}
/*
* track power level changes for children of this instance
*/
static void
{
"hubd_set_child_pwrlvl: port=%d power=%d",
old_power = 0;
}
/* assign the port power */
"hubd_set_child_pwrlvl: new_power=%d old_power=%d",
/* we have the first child coming out of low power */
/* we have the last child going to low power */
}
}
/*
* given a child dip, locate its port number
*/
static usb_port_t
{
break;
}
}
return (port);
}
/*
* if the hub can be put into low power mode, return success
* NOTE: suspend here means going to lower power, not CPR suspend.
*/
static int
{
int total_power = 0;
return (USB_SUCCESS);
}
/*
* Don't go to lower power if haven't been at full power for enough
* time to let hotplug thread kickoff.
*/
return (USB_FAILURE);
}
}
"hubd_can_suspend: %d", total_power);
}
/*
* resume port depending on current device state
*/
static int
{
int retval = USB_FAILURE;
"hubd_resume_port: port=%d state=0x%x (%s)", port,
switch (hubd->h_dev_state) {
case USB_DEV_HUB_CHILD_PWRLVL:
/*
* This could be a bus ctl for a port other than the one
* that has a remote wakeup condition. So check.
*/
/* the port isn't suspended, so don't resume */
"hubd_resume_port: port=%d not suspended", port);
break;
}
/*
* Device has initiated a wakeup.
* Issue a ClearFeature(PortSuspend)
*/
port,
0, NULL, 0,
"ClearFeature(PortSuspend) fails "
"rval=%d cr=%d cb=0x%x", rval,
}
/* either way ack changes on the port */
break;
/*
* When hubd's connect event callback posts a connect
* event to its child, it results in this busctl call
* which is valid
*/
/* FALLTHRU */
case USB_DEV_ONLINE:
/*
* the port isn't suspended, or connected
* so don't resume
*/
"hubd_resume_port: port=%d not suspended", port);
break;
}
/*
* prevent kicking off the hotplug thread
*/
hubd->h_hotplug_thread++;
/* Now ClearFeature(PortSuspend) */
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0);
if (rval != USB_SUCCESS) {
"ClearFeature(PortSuspend) fails"
"rval=%d cr=%d cb=0x%x", rval,
} else {
/*
* As per spec section 11.9 and 7.1.7.7
* hub need to provide at least 20ms of
* resume signalling, and s/w provide 10ms of
* recovery time before accessing the port.
*/
if ((status & PORT_STATUS_PSS) == 0) {
/* the port did finally resume */
break;
}
}
}
/* allow hotplug thread again */
hubd->h_hotplug_thread--;
hubd_start_polling(hubd, 0);
break;
case USB_DEV_DISCONNECTED:
/* Ignore - NO Operation */
break;
case USB_DEV_SUSPENDED:
case USB_DEV_PWRED_DOWN:
default:
"Improper state for port Resume");
break;
}
return (retval);
}
/*
* suspend port depending on device state
*/
static int
{
int retval = USB_FAILURE;
"hubd_suspend_port: port=%d", port);
switch (hubd->h_dev_state) {
/*
* When hubd's connect event callback posts a connect
* event to its child, it results in this busctl call
* which is valid
*/
/* FALLTHRU */
case USB_DEV_HUB_CHILD_PWRLVL:
/*
* When one child is resuming, the other could timeout
* and go to low power mode, which is valid
*/
/* FALLTHRU */
case USB_DEV_ONLINE:
hubd->h_hotplug_thread++;
/*
* Some devices start an unprovoked resume. According to spec,
* normal resume time for port is 10ms. Wait for double that
* time, then check to be sure port is really suspended.
*/
/* Now SetFeature(PortSuspend) */
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0)) !=
USB_SUCCESS) {
"SetFeature(PortSuspend) fails"
"rval=%d cr=%d cb=0x%x",
}
/*
* some devices start an unprovoked resume
* wait and check port status after some time
*/
/* either ways ack changes on the port */
if (status & PORT_STATUS_PSS) {
/* the port is indeed suspended */
break;
} else {
"hubdi: port%d failed to be suspended!",
port);
}
}
hubd->h_hotplug_thread--;
hubd_start_polling(hubd, 0);
break;
case USB_DEV_DISCONNECTED:
/* Ignore - No Operation */
break;
case USB_DEV_SUSPENDED:
case USB_DEV_PWRED_DOWN:
default:
"Improper state for port Suspend");
break;
}
return (retval);
}
/*
*/
static void
{
"hubd_post_attach: port=%d result=%d",
/*
* Check if the child created wants to be power managed.
* If yes, the childs power level gets automatically tracked
* by DDI_CTLOPS_POWER busctl.
* If no, we set power of the new child by default
* to USB_DEV_OS_FULL_PWR. Because we should never suspend.
*/
}
}
}
static void
{
/*
* if the device is successfully detached and is the
* last device to detach, mark component as idle
*/
/*
* We set power of the detached child
* to 0, so that we can suspend if all
* our children are gone
*/
/* check for leaks on detaching */
}
} else {
}
}
/*
* hubd_post_power
* After the child's power entry point has been called
* we record its power level in our local struct.
* If the device has powered off, we suspend port
*/
static int
int result)
{
int retval = USB_SUCCESS;
"hubd_post_power: port=%d", port);
if (result == DDI_SUCCESS) {
/* record this power in our local struct */
/* now suspend the port */
/* make sure the port is resumed */
}
} else {
/* record old power in our local struct */
/*
* As this device failed to transition from
* power off state, suspend the port again
*/
}
}
return (retval);
}
/*
*/
static int
void *arg,
void *result)
{
struct attachspec *as;
struct detachspec *ds;
int retval = DDI_FAILURE;
/* flag that we are currently running bus_ctl */
hubd->h_bus_ctls++;
"usba_hubdi_bus_ctl:\n\t"
"dip=0x%p, rdip=0x%p, op=0x%x, arg=0x%p",
switch (op) {
case DDI_CTLOPS_ATTACH:
/* there is nothing to do at resume time */
break;
}
/* serialize access */
case DDI_PRE:
"DDI_PRE DDI_CTLOPS_ATTACH: dip=%p, port=%d",
/* Go busy here. Matching idle is DDI_POST case. */
/*
* if we suspended the port previously
* because child went to low power state, and
* someone unloaded the driver, the port would
* still be suspended and needs to be resumed
*/
if (rval == USB_SUCCESS) {
}
break;
case DDI_POST:
"DDI_POST DDI_CTLOPS_ATTACH: dip=%p, port=%d",
/* Matching idle call for DDI_PRE busy call. */
}
break;
case DDI_CTLOPS_DETACH:
/* there is nothing to do at suspend time */
break;
}
/* serialize access */
case DDI_PRE:
"DDI_PRE DDI_CTLOPS_DETACH: dip=%p port=%d",
/* Go busy here. Matching idle is DDI_POST case. */
break;
case DDI_POST:
"DDI_POST DDI_CTLOPS_DETACH: dip=%p port=%d",
/* Matching idle call for DDI_PRE busy call. */
break;
}
break;
default:
}
/* decrement bus_ctls count */
hubd->h_bus_ctls--;
return (retval);
}
/*
* hubd_config_one:
* enumerate one child according to 'port'
*/
static boolean_t
{
"hubd_config_one: started, hubd_reset_port = 0x%x", port);
/*
* this ensures one config activity per system at a time.
* we enter the parent PCI node to have this serialization.
* this also excludes ioctls and deathrow thread
*/
/* exclude other threads */
if (status & PORT_STATUS_CCS) {
port) == USB_SUCCESS);
}
} else {
}
if (online_child) {
"hubd_config_one: onlining child");
}
"hubd_config_one: exit");
return (found);
}
/*
* bus enumeration entry points
*/
static int
{
long port;
"hubd_bus_config: op=%d", op);
if (hubdi_bus_config_debug) {
flag |= NDI_DEVI_DEBUG;
}
if (op == BUS_CONFIG_ONE) {
char cname[80];
"hubd_bus_config: op=%d (BUS_CONFIG_ONE)", op);
/* split name into "name@addr" parts */
} else {
return (NDI_FAILURE);
}
if (found == 0) {
return (NDI_FAILURE);
}
}
return (rval);
}
static int
void *arg)
{
int circ;
int rval;
"hubd_bus_unconfig: op=%d", op);
if (hubdi_bus_config_debug) {
flag |= NDI_DEVI_DEBUG;
}
flag |= NDI_DEVI_REMOVE;
}
/* serialize access */
/* logically zap children's list */
}
/* fill in what's left */
if (usba_device == NULL) {
continue;
}
}
/* physically zap the children we didn't find */
/* zap the dip and usba_device structure as well */
}
}
"hubd_bus_unconfig: rval=%d", rval);
return (rval);
}
/* bus_power entry point */
static int
{
int retval = DDI_FAILURE;
"hubd_bus_power: dip=%p, impl_arg=%p, power_op=%d, arg=%p, "
switch (op) {
"hubd_bus_power: BUS_POWER_PRE_NOTIFICATION, port=%d",
port);
/* go to full power if we are powered down */
/*
* If this case completes normally, idle will be in
* hubd_bus_power / BUS_POWER_POST_NOTIFICATION
*/
/*
* raise power only if we have created the components
* and are currently in low power
*/
BUS_POWER_NEXUS_PWRUP, (void *)&bpn,
(void *)&pwrup_res);
break;
}
}
/* indicate that child is changing power level */
if ((bpc->bpc_olevel == 0) &&
/*
* this child is transitioning from power off
* to power on state - resume port
*/
if (rval == USB_SUCCESS) {
} else {
/* reset this flag on failure */
}
} else {
}
break;
"hubd_bus_power: BUS_POWER_POST_NOTIFICATION, port=%d",
port);
/* record child's pwr and suspend port if required */
if (rval == USB_SUCCESS) {
}
/*
* Matching idle for the busy in
* hubd_bus_power / BUS_POWER_PRE_NOTIFICATION
*/
break;
default:
break;
}
return (retval);
}
/*
* functions to handle power transition for OS levels 0 -> 3
*/
static int
{
/* We can't power down if hotplug thread is running */
return (USB_FAILURE);
}
switch (hubd->h_dev_state) {
case USB_DEV_ONLINE:
/*
* To avoid race with bus_power pre_notify on check over
* dev_state, we need to correctly set the dev state
* before the mutex is dropped in stop polling.
*/
/*
* if we are the root hub, do not stop polling
* otherwise, we will never see a resume
*/
/* place holder to implement Global Suspend */
"Global Suspend: Not Yet Implemented");
} else {
}
/* Issue USB D3 command to the device here */
break;
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
case USB_DEV_PWRED_DOWN:
default:
break;
}
return (USB_SUCCESS);
}
/* ARGSUSED */
static int
{
/* Issue USB D2 command to the device here */
return (USB_FAILURE);
}
/* ARGSUSED */
static int
{
/* Issue USB D1 command to the device here */
return (USB_FAILURE);
}
static int
{
int rval;
switch (hubd->h_dev_state) {
case USB_DEV_PWRED_DOWN:
/* implement global resume here */
"Global Resume: Not Yet Implemented");
}
/* Issue USB D0 command to the device here */
hubd_start_polling(hubd, 0);
/* FALLTHRU */
case USB_DEV_ONLINE:
/* we are already in full power */
/* FALLTHRU */
case USB_DEV_DISCONNECTED:
case USB_DEV_SUSPENDED:
/*
* PM framework tries to put you in full power
* during system shutdown. If we are disconnected
* return success. Also, we should not change state
* when we are disconnected or suspended or about to
* transition to that state
*/
return (USB_SUCCESS);
default:
return (USB_FAILURE);
}
}
/* power entry point */
/* ARGSUSED */
int
{
int retval;
int circ;
"usba_hubdi_power: level=%d", level);
/* check if we are transitioning to a legal power level */
"usba_hubdi_power: illegal power level=%d "
return (DDI_FAILURE);
}
switch (level) {
case USB_DEV_OS_PWR_OFF:
break;
case USB_DEV_OS_PWR_1:
break;
case USB_DEV_OS_PWR_2:
break;
case USB_DEV_OS_FULL_PWR:
break;
}
}
/* power entry point for the root hub */
int
{
}
/*
* standard driver entry points support code
*/
int
{
int i, rval;
int minor;
const char *root_hub_drvname;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* Allocate softc information.
*/
if (usba_is_root_hub(dip)) {
/* soft state has already been allocated */
/* generate readable labels for different root hubs */
log_name = "eusb";
log_name = "uusb";
} else {
/* std. for ohci */
log_name = "usb";
}
} else {
minor = 0;
if (rval != DDI_SUCCESS) {
"cannot allocate soft state (%d)", instance);
goto fail;
}
goto fail;
}
}
&hubd_errmask, &hubd_instance_debug, 0);
if ((child_port_status == USBA_FULL_SPEED_DEV) &&
"Use of a USB1.0 hub behind a high speed port may "
"cause unexpected failures");
}
/* register with USBA as client driver */
"client attach failed");
goto fail;
}
USB_PARSE_LVL_IF, 0) != USB_SUCCESS) {
"cannot get dev_data");
goto fail;
}
"no interrupt IN endpoint found");
goto fail;
}
/*
* register this hub instance with usba
*/
if (rval != USB_SUCCESS) {
"usba_hubdi_register failed");
goto fail;
}
/* now create components to power manage this device */
/*
* Event handling: definition and registration
*
* first the definition:
* get event handle
*/
/* bind event set to the handle */
NDI_SLEEP)) {
"binding event set failed");
goto fail;
}
/* event registration */
"hubd_register_events failed");
goto fail;
}
goto fail;
}
"hub-ignore-power-budget") == 1) {
} else {
/* initialize hub power budget variables */
"hubd_init_power_budget failed");
goto fail;
}
}
/* initialize and create children */
"hubd_check_ports failed");
goto fail;
}
/*
* create cfgadm nodes
*/
char ap_name[HUBD_APID_NAMELEN];
hubd->h_ancestry_str, i);
"ap_name=%s", ap_name);
DDI_NT_USB_ATTACHMENT_POINT, 0) != DDI_SUCCESS) {
"cannot create attachment point node (%d)",
instance);
goto fail;
}
}
/* create minor nodes */
"cannot create devctl minor node (%d)", instance);
goto fail;
}
"usb-port-count update failed");
}
/*
* host controller driver has already reported this dev
* if we are the root hub
*/
if (!usba_is_root_hub(dip)) {
}
/* enable deathrow thread */
return (DDI_SUCCESS);
fail:
{
}
if (hubd) {
if (rval != USB_SUCCESS) {
"failure to complete cleanup after attach failure");
}
}
return (DDI_FAILURE);
}
int
{
int rval;
"hubd_detach: cmd=0x%x", cmd);
switch (cmd) {
case DDI_DETACH:
case DDI_SUSPEND:
default:
return (DDI_FAILURE);
}
}
/*
* hubd_setdevaddr
* set the device addrs on this port
*/
static int
{
int rval;
int retry = 0;
long time_delay;
"hubd_setdevaddr: port=%d", port);
/* close the default pipe with addr x */
/*
* As this device has been reset, temporarily
* assign the default address
*/
/* open child's default pipe with USBA_DEFAULT_ADDR */
USB_SUCCESS) {
"hubd_setdevaddr: Unable to open default pipe");
break;
}
/* Set the address of the device */
USB_REQ_SET_ADDRESS, /* bRequest */
address, /* wValue */
0, /* wIndex */
0, /* wLength */
NULL, 0,
"hubd_setdevaddr(%d): rval=%d cr=%d cb_fl=0x%x",
}
if (rval == USB_SUCCESS) {
break;
}
}
/* Reset to the old address */
return (rval);
}
/*
* hubd_setdevconfig
* set the device addrs on this port
*/
static void
{
int rval;
"hubd_setdevconfig: port=%d", port);
/* open the default control pipe */
USB_SUCCESS) {
/* Set the default configuration of the device */
USB_REQ_SET_CFG, /* bRequest */
config_value, /* wValue */
0, /* wIndex */
0, /* wLength */
NULL, 0,
"hubd_setdevconfig: set device config failed: "
"cr=%d cb_fl=0x%x rval=%d",
}
/*
* After setting the configuration, we make this default
* control pipe persistent, so that it gets re-opened
* on posting a connect event
*/
} else {
"pipe open fails: rval=%d", rval);
}
}
/*ARGSUSED*/
static int
{
int circ;
/*
* make sure dip is a usb hub, major of root hub is HCD
* major
*/
if (!usba_is_root_hub(dip)) {
/*
* need to walk the children since it might be a
* HWA device
*/
return (DDI_WALK_CONTINUE);
}
/* TODO: DWA device may also need special handling */
!i_ddi_devi_attached(dip)) {
return (DDI_WALK_PRUNECHILD);
}
}
return (DDI_WALK_PRUNECHILD);
}
/* walk child list and remove nodes with flag DEVI_DEVICE_REMOVED */
/* for normal usb hub or root hub */
continue;
}
B_TRUE);
}
} else {
/* for HWA */
return (DDI_WALK_PRUNECHILD);
}
} else {
return (DDI_WALK_PRUNECHILD);
}
}
/* skip siblings of root hub */
if (usba_is_root_hub(dip)) {
return (DDI_WALK_PRUNESIB);
}
return (DDI_WALK_CONTINUE);
}
/*
* this thread will walk all children under the root hub for this
* USB bus instance and attempt to remove them
*/
static void
hubd_root_hub_cleanup_thread(void *arg)
{
int circ;
#ifndef __lock_lint
"USB root hub");
#endif
for (;;) {
/* don't race with detach */
root_hubd->h_cleanup_needed = 0;
NULL);
#ifdef __lock_lint
#endif
/* quit if we are not enabled anymore */
break;
}
#ifndef __lock_lint
#endif
}
#ifndef __lock_lint
#endif
}
void
{
/*
* The usb_root_hub_dip pointer for the child hub of the WUSB
* wire adapter class device points to the wire adapter, not
* the root hub. Need to find the real root hub dip so that
* the cleanup thread only starts from the root hub.
*/
while (!usba_is_root_hub(rh_dip)) {
"hubd_schedule_cleanup: null rh dip");
return;
}
} else {
"hubd_schedule_cleanup: cannot find root hub");
return;
}
}
(void) thread_create(NULL, 0,
} else {
}
}
/*
* hubd_restore_device_state:
* - set config for the hub
* - power cycle all the ports
* - for each port that was connected
* - reset port
* - assign addrs to the device on this port
* - restart polling
* - reset suspend flag
*/
static void
{
int rval;
int retry;
"hubd_restore_device_state:");
/* First bring the device to full power */
if (!usba_is_root_hub(dip) &&
/* change the device state to disconnected */
return;
}
/* First turn off all port power */
if (rval != USB_SUCCESS) {
"hubd_restore_device_state:"
"turning off port power failed");
}
/* Settling time before turning on again */
/* enable power on all ports so we can see connects */
"hubd_restore_device_state: turn on port power failed");
/* disable whatever was enabled */
(void) hubd_disable_all_port_power(hubd);
return;
}
/*
* wait at least 3 frames before accessing devices
* (note that delay's minimal time is one clock tick which
* is 10ms unless hires_tick has been changed)
*/
"hubd_restore_device_state: port=%d", port);
/*
* the childen_dips list may have dips that have been
* already deallocated. we only get a post_detach notification
* but not a destroy notification
*/
if (ch_dip) {
/* get port status */
/* check if it is truly connected */
if (status & PORT_STATUS_CCS) {
/*
* Now reset port and assign the device
* its original address
*/
retry = 0;
do {
/* required for ppx */
if (retry) {
hubd_device_delay/2));
}
retry++;
} while ((rval != USB_SUCCESS) &&
(retry < hubd_retry_enumerate));
if (hub_prev_state == USB_DEV_DISCONNECTED) {
/* post a connect event */
} else {
/*
* Since we have this device connected
* mark it reinserted to prevent
* cleanup thread from stepping in.
*/
/*
* reopen pipes for children for
* their DDI_RESUME
*/
}
} else {
/*
* Mark this dip for deletion as the device
* is not physically present, and schedule
* cleanup thread upon post resume
*/
"hubd_restore_device_state: "
"dip=%p on port=%d marked for cleanup",
}
} else if (ehci_root_hub) {
/* get port status */
/* check if it is truly connected */
if (status & PORT_STATUS_CCS) {
/*
* reset the port to find out if we have
* 2.0 device connected or 1.X. A 2.0
* device will still be seen as connected,
* while a 1.X device will switch over to
* the companion controller.
*/
if (status &
(PORT_STATUS_CCS | PORT_STATUS_HSDA)) {
/*
* We have a USB 2.0 device
* connected. Power cycle this port
* so that hotplug thread can
* enumerate this device.
*/
} else {
"hubd_restore_device_state: "
"device on port %d switched over",
port);
}
}
}
}
/* if the device had remote wakeup earlier, enable it again */
}
hubd_start_polling(hubd, 0);
}
/*
* hubd_cleanup:
* cleanup hubd and deallocate. this function is called for
* handling attach failures and detaching including dynamic
* reconfiguration. If called from attaching, it must clean
* up the whole thing and return success.
*/
/*ARGSUSED*/
static int
{
#ifdef DEBUG
#endif
"hubd_cleanup:");
goto done;
}
/* ensure we are the only one active */
/* Cleanup failure is only allowed if called from detach */
if (DEVI_IS_DETACHING(dip)) {
/*
* We are being called from detach.
* Fail immediately if the hotplug thread is running
* else set the dev_state to disconnected so that
* hotplug thread just exits without doing anything.
*/
hubd->h_hotplug_thread) {
"- failing detach");
return (USB_FAILURE);
}
/*
* if the deathrow thread is still active or about
* to become active, fail detach
* the roothup can only be detached if nexus drivers
* are unloaded or explicitly offlined
*/
if (hubd->h_cleanup_needed ||
hubd->h_cleanup_active) {
"hubd_cleanup: deathrow still active?"
"- failing detach");
return (USB_FAILURE);
}
}
}
"hubd_cleanup: stop polling");
hubd->h_hotplug_thread) == 0);
/*
* deallocate events, if events are still registered
* (ie. children still attached) then we have to fail the detach
*/
if (hubd->h_ndi_event_hdl) {
if (DEVI_IS_ATTACHING(dip)) {
/* It must return success if attaching. */
} else if (rval != NDI_SUCCESS) {
"hubd_cleanup: ndi_event_free_hdl failed");
return (USB_FAILURE);
}
}
#ifdef DEBUG
}
#endif
}
/*
* 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.
* Note that cleanup can't fail after deregistration of events.
*/
}
/* restore the old dev state so that device can be put into low power */
/*
* Bring the hub to full power before
* issuing the disable remote wakeup command
*/
USB_REMOTE_WAKEUP_DISABLE)) != USB_SUCCESS) {
"hubd_cleanup: disable remote wakeup "
"fails=%d", rval);
}
}
}
if (hubpm) {
if (hubpm->hubp_child_pwrstate) {
MAX_PORTS + 1);
}
}
"hubd_cleanup: freeing space");
}
}
}
if (usba_is_root_hub(dip)) {
}
done:
if (hubd->h_ancestry_str) {
}
if (!usba_is_root_hub(dip)) {
}
return (USB_SUCCESS);
}
/*
* hubd_determine_port_connection:
* Determine which port is in connect status but does not
* have connect status change bit set, and mark port change
* bit accordingly.
* This function is applied during hub attach time.
*/
static usb_port_mask_t
{
&change, 0);
/* Check if port is in connect status */
if (!(status & PORT_STATUS_CCS)) {
continue;
}
/*
* Check if port Connect Status Change bit has been set.
* If already set, the connection will be handled by
* intr polling callback, not during attach.
*/
if (change & PORT_CHANGE_CSC) {
continue;
}
}
return (port_change);
}
/*
* hubd_check_ports:
* - get hub descriptor
* - check initial port status
* - enable power on all ports
* - enable polling on ep1
*/
static int
{
int rval;
/*
* First turn off all port power
*/
/* disable whatever was enabled */
(void) hubd_disable_all_port_power(hubd);
return (rval);
}
/*
* do not switch on immediately (instantly on root hub)
* and allow time to settle
*/
/*
* enable power on all ports so we can see connects
*/
/* disable whatever was enabled */
(void) hubd_disable_all_port_power(hubd);
return (rval);
}
/* wait at least 3 frames before accessing devices */
/*
* allocate arrays for saving the dips of each child per port
*
* ports go from 1 - n, allocate 1 more entry
*/
sizeof (hubd_hotplug_arg_t), KM_SLEEP);
return (rval);
}
hubd_start_polling(hubd, 0);
/*
* Some hub devices, like the embedded hub in the CKS ErgoMagic
* keyboard, may only have connection status bit set, but not
* have connect status change bit set when a device has been
* connected to its downstream port before the hub is enumerated.
* Then when the hub is in enumeration, the devices connected to
* it cannot be detected by the intr pipe and won't be enumerated.
* We need to check such situation here and enumerate the downstream
* devices for such hubs.
*/
if (port_change) {
"hubd_check_ports: port change=0x%x, need to connect",
(void *)arg, 0) == USB_SUCCESS) {
hubd->h_hotplug_thread++;
} else {
/* mark this device as idle */
}
} else {
}
"hubd_check_ports done");
return (USB_SUCCESS);
}
/*
* hubd_get_hub_descriptor:
*/
static int
{
int rval;
usb_req_attrs_t attr = 0;
"hubd_get_hub_descriptor:");
}
/* get hub descriptor length first by requesting 8 bytes only */
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_HUB, /* wValue */
0, /* wIndex */
8, /* wLength */
&data, 0,
"get hub descriptor failed: cr=%d cb_fl=0x%x rval=%d",
return (rval);
}
if (length > 8) {
/* get complete hub descriptor */
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_HUB, /* wValue */
0, /* wIndex */
length, /* wLength */
&completion_reason, &cb_flags, 0);
/*
* Hub descriptor data less than 9 bytes is not valid and
* may cause trouble if we use it. See USB2.0 Tab11-13.
*/
"get hub descriptor failed: "
"cr=%d cb_fl=0x%x rval=%d, len=%ld",
return (rval);
}
}
/* parse the hub descriptor */
/* only 32 ports are supported at present */
if (usb_parse_CV_descr("cccscccccc",
(void *)hub_descr, sizeof (usb_hub_descr_t)) == 0) {
"parsing hub descriptor failed");
return (USB_FAILURE);
}
"rval=0x%x bNbrPorts=0x%x wHubChars=0x%x "
"PwrOn2PwrGood=0x%x HubContrCurrent=%dmA", rval,
"Hub driver supports max of %d ports on hub. "
"Hence using the first %d port of %d ports available",
}
return (USB_SUCCESS);
}
/*
* hubd_get_hub_status_words:
*/
static int
{
0,
0,
&data, 0,
"get hub status failed: cr=%d cb=0x%x",
if (data) {
}
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hubd_open_intr_pipe:
* we read all descriptors first for curiosity and then simply
* open the pipe
*/
static int
{
int rval;
"hubd_open_intr_pipe:");
"open intr pipe failed (%d)", rval);
return (rval);
}
return (USB_SUCCESS);
}
/*
* hubd_start_polling:
* start or restart the polling
*/
static void
{
int rval;
"start polling: always=%d dev_state=%d pipe_state=%d\n\t"
"thread=%d ep1_ph=0x%p",
/*
* start or restart polling on the intr pipe
* only if hotplug thread is not running
*/
if ((always == HUBD_ALWAYS_START_POLLING) ||
"start polling requested");
USB_FLAGS_SLEEP)) != USB_SUCCESS) {
"start polling failed, rval=%d", rval);
}
if (pipe_state != USB_PIPE_STATE_ACTIVE) {
}
"start polling request 0x%p", (void *)reqp);
}
}
/*
* hubd_stop_polling
* stop polling but do not close the pipe
*/
static void
{
int rval;
"hubd_stop_polling:");
if (pipe_state != USB_PIPE_STATE_IDLE) {
}
}
}
}
/*
* hubd_close_intr_pipe:
* close the pipe (which also stops the polling
* and wait for the hotplug thread to exit
*/
static void
{
"hubd_close_intr_pipe:");
/*
* Now that no async operation is outstanding on pipe,
* we can change the state to HUBD_INTR_PIPE_CLOSING
*/
}
}
/*
* hubd_exception_cb
* interrupt ep1 exception callback function.
* this callback executes in taskq thread context and assumes
* autoclearing
*/
/*ARGSUSED*/
static void
{
"hubd_exception_cb: "
"req=0x%p cr=%d data=0x%p cb_flags=0x%x", (void *)reqp,
switch (reqp->intr_completion_reason) {
case USB_CR_PIPE_RESET:
/* only restart polling after autoclearing */
(hubd->h_port_reset_wait == 0)) {
hubd_start_polling(hubd, 0);
}
break;
case USB_CR_DEV_NOT_RESP:
case USB_CR_STOPPED_POLLING:
case USB_CR_PIPE_CLOSING:
case USB_CR_UNSPECIFIED_ERR:
/* never restart polling on these conditions */
default:
/* for all others, wait for the autoclearing PIPE_RESET cb */
break;
}
}
/*
* helper function to convert LE bytes to a portmask
*/
static usb_port_mask_t
{
usb_port_mask_t rval = 0;
int i;
for (i = 0; i < len; i++) {
}
return (rval);
}
/*
* hubd_read_cb:
* interrupt ep1 callback function
*
* the status indicates just a change on the pipe with no indication
* of what the change was
*
* known conditions:
* - reset port completion
* - connect
* - disconnect
*
* for handling the hotplugging, create a new thread that can do
* synchronous usba calls
*/
static void
{
int mem_flag = 0;
/*
* At present, we are not handling notification for completion of
* asynchronous pipe reset, for which this data ptr could be NULL
*/
return;
}
sizeof (hubd_hotplug_arg_t), KM_SLEEP);
mem_flag = 1;
return;
}
/*
* Only look at the data and startup the hotplug thread if
* there actually is data.
*/
if (length != 0) {
/*
* if a port change was already reported and we are waiting for
* reset port completion then wake up the hotplug thread which
* should be waiting on reset port completion
*
* if there is disconnect event instead of reset completion, let
* the hotplug thread figure this out
*/
/* remove the reset wait bits from the status */
"port change=0x%x port_reset_wait=0x%x",
/* there should be only one reset bit active at the time */
hubd->h_port_reset_wait = 0;
}
/*
* kick off the thread only if device is ONLINE and it is not
* during attaching or detaching
*/
(hubd->h_port_change) &&
(hubd->h_hotplug_thread == 0)) {
"creating hotplug thread: "
/*
* Mark this device as busy. The will be marked idle
* if the async req fails or at the exit of hotplug
* thread
*/
(void *)arg, 0) == USB_SUCCESS) {
hubd->h_hotplug_thread++;
mem_flag = 0;
} else {
/* mark this device as idle */
(void) hubd_pm_idle_component(hubd,
}
}
}
if (mem_flag == 1) {
}
}
/*
* hubd_hotplug_thread:
* handles resetting of port, and creating children
*
* the ports to check are indicated in h_port_change bit mask
* XXX note that one time poll doesn't work on the root hub
*/
static void
hubd_hotplug_thread(void *arg)
{
"hubd_hotplug_thread: started");
/*
* Before console is init'd, we temporarily block the hotplug
* threads so that BUS_CONFIG_ONE through hubd_bus_config() can be
* processed quickly. This reduces the time needed for vfs_mountroot()
* to mount the root FS from a USB disk. And on SPARC platform,
* in order to load 'consconfig' successfully after OBP is gone,
* we need to check 'modrootloaded' to make sure root filesystem is
* available.
*/
while (!modrootloaded || !consconfig_console_is_ready()) {
}
/*
* if our bus power entry point is active, process the change
* on the next notification of interrupt pipe
*/
hubd->h_hotplug_thread--;
/* mark this device as idle */
"hubd_hotplug_thread: "
"bus_power in progress/hotplugging undesirable - quit");
return;
}
/* is this the root hub? */
/* mark the root hub as full power */
"hubd_hotplug_thread: call pm_power_has_changed");
(void) pm_power_has_changed(hdip, 0,
}
} else {
"hubd_hotplug_thread: not root hub");
}
/*
* this ensures one hotplug activity per system at a time.
* we enter the parent PCI node to have this serialization.
* this also excludes ioctls and deathrow thread
* (a bit crude but easier to debug)
*/
/* exclude other threads */
(hubd->h_port_change)) {
/*
* The 0th bit is the hub status change bit.
* handle loss of local power here
*/
"hubd_hotplug_thread: hub status change!");
/*
* This should be handled properly. For now,
* mask off the bit.
*/
/*
* check and ack hub status
* this causes stall conditions
* when local power is removed
*/
(void) hubd_get_hub_status(hubd);
}
"hubd_hotplug_thread: "
"port %d mask=0x%x change=0x%x connected=0x%x",
/*
* is this a port connection that changed?
*/
continue;
}
/* ack all changes */
"handle port %d:\n\t"
"new status=0x%x change=0x%x was_conn=0x%x ",
/* Recover a disabled port */
if (change & PORT_CHANGE_PESC) {
"port%d Disabled - "
"status=0x%x, change=0x%x",
/*
* if the port was connected and is still
* connected, recover the port
*/
if (was_connected && (status &
PORT_STATUS_CCS)) {
online_child |=
port) == USB_SUCCESS);
}
}
/*
* Now check what changed on the port
*/
if ((status & PORT_STATUS_CCS) &&
(!was_connected)) {
/* new device plugged in */
online_child |=
port) == USB_SUCCESS);
} else if ((status & PORT_STATUS_CCS) &&
/*
* In this case we can never be sure
* if the device indeed got hotplugged
* or the hub is falsely reporting the
* change.
*/
/*
* this ensures we do not race with
* other threads which are detaching
* the child driver at the same time.
*/
/*
* Now check if the driver remains
* attached.
*/
if (i_ddi_devi_attached(child_dip)) {
/*
* first post a disconnect event
* to the child.
*/
/*
* then reset the port and
* recover the device
*/
online_child |=
}
} else if (was_connected) {
/* this is a disconnect */
}
}
/*
* Check if any port is coming out of suspend
*/
if (change & PORT_CHANGE_PSSC) {
/* a resuming device could have disconnected */
if (was_connected &&
/* device on this port resuming */
/*
* Don't raise power on detaching child
*/
if (!DEVI_IS_DETACHING(dip)) {
/*
* As this child is not
* detaching, we set this
* flag, causing bus_ctls
* to stall detach till
* pm_raise_power returns
* and flag it for a deferred
* raise_power.
*
* pm_raise_power is deferred
* because we need to release
* the locks first.
*/
/*
* make sure that child
* doesn't disappear
*/
}
}
}
/*
* Check if the port is over-current
*/
if (change & PORT_CHANGE_OCIC) {
"Port%d in over current condition, "
"please check the attached device to "
"clear the condition. The system will "
"try to recover the port, but if not "
"successful, you need to re-connect "
"the hub or reboot the system to bring "
"the port back to work", port);
if (!(status & PORT_STATUS_PPS)) {
/*
* Try to enable port power, but
* possibly fail. Ignore failure
*/
(void) hubd_enable_port_power(hubd,
port);
/*
* Delay some time to avoid
* over-current event to happen
* too frequently in some cases
*/
}
}
}
}
/* release locks so we can do a devfs_clean */
/* delete cached dv_node's but drop locks first */
/* now check if any children need onlining */
if (online_child) {
"hubd_hotplug_thread: onlining children");
}
/* now check if any disconnected devices need to be cleaned up */
if (offline_child) {
"hubd_hotplug_thread: scheduling cleanup");
}
/* now raise power on the children that have woken up */
if (pwrup_child) {
/* Get the device to full power */
(void) pm_busy_component(dip, 0);
(void) pm_raise_power(dip, 0,
(void) pm_idle_component(dip, 0);
/* release the hold on the child */
}
}
/*
* make sure that we don't accidentally
* over write the disconnect state
*/
}
}
/*
* start polling can immediately kick off read callback
* we need to set the h_hotplug_thread to 0 so that
* the callback is not dropped
*
* if there is device during reset, still stop polling to avoid the
* read callback interrupting the reset, the polling will be started
* in hubd_reset_thread.
*/
break;
}
}
}
/*
* Earlier we would set the h_hotplug_thread = 0 before
* polling was restarted so that
* if there is any root hub status change interrupt, we can still kick
* off the hotplug thread. This was valid when this interrupt was
* delivered in hardware, and only ONE interrupt would be delivered.
* Now that we poll on the root hub looking for status change in
* software, this assignment is no longer required.
*/
hubd->h_hotplug_thread--;
/* mark this device as idle */
"hubd_hotplug_thread: exit");
}
/*
* hubd_handle_port_connect:
* Transition a port from Disabled to Enabled. Ensure that the
* port is in the correct state before attempting to
* access the device.
*/
static int
{
int rval;
int retry;
long time_delay;
long settling_time;
/* Get the hub address and port status */
/*
* If a device is connected, transition the
* port from Disabled to the Enabled state.
* The device will receive downstream packets
* in the Enabled state.
*
* reset port and wait for the hub to report
* completion
*/
/*
* According to section 9.1.2 of USB 2.0 spec, the host should
* wait for atleast 100ms to allow completion of an insertion
* process and for power at the device to become stable.
* We wait for 200 ms
*/
/* calculate 600 ms delay time */
(void) hubd_determine_port_status(hubd,
/* continue only if port is still connected */
if (status & PORT_STATUS_CCS) {
continue;
}
/* carry on regardless */
}
/*
* according to USB 2.0 spec section 11.24.2.7.1.2
* at the end of port reset, the hub enables the port.
* But for some strange reasons, uhci port remains disabled.
* And because the port remains disabled for the settling
* time below, the device connected to the port gets wedged
* - fails to enumerate (device not responding)
* Hence, we enable it here immediately and later again after
* the delay
*/
/* we skip this delay in the first iteration */
if (retry) {
/*
* delay for device to signal disconnect/connect so
* that hub properly recognizes the speed of the device
*/
/*
* When a low speed device is connected to any port of
* PPX it has to be explicitly enabled
* Also, if device intentionally signals
* disconnect/connect, it will disable the port.
* So enable it again.
*/
}
&change, 0)) != USB_SUCCESS) {
"getting status failed (%d)", rval);
continue;
}
if (status & PORT_STATUS_POCI) {
"port %d overcurrent", port);
/* ack changes */
(void) hubd_determine_port_status(hubd,
continue;
}
/* is status really OK? */
"port %d status (0x%x) not OK on retry %d",
/* check if we still have the connection */
if (!(status & PORT_STATUS_CCS)) {
/* lost connection, set exit condition */
break;
}
} else {
/*
* Determine if the device is high or full
* or low speed.
*/
if (status & PORT_STATUS_LSDA) {
} else if (status & PORT_STATUS_HSDA) {
} else {
}
"creating child port%d, status=0x%x "
"port status=0x%x",
/*
* if the child already exists, set addrs and config
* to the device post connect event to the child
*/
/* set addrs to this device */
/*
* This delay is important for the CATC hub
* to enumerate. But, avoid delay in the first
* iteration
*/
if (retry) {
hubd_device_delay/100));
}
if (rval == USB_SUCCESS) {
/*
* if the port is resetting, check if
* device's descriptors have changed.
*/
port) != USB_SUCCESS)) {
break;
}
/*
* set the default config for
* this device
*/
/*
* if we are doing Default reset, do
* not post reconnect event since we
* don't know where reset function is
* called.
*/
return (USB_SUCCESS);
}
/*
* indicate to the child that
* it is online again
*/
return (USB_SUCCESS);
}
} else {
/*
* We need to release access here
* so that busctls on other ports can
* continue and don't cause a deadlock
* when busctl and removal of prom node
* takes concurrently. This also ensures
* busctls for attach of successfully
* enumerated devices on other ports can
* continue concurrently with the process
* of enumerating the new devices. This
* reduces the overall boot time of the system.
*/
hubd,
retry);
if (rval == USB_SUCCESS) {
if (retry > 0) {
"device on port %d "
"enumerated after %d %s",
"retry");
}
return (USB_SUCCESS);
}
}
}
/* wait a while until it settles? */
"disabling port %d again", port);
if (retry) {
}
"retrying on port %d", port);
}
if (retry >= hubd_retry_enumerate) {
/*
* If it is a High Speed Root Hub and connected device
* Host Controller. In this case, USB 2.0 Host Controller
* will transfer the ownership of this port to USB 1.1
* Host Controller. So don't display any error message on
* the console.
*/
if ((hubd_usb_addr == ROOT_HUB_ADDR) &&
(hub_port_status == USBA_HIGH_SPEED_DEV) &&
(port_status != USBA_HIGH_SPEED_DEV)) {
"device is connected to High Speed root hub");
} else {
"Connecting device on port %d failed", port);
}
/*
* the port should be automagically
* disabled but just in case, we do
* it here
*/
/* ack all changes because we disabled this port */
(void) hubd_determine_port_status(hubd,
}
return (USB_FAILURE);
}
/*
* hubd_get_hub_status:
*/
static int
{
int rval;
"hubd_get_hub_status:");
return (USB_FAILURE);
}
/* Obtain the raw configuration descriptor */
/* get configuration descriptor */
if (rval != USB_CFG_DESCR_SIZE) {
"get hub configuration descriptor failed.");
return (USB_FAILURE);
} else {
}
/* check if local power status changed. */
if (change & C_HUB_LOCAL_POWER_STATUS) {
/*
* local power has been lost, check the maximum
* power consumption of current configuration.
* see USB2.0 spec Table 11-12.
*/
if (status & HUB_LOCAL_POWER_STATUS) {
if (MaxPower == 0) {
/*
* Self-powered only hub. Because it could
* not draw any power from USB bus.
* It can't work well on this condition.
*/
"local power has been lost, "
"please disconnect hub");
} else {
/*
* Bus-powered only or self/bus-powered hub.
*/
"local power has been lost,"
"the hub could draw %d"
" mA power from the USB bus.",
2*MaxPower);
}
}
"clearing feature C_HUB_LOCAL_POWER ");
0,
0,
NULL, 0,
"clear feature C_HUB_LOCAL_POWER "
"failed (%d 0x%x %d)",
}
}
if (change & C_HUB_OVER_CURRENT) {
if (status & HUB_OVER_CURRENT) {
/*
* The root hub should be automatically
* recovered when over-current condition is
* cleared. But there might be exception and
* need user interaction to recover.
*/
"Root hub over current condition, "
"please check your system to clear the "
"condition as soon as possible. And you "
"may need to reboot the system to bring "
"the root hub back to work if it cannot "
"recover automatically");
} else {
/*
* The driver would try to recover port power
* on over current condition. When the recovery
* fails, the user may still need to offline
* this hub in order to recover.
* The port power is automatically disabled,
* so we won't see disconnects.
*/
"Hub global over current condition, "
"please disconnect the devices connected "
"to the hub to clear the condition. And "
"you may need to re-connect the hub if "
"the ports do not work");
}
}
"clearing feature C_HUB_OVER_CURRENT");
0,
0,
NULL, 0,
"clear feature C_HUB_OVER_CURRENT "
"failed (%d 0x%x %d)",
}
/*
* Try to recover all port power if they are turned off.
* Don't do this for root hub, but rely on the root hub
* to recover itself.
*/
/*
* Only check the power status of the 1st port
* since all port power status should be the same.
*/
&change, 0);
if (status & PORT_STATUS_PPS) {
return (USB_SUCCESS);
}
port++) {
}
/*
* Delay some time to avoid over-current event
* to happen too frequently in some cases
*/
}
}
return (USB_SUCCESS);
}
/*
* hubd_reset_port:
*/
static int
{
int rval;
int i;
"hubd_reset_port: port=%d", port);
port,
0,
NULL, 0,
"reset port%d failed (%d 0x%x %d)",
return (USB_FAILURE);
}
"waiting on cv for reset completion");
/*
* wait for port status change event
*/
for (i = 0; i < hubd_retry_enumerate; i++) {
/*
* start polling ep1 for receiving notification on
* reset completion
*/
/*
* sleep a max of 100ms for reset completion
* notification to be received
*/
if ((rval <= 0) &&
/* we got woken up because of a timeout */
"timeout: reset port=%d failed", port);
return (USB_FAILURE);
}
}
"reset completion received");
/* check status to determine whether reset completed */
0,
port,
&data, 0,
"get status port%d failed (%d 0x%x %d)",
if (data) {
}
continue;
}
/* continue only if port is still connected */
if (!(status & PORT_STATUS_CCS)) {
/* lost connection, set exit condition */
i = hubd_retry_enumerate;
break;
}
if (status & PORT_STATUS_PRS) {
"port%d reset active", port);
continue;
} else {
"port%d reset inactive", port);
}
if (change & PORT_CHANGE_PRSC) {
"clearing feature CFS_C_PORT_RESET");
port,
0,
NULL, 0,
"clear feature CFS_C_PORT_RESET"
" port%d failed (%d 0x%x %d)",
}
}
break;
}
if (i >= hubd_retry_enumerate) {
/* port reset has failed */
rval = USB_FAILURE;
}
return (rval);
}
/*
* hubd_enable_port:
* this may fail if the hub as been disconnected
*/
static int
{
int rval;
"hubd_enable_port: port=%d", port);
/* Do not issue a SetFeature(PORT_ENABLE) on external hubs */
return (USB_SUCCESS);
}
port,
0,
NULL, 0,
"enable port%d failed (%d 0x%x %d)",
}
"enabling port done");
return (rval);
}
/*
* hubd_disable_port
*/
static int
{
int rval;
"hubd_disable_port: port=%d", port);
port,
0,
NULL, 0,
"disable port%d failed (%d 0x%x %d)", port,
return (USB_FAILURE);
}
"clearing feature CFS_C_PORT_ENABLE");
port,
0,
NULL, 0,
"clear feature CFS_C_PORT_ENABLE port%d failed "
"(%d 0x%x %d)",
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hubd_determine_port_status:
*/
static int
{
int rval;
"hubd_determine_port_status: port=%d, state=0x%x ack=0x%x", port,
0,
port,
&data, 0,
"port=%d get status failed (%d 0x%x %d)",
if (data) {
}
return (rval);
}
"port %d: length incorrect %ld",
return (rval);
}
if (*status & PORT_STATUS_CCS) {
"port%d connected", port);
} else {
"port%d disconnected", port);
}
if (*status & PORT_STATUS_PES) {
"port%d enabled", port);
} else {
"port%d disabled", port);
}
if (*status & PORT_STATUS_PSS) {
"port%d suspended", port);
} else {
"port%d not suspended", port);
}
if (*change & PORT_CHANGE_PRSC) {
"port%d reset completed", port);
} else {
}
if (*status & PORT_STATUS_POCI) {
"port%d overcurrent!", port);
} else {
}
if (*status & PORT_STATUS_PRS) {
"port%d reset active", port);
} else {
"port%d reset inactive", port);
}
if (*status & PORT_STATUS_PPS) {
"port%d power on", port);
} else {
"port%d power off", port);
}
if (*status & PORT_STATUS_LSDA) {
"port%d low speed", port);
} else {
if (*status & PORT_STATUS_HSDA) {
"high speed", port);
(PORT_STATUS_HSDA & ack_flag);
} else {
"full speed", port);
~(PORT_STATUS_HSDA & ack_flag);
}
}
/*
* Acknowledge connection, enable, reset status
*/
if (ack_flag) {
"clearing feature CFS_C_PORT_CONNECTION");
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0)) !=
USB_SUCCESS) {
"clear feature CFS_C_PORT_CONNECTION"
" port%d failed (%d 0x%x %d)",
}
}
"clearing feature CFS_C_PORT_ENABLE");
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0)) !=
USB_SUCCESS) {
"clear feature CFS_C_PORT_ENABLE"
" port%d failed (%d 0x%x %d)",
}
}
"clearing feature CFS_C_PORT_SUSPEND");
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0)) !=
USB_SUCCESS) {
"clear feature CFS_C_PORT_SUSPEND"
" port%d failed (%d 0x%x %d)",
}
}
"clearing feature CFS_C_PORT_OVER_CURRENT");
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0)) !=
USB_SUCCESS) {
"clear feature CFS_C_PORT_OVER_CURRENT"
" port%d failed (%d 0x%x %d)",
}
}
"clearing feature CFS_C_PORT_RESET");
port,
0, NULL, 0,
&completion_reason, &cb_flags, 0)) !=
USB_SUCCESS) {
"clear feature CFS_C_PORT_RESET"
" port%d failed (%d 0x%x %d)",
}
}
}
return (USB_SUCCESS);
}
/*
* hubd_recover_disabled_port
* if the port got disabled because of an error
* enable it. If hub doesn't suport enable port,
* reset the port to bring the device to life again
*/
static int
{
int rval = USB_FAILURE;
/* first try enabling the port */
/* read the port status */
if (status & PORT_STATUS_PES) {
"Port%d now Enabled", port);
} else if (status & PORT_STATUS_CCS) {
/* first post a disconnect event to the child */
/* then reset the port and recover the device */
"Port%d now Enabled by force", port);
}
return (rval);
}
/*
* hubd_enable_all_port_power:
*/
static int
{
int wait;
"hubd_enable_all_port_power");
/*
* According to section 11.11 of USB, for hubs with no power
* switches, bPwrOn2PwrGood is zero. But we wait for some
* arbitrary time to enable power to become stable.
*
* If an hub supports port power switching, we need to wait
* at least 20ms before accessing corresponding usb port.
*/
if ((hub_descr->wHubCharacteristics &
} else {
}
"hubd_enable_all_port_power: popg=%d wait=%d",
/*
* Enable power per port. we ignore gang power and power mask
* and always enable all ports one by one.
*/
/*
* Transition the port from the Powered Off to the
* Disconnected state by supplying power to the port.
*/
"hubd_enable_all_port_power: power port=%d", port);
}
/* For retry if any, use some extra delay */
/* Check each port power status for a given usb hub */
/* Get port status */
"Retry is in progress %d: port %d status %d",
/* Get port status */
}
/* Print warning message if port has no power */
if (!(status & PORT_STATUS_PPS)) {
"hubd_enable_all_port_power: port %d power-on "
}
}
return (USB_SUCCESS);
}
/*
* hubd_enable_port_power:
* enable individual port power
*/
static int
{
int rval;
"hubd_enable_port_power: port=%d", port);
port,
0, NULL, 0,
"set port power failed (%d 0x%x %d)",
return (USB_FAILURE);
} else {
return (USB_SUCCESS);
}
}
/*
* hubd_disable_all_port_power:
*/
static int
{
"hubd_disable_all_port_power");
/*
* disable power per port, ignore gang power and power mask
*/
}
return (USB_SUCCESS);
}
/*
* hubd_disable_port_power:
* disable individual port power
*/
static int
{
int rval;
"hubd_disable_port_power: port=%d", port);
port,
0, NULL, 0,
"clearing port%d power failed (%d 0x%x %d)",
return (USB_FAILURE);
} else {
ASSERT(completion_reason == 0);
return (USB_SUCCESS);
}
}
/*
* Search the database of user preferences and find out the preferred
* configuration for this new device
*/
int
{
int user_conf;
int pathlen;
/* try to get pathname for this device */
/*
* We haven't initialized the node and it doesn't have an address
* yet. Append port number to the physical pathname
*/
"hubd_select_device_configuration: Device=%s\n\t"
"Child path=%s",
pathname);
/* database search for user preferences */
if (user_pref) {
"hubd_select_device_configuration: "
"usba_devdb_get_user_preferences "
"return user_conf=%d\npreferred driver=%s path=%s",
}
} else {
"hubd_select_device_configuration: No match found");
/* select default configuration for this device */
}
/* if the device has just one configuration, set default value */
}
return (user_conf);
}
/*
* Retrieves config cloud for this configuration
*/
int
{
int rval;
char *tmpbuf;
"hubd_get_this_config_cloud: conf_index=%d", conf_index);
/* alloc temporary space for config descriptor */
KM_SLEEP);
/* alloc temporary space for string descriptor */
0,
&pdata,
0,
&cb_flags,
0)) == USB_SUCCESS) {
/* this must be true since we didn't allow data underruns */
"device returned incorrect configuration "
"descriptor size.");
rval = USB_FAILURE;
goto done;
}
/*
* Parse the configuration descriptor
*/
/* if parse cfg descr error, it should return failure */
if (size == USB_PARSE_ERROR) {
"device returned incorrect "
"configuration descriptor type.");
}
rval = USB_FAILURE;
goto done;
}
"device returned incorrect "
"configuration descriptor size.");
rval = USB_FAILURE;
goto done;
}
/* Now fetch the complete config cloud */
0,
&pdata,
0,
&cb_flags,
0)) == USB_SUCCESS) {
"device returned incorrect "
"configuration descriptor.");
rval = USB_FAILURE;
goto done;
}
/*
* copy config descriptor into usba_device
*/
/*
* retrieve string descriptor describing this
* configuration
*/
if (confdescr->iConfiguration) {
"Get conf str descr for config_index=%d",
/*
* Now fetch the string descriptor describing
* this configuration
*/
tmpbuf, USB_MAXSTRINGLEN)) ==
USB_SUCCESS) {
if (size > 0) {
[conf_index] = (char *)
KM_SLEEP);
(void) strcpy(
[conf_index], tmpbuf);
}
} else {
"hubd_get_this_config_cloud: "
"getting config string (%d) "
"failed",
/* ignore this error */
rval = USB_SUCCESS;
}
}
}
}
done:
if (rval != USB_SUCCESS) {
"hubd_get_this_config_cloud: "
"error in retrieving config descriptor for "
"config index=%d rval=%d cr=%d",
}
if (pdata) {
}
return (rval);
}
/*
* Retrieves the entire config cloud for all configurations of the device
*/
int
{
int rval = USB_SUCCESS;
int ncfgs;
char **str_descr;
"hubd_get_all_device_config_cloud: Start");
/* alloc pointer array for conf. descriptors */
/* Get configuration descriptor for each configuration */
}
return (rval);
}
/*
* hubd_ready_device:
* Update the usba_device structure
* Set the given configuration
* Prepares the device node for driver to online. If an existing
* OBP node is found, it will switch to the OBP node.
*/
{
"hubd_ready_device: dip=0x%p, user_conf_index=%d",
(void *)child_dip, config_index);
/* Set the configuration */
USB_REQ_SET_CFG, /* bRequest */
0, /* wIndex */
0, /* wLength */
NULL,
0,
&cb_flags,
0);
/* ready the device node */
/* set owner of default pipe to child dip */
return (child_dip);
}
/*
* hubd_create_child
* - create child dip
* - open default pipe
* - get device descriptor
* - set the address
* - get device string descriptors
* - get the entire config cloud (all configurations) of the device
* - set user preferred configuration
* - close default pipe
* - load appropriate driver(s)
*/
static int
int iteration)
{
int rval;
int user_conf_index;
"hubd_create_child: port=%d", port);
/*
* create a dip which can be used to open the pipe. we set
* the name after getting the descriptors from the device
*/
"device", /* driver name */
port_status, /* low speed device */
&child_dip);
if (rval != USB_SUCCESS) {
"usb_create_child_devi failed (%d)", rval);
goto fail_cleanup;
}
/*
* To support split transactions, update address and port
* of high speed hub to which given device is connected.
*/
if (parent_port_status == USBA_HIGH_SPEED_DEV) {
} else {
}
KM_SLEEP);
sizeof (usb_dev_descr_t));
/* Open the default pipe */
"usb_pipe_open failed (%d)", rval);
goto fail_cleanup;
}
/*
* get device descriptor
*/
"hubd_create_child: get device descriptor: 64 bytes");
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_DEV, /* wValue */
0, /* wIndex */
64, /* wLength */
&completion_reason, &cb_flags, 0);
if ((rval != USB_SUCCESS) &&
/*
* rval != USB_SUCCESS AND
* completion_reason != USB_CR_DATA_OVERRUN
* pdata could be != NULL.
* Free pdata now to prevent memory leak.
*/
"hubd_create_child: get device descriptor: 8 bytes");
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_DEV, /* wValue */
0, /* wIndex */
8, /* wLength */
&completion_reason, &cb_flags, 0);
if (rval != USB_SUCCESS) {
"getting device descriptor failed (%s 0x%x %d)",
goto fail_cleanup;
}
} else {
}
sizeof (usb_dev_descr_t));
"parsing device descriptor returned %lu", size);
if (size < 8) {
"get device descriptor returned %lu bytes", size);
goto fail_cleanup;
}
if (length < 8) {
"fail enumeration: bLength=%d", length);
goto fail_cleanup;
}
/* Set the address of the device */
USB_REQ_SET_ADDRESS, /* bRequest */
address, /* wValue */
0, /* wIndex */
0, /* wLength */
NULL, 0,
char buffer[64];
"setting address failed (cr=%s cb_flags=%s rval=%d)",
rval);
goto fail_cleanup;
}
"set address 0x%x done", address);
/* now close the pipe for addr 0 */
/*
* This delay is important for the CATC hub to enumerate
* But, avoid delay in the first iteration
*/
if (iteration) {
}
/* assign the address in the usba_device structure */
child_ud->usb_no_cpr = 0;
/* save this device descriptor */
sizeof (usb_dev_descr_t));
/* re-open the pipe for the device with the new address */
"usb_pipe_open failed (%d)", rval);
goto fail_cleanup;
}
/*
* Get full device descriptor only if we have not received full
* device descriptor earlier.
*/
"hubd_create_child: get full device descriptor: "
"%d bytes", length);
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_DEV, /* wValue */
0, /* wIndex */
length, /* wLength */
&pdata, 0,
"hubd_create_child: get full device descriptor: "
"64 bytes");
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_DEV, /* wValue */
0, /* wIndex */
64, /* wLength */
&completion_reason, &cb_flags, 0);
/* we have to trust the data now */
if (pdata) {
goto fail_cleanup;
}
} else if (rval != USB_SUCCESS) {
"getting device descriptor failed "
"(%d 0x%x %d)",
goto fail_cleanup;
}
}
sizeof (usb_dev_descr_t));
"parsing device descriptor returned %lu", size);
/*
* For now, free the data
* eventually, each configuration may need to be looked at
*/
if (size != USB_DEV_DESCR_SIZE) {
"fail enumeration: descriptor size=%lu "
goto fail_cleanup;
}
/*
* save the device descriptor in usba_device since it is needed
* later on again
*/
sizeof (usb_dev_descr_t));
}
if (usb_dev_descr.bNumConfigurations == 0) {
"device descriptor:\n\t"
"l=0x%x type=0x%x USB=0x%x class=0x%x subclass=0x%x\n\t"
"protocol=0x%x maxpktsize=0x%x "
"Vid=0x%x Pid=0x%x rel=0x%x\n\t"
"Mfg=0x%x P=0x%x sn=0x%x #config=0x%x",
goto fail_cleanup;
}
/* get the device string descriptor(s) */
/* retrieve config cloud for all configurations */
if (rval != USB_SUCCESS) {
"failed to get configuration descriptor(s)");
goto fail_cleanup;
}
/* get the preferred configuration for this device */
/* Check if the user selected configuration index is in range */
(user_conf_index < 0)) {
"Configuration index for device idVendor=%d "
"idProduct=%d is=%d, and is out of range[0..%d]",
/* treat this as user didn't specify configuration */
}
/*
* Warn users of a performance hit if connecting a
* High Speed behind a 1.1 hub, which is behind a
* 2.0 port.
*/
if ((parent_port_status != USBA_HIGH_SPEED_DEV) &&
(parent_usb_addr)) {
/*
* Now that we know the root port is a high speed port
* and that the parent port is not a high speed port,
* let's find out if the device itself is a high speed
* device. If it is a high speed device,
* USB_DESCR_TYPE_SETUP_DEV_QLF should return a value,
* otherwise the command will fail.
*/
USB_REQ_GET_DESCR, /* bRequest */
USB_DESCR_TYPE_SETUP_DEV_QLF, /* wValue */
0, /* wIndex */
10, /* wLength */
&completion_reason, &cb_flags, 0);
if (pdata) {
}
/*
* USB_DESCR_TYPE_SETUP_DEV_QLF query was successful
* that means this is a high speed device behind a
* high speed root hub, but running at full speed
* because there is a full speed hub in the middle.
*/
if (rval == USB_SUCCESS) {
"Connecting a high speed device to a "
"non high speed hub (port %d) will result "
"in a loss of performance. Please connect "
"the device to a high speed hub to get "
"the maximum performance.",
port);
}
}
/*
* Now we try to online the device by attaching a driver
* The following truth table illustrates the logic:-
* Cfgndx Driver Action
* 0 0 loop all configs for driver with full
* compatible properties.
* 0 1 set first configuration,
* compatible prop = drivername.
* 1 0 Set config, full compatible prop
* 1 1 Set config, compatible prop = drivername.
*
* Note:
* cfgndx = user_conf_index
* Driver = usb_preferred_driver
*/
if (child_ud->usb_preferred_driver) {
/*
* It is the job of the "preferred driver" to put the
* device in the desired configuration. Till then
* put the device in config index 0.
*/
goto fail_cleanup;
}
/*
* Assign the dip before onlining to avoid race
* with busctl
*/
(void) usba_bind_driver(child_dip);
} else {
/*
* loop through all the configurations to see if we
* can find a driver for any one config. If not, set
* the device in config_index 0
*/
rval = USB_FAILURE;
for (config_index = 0;
/*
* Assign the dip before onlining to avoid race
* with busctl
*/
/*
* Normally power budget should be checked
* before device is configured. A failure in
* power budget checking will stop the device
* from being configured with current
* config_index and may enable the device to
* be configured in another configuration.
* This may break the user experience that a
* device which previously worked in config
* A now works in config B after power budget
* control is enabled. To avoid such situation,
* power budget checking is moved here and will
* fail the child creation directly if config
* A exceeds the power available.
*/
if (rval == USB_SUCCESS) {
child_ud, config_index)) !=
USB_SUCCESS) {
goto fail_cleanup;
}
}
}
if (rval != USB_SUCCESS) {
child_ud, 0)) != USB_SUCCESS) {
goto fail_cleanup;
}
child_ud, 0);
}
} /* end else loop all configs */
} else {
goto fail_cleanup;
}
/*
* Assign the dip before onlining to avoid race
* with busctl
*/
(void) usba_bind_driver(child_dip);
}
} else {
}
return (USB_SUCCESS);
"hubd_create_child: fail_cleanup");
if (pdata) {
}
if (ph) {
}
if (child_dip) {
if (rval != USB_SUCCESS) {
"failure to remove child node");
}
}
if (child_ud) {
/* to make sure we free the address */
} else {
}
}
return (USB_FAILURE);
}
/*
* hubd_delete_child:
* - free usb address
* - lookup child dips, there may be multiple on this port
* - offline each child devi
*/
static int
{
int rval = USB_SUCCESS;
"hubd_delete_child: port=%d, dip=0x%p usba_device=0x%p",
if (child_dip) {
"hubd_delete_child:\n\t"
"dip = 0x%p (%s) at port %d",
if (usba_device) {
}
/*
* This is only useful for HWA device node.
* Since hwahc interface must hold hwarc interface
* open until hwahc is detached, the first call to
* ndi_devi_unconfig_one() can only offline hwahc
* driver but not hwarc driver. Need to make a second
* call to ndi_devi_unconfig_one() to make the hwarc
* driver detach.
*/
}
/*
* if the child was still < DS_INITIALIZED
* then our bus_unconfig was not called and
* we have to zap the child here
*/
usba_device_t *ud =
if (ud) {
ud->usb_ref_count = 0;
}
}
}
}
}
return (rval);
}
/*
* hubd_free_usba_device:
* free usb device structure unless it is associated with
* the root hub which is handled differently
*/
static void
{
"hubd_free_usba_device: hubd=0x%p, usba_device=0x%p",
(void *)hubd, (void *)usba_device);
#ifdef DEBUG
if (dip) {
}
#endif
}
}
/*
* event support
*
* busctl event support
*/
static int
char *eventname,
{
"hubd_busop_get_eventcookie: dip=0x%p, rdip=0x%p, "
"(dip=%s%d, rdip=%s%d)",
/* return event cookie, iblock cookie, and level */
}
static int
void *bus_impldata),
{
"hubd_busop_add_eventcall: dip=0x%p, rdip=0x%p "
"cookie=0x%p, cb=0x%p, arg=0x%p",
"(dip=%s%d, rdip=%s%d, event=%s)",
/* Set flag on children registering events */
break;
break;
default:
break;
}
/* add callback to our event set */
}
static int
{
"hubd_busop_remove_eventcall: dip=0x%p, rdip=0x%p "
(void *)id->ndi_evtcb_cookie);
"(dip=%s%d, rdip=%s%d, event=%s)",
id->ndi_evtcb_cookie));
/* remove event registration from our event set */
}
/*
* event distribution
*
* hubd_do_callback:
* Post this event to the specified child
*/
static void
{
"hubd_do_callback");
}
/*
* hubd_run_callbacks:
* Send this event to all children
*/
static void
{
"hubd_run_callbacks");
/*
* the childen_dips list may have dips that have been
* already deallocated. we only get a post_detach notification
* but not a destroy notification
*/
}
}
}
/*
* hubd_post_event
* post event to a child on the port depending on the type
*/
static void
{
int rval;
"hubd_post_event: port=%d event=%s", port,
/*
* Hotplug daemon may be attaching a driver that may be registering
* event callbacks. So it already has got the device tree lock and
* event handle mutex. So to prevent a deadlock while posting events,
* we grab and release the locks in the same order.
*/
switch (type) {
/* Clear the registered event flag */
/*
* Mark the dip for deletion only after the driver has
* seen the disconnect event to prevent cleanup thread
* from stepping in between.
*/
break;
/*
* persistent pipe close for this event is taken care by the
* caller after verfying that all children can suspend
*/
break;
/*
* Check if this child has missed the disconnect event before
* it registered for event callbacks
*/
/* clear the flag and post disconnect event */
}
/*
* Mark the dip as reinserted to prevent cleanup thread
* from stepping in.
*/
if (rval != USB_SUCCESS) {
"failed to reopen all pipes on reconnect");
}
/*
* We might see a connect event only if hotplug thread for
* disconnect event don't run in time.
* Set the flag again, so we don't miss posting a
* disconnect event.
*/
break;
/*
* Check if this child has missed the pre-suspend event before
* it registered for event callbacks
*/
/* clear the flag and post pre_suspend event */
}
usba_device->usb_no_cpr = 0;
/*
* Since the pipe has already been opened by hub
* at DDI_RESUME time, there is no need for a
* persistent pipe open
*/
/*
* Set the flag again, so we don't miss posting a
* pre-suspend event. This enforces a tighter
* dev_state model.
*/
break;
}
}
/*
* handling of events coming from above
*/
static int
{
int circ;
"hubd_disconnect_event_cb: tag=%d", tag);
switch (hubd->h_dev_state) {
case USB_DEV_ONLINE:
case USB_DEV_PWRED_DOWN:
/* stop polling on the interrupt pipe */
/* FALLTHROUGH */
case USB_DEV_SUSPENDED:
/* we remain in this state */
/* close all the open pipes of our children */
}
}
break;
case USB_DEV_DISCONNECTED:
/* avoid passing multiple disconnects to children */
"hubd_disconnect_event_cb: Already disconnected");
break;
default:
"hubd_disconnect_event_cb: Illegal devstate=%d",
hubd->h_dev_state);
break;
}
return (USB_SUCCESS);
}
static int
{
return (rval);
}
/*
* hubd_pre_suspend_event_cb
* propogate event for binary compatibility of old drivers
*/
static int
{
int circ;
"hubd_pre_suspend_event_cb");
/* disable hotplug thread */
hubd->h_hotplug_thread++;
/* keep PM out till we see a cpr resume */
return (USB_SUCCESS);
}
/*
* hubd_post_resume_event_cb
* propogate event for binary compatibility of old drivers
*/
static int
{
int circ;
"hubd_post_resume_event_cb");
/* enable PM */
/* allow hotplug thread */
hubd->h_hotplug_thread--;
/* start polling */
hubd_start_polling(hubd, 0);
return (USB_SUCCESS);
}
/*
* hubd_cpr_suspend
*/
static int
{
int rval = USB_FAILURE;
"hubd_cpr_suspend: Begin");
/* Make sure device is powered up to save state. */
/* bring the device to full power */
switch (hubd->h_dev_state) {
case USB_DEV_ONLINE:
case USB_DEV_PWRED_DOWN:
case USB_DEV_DISCONNECTED:
/* find out if all our children have been quiesced */
}
}
if (no_cpr > 0) {
"Children busy - can't checkpoint");
/* remain in same state to fail checkpoint */
break;
} else {
/*
* do not suspend if our hotplug thread
* or the deathrow thread is active
*/
"hotplug thread active - can't cpr");
/* remain in same state to fail checkpoint */
break;
}
/* quiesce ourselves now */
/* close all the open pipes of our children */
"suspending port %d failed",
port);
}
}
}
/*
* if we are the root hub, we close our pipes
* ourselves.
*/
}
rval = USB_SUCCESS;
break;
}
case USB_DEV_SUSPENDED:
default:
"hubd_cpr_suspend: Illegal dev state=%d",
hubd->h_dev_state);
break;
}
return (rval);
}
static void
{
/*
* if we are the root hub, we open our pipes
* ourselves.
*/
if (usba_is_root_hub(dip)) {
}
(void) hubd_restore_state_cb(dip);
}
/*
* hubd_restore_state_cb
* Event callback to restore device state
*/
static int
{
"hubd_restore_state_cb: Begin");
/* restore the state of this device */
return (USB_SUCCESS);
}
/*
* registering for events
*/
static int
{
int rval = USB_SUCCESS;
} else {
}
return (rval);
}
/*
* hubd cpr callback related functions
*
* hubd_cpr_post_user_callb:
* This function is called during checkpoint & resume -
* 1. after user threads are stopped during checkpoint
* 2. after kernel threads are resumed during resume
*/
/* ARGSUSED */
static boolean_t
{
int retry = 0;
"hubd_cpr_post_user_callb");
switch (code) {
case CB_CODE_CPR_CHKPT:
"hubd_cpr_post_user_callb: CB_CODE_CPR_CHKPT");
/* turn off deathrow thread */
/* give up if deathrow thread doesn't exit */
"hubd_cpr_post_user_callb, waiting for "
"deathrow thread to exit");
}
/* save the state of the device */
return (B_TRUE);
case CB_CODE_CPR_RESUME:
"hubd_cpr_post_user_callb: CB_CODE_CPR_RESUME");
/* restore the state of the device */
/* turn on deathrow thread */
return (B_TRUE);
default:
return (B_FALSE);
}
}
/* register callback with cpr framework */
void
{
"hubd_register_cpr_callback");
}
/* unregister callback with cpr framework */
void
{
"hubd_unregister_cpr_callback");
}
}
/*
* Power management
*
* create the pm components required for power management
*/
static void
{
"hubd_create_pm_components: Begin");
/* Allocate the state structure */
hubpm->hubp_pm_capabilities = 0;
/* alloc memory to save power states of children */
/*
* if the enable remote wakeup fails
* we still want to enable
* parent notification so we can PM the children
*/
"hubd_create_pm_components: "
"Remote Wakeup Enabled");
USB_SUCCESS) {
/* we are busy now till end of the attach */
/* bring the device to full power */
(void) pm_raise_power(dip, 0,
}
}
"hubd_create_pm_components: END");
}
/*
* Attachment point management
*/
/* ARGSUSED */
int
{
return (EINVAL);
return (ENXIO);
}
"hubd_open:");
return (EBUSY);
}
return (0);
}
/* ARGSUSED */
int
{
return (EINVAL);
}
return (ENXIO);
}
return (0);
}
/*
* hubd_ioctl: cfgadm controls
*/
/* ARGSUSED */
int
{
int rv = 0;
char *msg; /* for messages */
usb_port_t port = 0;
return (ENXIO);
}
"usba_hubdi_ioctl: "
"cmd=%x, arg=%lx, mode=%x, cred=%p, rval=%p dev=0x%lx",
/* read devctl ioctl data */
if ((cmd != DEVCTL_AP_CONTROL) &&
return (EFAULT);
}
/*
* make sure the hub is connected before trying any
* of the following operations:
* configure, connect, disconnect
*/
switch (cmd) {
case DEVCTL_AP_DISCONNECT:
case DEVCTL_AP_UNCONFIGURE:
case DEVCTL_AP_CONFIGURE:
"hubd: already gone");
if (dcp) {
}
return (EIO);
}
/* FALLTHROUGH */
case DEVCTL_AP_GETSTATE:
"hubd: bad port");
if (dcp) {
}
return (EINVAL);
}
break;
case DEVCTL_AP_CONTROL:
break;
default:
if (dcp) {
}
return (ENOTTY);
}
/* should not happen, just in case */
if (dcp) {
}
return (EIO);
}
"This port is resetting, just return");
if (dcp) {
}
return (EIO);
}
/* go full power */
hubd->h_hotplug_thread++;
/* stop polling if it was active */
if (prev_pipe_state == USB_PIPE_STATE_ACTIVE) {
}
}
switch (cmd) {
case DEVCTL_AP_DISCONNECT:
}
break;
case DEVCTL_AP_UNCONFIGURE:
}
break;
case DEVCTL_AP_CONFIGURE:
/* toggle port */
break;
}
} else {
}
break;
case DEVCTL_AP_GETSTATE:
case HUBD_CFGADM_DISCONNECTED:
/* port previously 'disconnected' by cfgadm */
break;
case HUBD_CFGADM_UNCONFIGURED:
break;
case HUBD_CFGADM_CONFIGURED:
break;
break;
case HUBD_CFGADM_EMPTY:
default:
break;
}
ap_state.ap_error_code = 0;
ap_state.ap_in_transition = 0;
"DEVCTL_AP_GETSTATE: "
"ostate=0x%x, rstate=0x%x, condition=0x%x",
/* copy the return-AP-state information to the user space */
}
break;
case DEVCTL_AP_CONTROL:
{
/*
* Generic devctl for hardware-specific functionality.
* For list of sub-commands see hubd_impl.h
*/
/* copy user ioctl data in first */
#ifdef _MULTI_DATAMODEL
break;
}
} else
#endif /* _MULTI_DATAMODEL */
mode) != 0) {
break;
}
"DEVCTL_AP_CONTROL: ioc: cmd=0x%x port=%d get_size=%d"
/*
* returns a 32-bit number.
*/
break;
}
case USB_DESCR_TYPE_DEV:
msg = "DEVCTL_AP_CONTROL: GET_DEVICE_DESC";
/* uint32 so this works 32/64 */
"%s: get_size copyout failed", msg);
break;
}
} else { /* send out the actual descr */
/* check child_dip */
break;
}
"%s: bufsize passed (%d) != sizeof "
"usba_device_descr_t (%d)", msg,
break;
}
if (ddi_copyout((void *)dev_descrp,
"%s: copyout failed.", msg);
break;
}
}
break;
case USB_DESCR_TYPE_STRING:
{
char *str;
msg = "DEVCTL_AP_CONTROL: GET_STRING_DESCR";
/* recheck */
NULL) {
break;
}
case HUBD_MFG_STR:
break;
case HUBD_PRODUCT_STR:
break;
case HUBD_SERIALNO_STR:
break;
case HUBD_CFG_DESCR_STR:
break;
default:
"%s: Invalid string request", msg);
break;
} /* end of switch */
if (rv != 0) {
break;
}
"%s: copyout of size failed.", msg);
break;
}
} else {
if (size == 0) {
"%s: String is NULL", msg);
break;
}
"%s: string buf size wrong", msg);
break;
}
"%s: copyout failed.", msg);
break;
}
}
break;
}
case HUBD_GET_CFGADM_NAME:
{
const char *name;
/* recheck */
NULL) {
break;
}
name = "unsupported";
}
msg = "DEVCTL_AP_CONTROL: HUBD_GET_CFGADM_NAME";
if (ddi_copyout((void *)&name_len,
"%s: copyout of size failed", msg);
break;
}
} else {
"%s: string buf length wrong", msg);
break;
}
"%s: copyout failed.", msg);
break;
}
}
break;
}
/*
* Return the config index for the currently-configured
* configuration.
*/
case HUBD_GET_CURRENT_CONFIG:
{
msg = "DEVCTL_AP_CONTROL: GET_CURRENT_CONFIG";
"%s", msg);
/*
* Return the config index for the configuration
* currently in use.
* Recheck if child_dip exists
*/
NULL) {
break;
}
if (ddi_copyout((void *)&size,
"%s: copyout of size failed.", msg);
break;
}
} else {
"%s: buffer size wrong", msg);
break;
}
if (ddi_copyout((void *)&config_index,
"%s: copyout failed", msg);
}
}
break;
}
case HUBD_GET_DEVICE_PATH:
{
char *path;
msg = "DEVCTL_AP_CONTROL: GET_DEVICE_PATH";
"%s", msg);
/* Recheck if child_dip exists */
NULL) {
break;
}
/* ddi_pathname doesn't supply /devices, so we do. */
if (ddi_copyout((void *)&size,
"%s: copyout of size failed.", msg);
}
} else {
"%s: buffer wrong size.", msg);
} else if (ddi_copyout((void *)path,
"%s: copyout failed.", msg);
}
}
break;
}
case HUBD_REFRESH_DEVDB:
msg = "DEVCTL_AP_CONTROL: HUBD_REFRESH_DEVDB";
"%s", msg);
}
break;
default:
} /* end switch */
break;
}
default:
}
if (dcp) {
}
/* allow hotplug thread now */
hubd->h_hotplug_thread--;
hubd_start_polling(hubd, 0);
}
return (rv);
}
/*
* Helper func used only to help construct the names for the attachment point
* minor nodes. Used only in usba_hubdi_attach.
* Returns whether it found ancestry or not (USB_SUCCESS if yes).
* ports between the root hub and the device represented by dip.
* E.g., "2.4.3.1" means this device is
* plugged into port 1 of a hub that is
* plugged into port 3 of a hub that is
* plugged into port 4 of a hub that is
* plugged into port 2 of the root hub.
* NOTE: Max ap_id path len is HUBD_APID_NAMELEN (32 chars), which is
* more than sufficient (as hubs are a max 6 levels deep, port needs 3
* chars plus NULL each)
*/
void
{
char ap_name[HUBD_APID_NAMELEN];
"hubd_get_ancestry_str: hubd=0x%p", (void *)hubd);
/*
* The function is extended to support wire adapter class
* devices introduced by WUSB spec. The node name is no
* longer "hub" only.
* Generate the ap_id str based on the parent and child
* relationship instead of retrieving it from the hub
* device path, which simplifies the algorithm.
*/
} else {
/*
* The parent of wire adapter device might be usb_mid.
* Need to look further up for hub device
*/
}
}
}
/* Get which port to operate on. */
static usb_port_t
{
/* Get which port to operate on. */
"hubd_get_port_num: port lookup failed");
port = 0;
}
return ((usb_port_t)port);
}
/* check if child still exists */
static dev_info_t *
{
return (child_dip);
}
/*
* hubd_cfgadm_state:
*
* child_dip list port_state cfgadm_state
* -------------- ---------- ------------
* != NULL connected configured or
* unconfigured
* != NULL not connected disconnect but
* NULL connected logically disconnected
* NULL not connected empty
*/
static uint_t
{
if (child_dip) {
/*
* connected, now check if driver exists
*/
if (DEVI_IS_DEVICE_OFFLINE(child_dip) ||
} else {
}
} else {
/*
* this means that the dip is around for
* a device that is still referenced but
* has been yanked out. So the cfgadm info
* for this state should be EMPTY (port empty)
* and CONFIGURED (dip still valid).
*/
}
} else {
/* connected but no child dip */
/* logically disconnected */
} else {
/* physically disconnected */
}
}
"hubd_cfgadm_state: hubd=0x%p, port=%d state=0x%x",
return (state);
}
/*
* hubd_toggle_port:
*/
static int
{
int wait;
return (USB_FAILURE);
}
/*
* see hubd_enable_all_port_power() which
* requires longer delay for hubs.
*/
/*
* According to section 11.11 of USB, for hubs with no power
* switches, bPwrOn2PwrGood is zero. But we wait for some
* arbitrary time to enable power to become stable.
*
* If an hub supports port power swicthing, we need to wait
* at least 20ms before accesing corresonding usb port.
*/
if ((hub_descr->wHubCharacteristics &
} else {
}
"hubd_toggle_port: popg=%d wait=%d",
retry = 0;
do {
/* Get port status */
/* For retry if any, use some extra delay */
retry++;
/* Print warning message if port has no power */
if (!(status & PORT_STATUS_PPS)) {
"hubd_toggle_port: port %d power-on failed, "
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* hubd_init_power_budget:
* Init power budget variables in hubd structure. According
* to USB spec, the power budget rules are:
* 1. local-powered hubs including root-hubs can supply
* 500mA to each port at maximum
* 2. two bus-powered hubs are not allowed to concatenate
* 3. bus-powered hubs can supply 100mA to each port at
* maximum, and the power consumed by all downstream
* ports and the hub itself cannot exceed the max power
* supplied by the upstream port, i.e., 500mA
* The routine is only called during hub attach time
*/
static int
{
if (hubd->h_ignore_pwr_budget) {
return (USB_SUCCESS);
}
"hubd_init_power_budget:");
/* get device status */
0, &status, 0)) != USB_SUCCESS) {
return (USB_FAILURE);
}
if (size != USB_CFG_DESCR_SIZE) {
"get hub configuration descriptor failed");
return (USB_FAILURE);
}
if (hubd->h_local_pwr_capable) {
"hub is capable of local power");
}
if (hubd->h_local_pwr_on) {
"hub is local-powered");
} else {
/*
* two bus-powered hubs are not
* allowed to be concatenated
*/
if (!phubd->h_ignore_pwr_budget) {
"two bus-powered hubs cannot "
"be concatenated");
return (USB_FAILURE);
}
}
"hub is bus-powered");
} else {
"root-hub must be local-powered");
}
/*
* Subtract the power consumed by the hub itself
* and get the power that can be supplied to
* downstream ports
*/
hubd->h_pwr_left -=
if (hubd->h_pwr_left < 0) {
"hubd->h_pwr_left is less than bHubContrCurrent, "
"should fail");
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
}
/*
* usba_hubdi_check_power_budget:
* Check if the hub has enough power budget to allow a
* child device to select a configuration of config_index.
*/
int
{
return (USB_FAILURE);
}
if (hubd->h_ignore_pwr_budget) {
return (USB_SUCCESS);
}
"usba_hubdi_check_power_budget: "
"dip=0x%p child_ud=0x%p conf_index=%d", (void *)dip,
(void *)child_ud, config_index);
}
"usba_hubdi_check_power_budget: "
if (size != USB_CFG_DESCR_SIZE) {
"get hub configuration descriptor failed");
return (USB_FAILURE);
}
"usba_hubdi_check_power_budget: "
"child bmAttributes=0x%x bMaxPower=%d "
if (pwr_required > pwr_limit) {
"configuration %d for device %s %s at port %d "
"exceeds power available for this port, please "
"re-insert your device into another hub port which "
"has enough power",
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
/*
* usba_hubdi_incr_power_budget:
* Increase the hub power budget value when a child device
* is removed from a bus-powered hub port.
*/
void
{
if (hubd->h_ignore_pwr_budget) {
return;
}
"usba_hubdi_incr_power_budget: "
"usba_hubdi_incr_power_budget: "
"hub is local powered");
return;
}
if (child_ud->usb_pwr_from_hub == 0) {
return;
}
"usba_hubdi_incr_power_budget: "
"available power is %dmA, increased by %dmA",
child_ud->usb_pwr_from_hub = 0;
}
/*
* usba_hubdi_decr_power_budget:
* Decrease the hub power budget value when a child device
* is inserted to a bus-powered hub port.
*/
void
{
if (hubd->h_ignore_pwr_budget) {
return;
}
"usba_hubdi_decr_power_budget: "
"usba_hubdi_decr_power_budget: "
"hub is local powered");
return;
}
if (child_ud->usb_pwr_from_hub > 0) {
return;
}
"usba_hubdi_decr_power_budget: "
"available power is %dmA, decreased by %dmA",
}
/*
* hubd_wait_for_hotplug_exit:
* Waiting for the exit of the running hotplug thread or ioctl thread.
*/
static int
{
int rval;
if (hubd->h_hotplug_thread) {
"waiting for hubd hotplug thread exit");
return (USB_FAILURE);
}
}
return (USB_SUCCESS);
}
/*
* hubd_reset_thread:
* handles the "USB_RESET_LVL_REATTACH" reset of usb device.
*
* - delete the child (force detaching the device and its children)
* - reset the corresponding parent hub port
* - create the child (force re-attaching the device and its children)
*/
static void
hubd_reset_thread(void *arg)
{
char *devname;
int i = 0;
int rval = USB_FAILURE;
"hubd_reset_thread: started, hubd_reset_port = 0x%x", reset_port);
/* if our bus power entry point is active, quit the reset */
"%s%d is under bus power management, cannot be reset. "
"Please disconnect and reconnect this device.",
goto Fail;
}
/* we got woken up because of a timeout */
" %s%d. Please disconnect and reconnect this device.",
goto Fail;
}
hubd->h_hotplug_thread++;
/* is this the root hub? */
/* mark the root hub as full power */
"hubd_reset_thread: call pm_power_has_changed");
(void) pm_power_has_changed(hdip, 0,
}
/*
* this ensures one reset activity per system at a time.
* we enter the parent PCI node to have this serialization.
* this also excludes ioctls and deathrow thread
*/
/* exclude other threads */
/*
* We need to make sure that the child is still online for a hotplug
* thread could have inserted which detached the child.
*/
/* First disconnect the device */
/* delete cached dv_node's but drop locks first */
/*
* workaround only for storage device. When it's able to force
* detach a driver, this code can be removed safely.
*
* If we're to reset storage device and the device is used, we
* will wait at most extra 20s for applications to exit and
* close the device. This is especially useful for HAL-based
* applications.
*/
while (i++ < hubdi_reset_delay) {
if (rval == USB_SUCCESS)
break;
}
}
/* Then force detaching the device */
"%s%d cannot be reset due to other applications "
"are using it, please first close these "
"applications, then disconnect and reconnect"
/* post a re-connect event */
} else {
/* Reset the parent hubd port and create new child */
if (status & PORT_STATUS_CCS) {
reset_port) == USB_SUCCESS);
}
}
}
/* release locks so we can do a devfs_clean */
/* delete cached dv_node's but drop locks first */
/* now check if any children need onlining */
if (online_child) {
"hubd_reset_thread: onlining children");
}
/* allow hotplug thread now */
hubd->h_hotplug_thread--;
Fail:
hubd_start_polling(hubd, 0);
/* mark this device as idle */
}
/*
* hubd_check_same_device:
* - open the default pipe of the device.
* - compare the old and new descriptors of the device.
* - close the default pipe.
*/
static int
{
int rval = USB_FAILURE;
/* Open the default pipe to operate the device */
&ph) == USB_SUCCESS) {
/*
* Check that if the device's descriptors are different
* from the values saved before the port reset.
*/
}
return (rval);
}
/*
* usba_hubdi_reset_device
* Called by usb_reset_device to handle usb device reset.
*/
int
{
usb_port_t port = 0;
int i, ph_open_cnt;
int rval = USB_FAILURE;
"usba_hubdi_reset_device: NULL dip or root hub");
return (USB_INVALID_ARGS);
}
if (!usb_owns_device(dip)) {
"usba_hubdi_reset_device: Not owns the device");
return (USB_INVALID_PERM);
}
if ((reset_level != USB_RESET_LVL_REATTACH) &&
(reset_level != USB_RESET_LVL_DEFAULT)) {
"usba_hubdi_reset_device: Unknown flags");
return (USB_INVALID_ARGS);
}
"usba_hubdi_reset_device: fail to get parent hub");
return (USB_INVALID_ARGS);
}
"usba_hubdi_reset_device: fail to get hub softstate");
return (USB_INVALID_ARGS);
}
/* make sure the hub is connected before trying any kinds of reset. */
"associated to the device 0x%p is incorrect",
return (USB_INVALID_ARGS);
}
"usb_reset_device: the corresponding port is resetting");
return (USB_SUCCESS);
}
/*
* For Default reset, client drivers should first close all the pipes
* except default pipe before calling the function, also should not
* call the function during interrupt context.
*/
if (reset_level == USB_RESET_LVL_DEFAULT) {
if (servicing_interrupt()) {
"usb_reset_device: during interrput context, quit");
return (USB_INVALID_CONTEXT);
}
/* Check if all the pipes have been closed */
ph_open_cnt++;
break;
}
}
if (ph_open_cnt) {
"usb_reset_device: %d pipes are still open",
return (USB_BUSY);
}
}
/* Don't perform reset while the device is detaching */
"usb_reset_device: the device is detaching, "
"cannot be reset");
return (USB_FAILURE);
}
/* Don't allow hub detached during the reset */
/* go full power */
hubd->h_hotplug_thread++;
/* stop polling if it was active */
if (prev_pipe_state == USB_PIPE_STATE_ACTIVE) {
}
}
switch (reset_level) {
case USB_RESET_LVL_REATTACH:
sizeof (hubd_reset_arg_t), KM_SLEEP);
(void *)arg, 0)) == USB_SUCCESS) {
hubd->h_hotplug_thread--;
return (USB_SUCCESS);
} else {
"Cannot create reset thread, the device %s%d failed"
}
break;
case USB_RESET_LVL_DEFAULT:
/*
* Reset hub port and then recover device's address, set back
* device's configuration, hubd_handle_port_connect() will
* handle errors happened during this process.
*/
== USB_SUCCESS) {
/* re-open the default pipe */
if (rval != USB_SUCCESS) {
"default pipe after reset, disable hub"
/*
* Disable port to set out a hotplug thread
* which will handle errors.
*/
}
}
break;
default:
break;
}
/* allow hotplug thread now */
hubd->h_hotplug_thread--;
(prev_pipe_state == USB_PIPE_STATE_ACTIVE)) {
hubd_start_polling(hubd, 0);
}
/* Clear reset mark for the port. */
return (rval);
}