/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* Copyright 2012 Garrett D'Amore <garrett@damore.org>. All rights reserved.
*/
/*
* usb interface association driver
*
* this driver attempts to the interface association node and
*/
#endif
/* Debugging support */
nodev, /* open */
nodev, /* close */
nodev, /* strategy */
nodev, /* print */
nodev, /* dump */
nodev, /* read */
nodev, /* write */
nodev, /* ioctl */
nodev, /* devmap */
nodev, /* mmap */
nodev, /* segmap */
nochpoll, /* poll */
ddi_prop_op, /* prop_op */
NULL, /* aread */
};
char *eventname,
void *bus_impldata),
void *bus_impldata);
void *arg,
dev_info_t **child);
void *arg);
/*
* autoconfiguration data and routines.
*/
void *, void **);
/* other routines */
ddi_ctl_enum_t, void *, void *);
static int usb_ia_power(dev_info_t *, int, int);
/* prototypes */
static void usb_ia_create_children(usb_ia_t *);
static int usb_ia_cleanup(usb_ia_t *);
/*
* Busops vector
*/
nullbusmap, /* bus_map */
NULL, /* bus_get_intrspec */
NULL, /* bus_add_intrspec */
NULL, /* bus_remove_intrspec */
NULL, /* XXXX bus_map_fault */
NULL, /* bus_dma_map */
ddi_dma_mctl, /* bus_dma_ctl */
usb_ia_bus_ctl, /* bus_ctl */
ddi_bus_prop_op, /* bus_prop_op */
usb_ia_busop_post_event, /* bus_post_event */
NULL, /* bus_intr_ctl */
usb_ia_bus_config, /* bus_config */
usb_ia_bus_unconfig, /* bus_unconfig */
NULL, /* bus_fm_init */
NULL, /* bus_fm_fini */
NULL, /* bus_fm_access_enter */
NULL, /* bus_fm_access_exit */
NULL /* bus_power */
};
DEVO_REV, /* devo_rev, */
0, /* refcnt */
usb_ia_info, /* info */
nulldev, /* identify */
nulldev, /* probe */
usb_ia_attach, /* attach */
usb_ia_detach, /* detach */
nodev, /* reset */
&usb_ia_cb_ops, /* driver operations */
&usb_ia_busops, /* bus operations */
usb_ia_power, /* power */
ddi_quiesce_not_needed, /* devo_quiesce */
};
&mod_driverops, /* Type of module. This one is a driver */
"USB Interface Association Driver", /* Name of the module. */
&usb_ia_ops, /* driver ops */
};
};
static void *usb_ia_statep;
/*
* event definition
*/
};
#define USB_IA_N_NDI_EVENTS \
(sizeof (usb_ia_ndi_event_defs) / sizeof (ndi_event_definition_t))
/*
* standard driver entry points
*/
int
_init(void)
{
int rval;
if (rval != 0) {
return (rval);
}
return (rval);
}
return (rval);
}
int
_fini(void)
{
int rval;
if (rval) {
return (rval);
}
return (rval);
}
int
{
}
/*ARGSUSED*/
static int
{
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
*/
static void
{
}
static void
{
}
/*
* bus ctl support. we handle notifications here and the
*/
/*ARGSUSED*/
static int
void *arg,
void *result)
{
"usb_ia_bus_ctl:\n\t"
"dip = 0x%p, rdip = 0x%p, op = 0x%x, arg = 0x%p",
switch (op) {
case DDI_CTLOPS_ATTACH:
case DDI_PRE :
/* nothing to do basically */
"DDI_PRE DDI_CTLOPS_ATTACH");
break;
case DDI_POST :
(struct attachspec *)arg);
break;
}
break;
case DDI_CTLOPS_DETACH:
case DDI_PRE :
/* nothing to do basically */
"DDI_PRE DDI_CTLOPS_DETACH");
break;
case DDI_POST :
(struct detachspec *)arg);
break;
}
break;
default:
/* pass to root hub to handle */
}
return (DDI_SUCCESS);
}
/*
* bus enumeration entry points
*/
static int
{
"usb_ia_bus_config: op=%d", op);
if (usb_ia_bus_config_debug) {
flag |= NDI_DEVI_DEBUG;
}
/* enumerate each interface below us */
return (rval);
}
static int
void *arg)
{
"usb_ia_bus_unconfig: op=%d", op);
if (usb_ia_bus_config_debug) {
flag |= NDI_DEVI_DEBUG;
}
/*
* first offline and if offlining successful, then
* remove children
*/
if (op == BUS_UNCONFIG_ALL) {
}
(flag & NDI_AUTODETACH) == 0) {
flag |= NDI_DEVI_REMOVE;
}
/* update children's list */
/* now search if this dip still exists */
/* we lost the dip on this interface */
} else if (cdip) {
/*
* keep in DS_INITALIZED to prevent parent
* from detaching
*/
}
}
"usb_ia_bus_config: rval=%d", rval);
return (rval);
}
/* power entry point */
/* ARGSUSED */
static int
{
"usb_ia_power: Begin: usb_ia = %p, level = %d",
/* check if we are transitioning to a legal power level */
"usb_ia_power: illegal power level = %d "
return (rval);
}
return (rval);
}
/*
*/
static int
{
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* Attach:
*
* Allocate soft state and initialize
*/
goto fail;
}
goto fail;
}
/* allocate handle for logging of messages */
0);
"interface-association property failed");
goto fail;
}
/* attach client driver to USBA */
"usb_client_attach failed");
goto fail;
}
0) != USB_SUCCESS) {
"usb_get_dev_data failed");
goto fail;
}
DDI_NT_NEXUS, 0) != DDI_SUCCESS) {
"cannot create devctl minor node");
goto fail;
}
/*
* allocate array for keeping track of child dips
*/
KM_SLEEP);
/*
* Event handling: definition and registration
* get event handle for events that we have defined
*/
/* bind event set to the handle */
NDI_SLEEP)) {
"usb_ia_attach: binding event set failed");
goto fail;
}
/*
* now create components to power manage this device
* before attaching children
*/
/* event registration for events from our parent */
return (DDI_SUCCESS);
fail:
instance);
if (usb_ia) {
(void) usb_ia_cleanup(usb_ia);
}
return (DDI_FAILURE);
}
/* detach or suspend this instance */
static int
{
"usb_ia_detach: cmd = 0x%x", cmd);
switch (cmd) {
case DDI_DETACH:
return (usb_ia_cleanup(usb_ia));
case DDI_SUSPEND:
/* nothing to do */
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/* NOTREACHED */
}
/*
* usb_ia_cleanup:
* cleanup usb_ia and deallocate. this function is called for
* handling attach failures and detaching including dynamic
* reconfiguration
*/
/*ARGSUSED*/
static int
{
int rval;
"usb_ia_cleanup:");
goto done;
}
/*
* deallocate events, if events are still registered
* (ie. children still attached) then we have to fail the detach
*/
if (usb_ia->ia_ndi_event_hdl &&
"usb_ia_cleanup: ndi_event_free_hdl failed");
return (DDI_FAILURE);
}
/*
* Disable the event callbacks, 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.
*/
}
(void) pm_busy_component(dip, 0);
if (iapm->uc_wakeup_enabled) {
/* First bring the device to full power */
if (rval != DDI_SUCCESS) {
"usb_cleanup: disable remote "
"wakeup failed, rval=%d", rval);
}
}
(void) pm_idle_component(dip, 0);
} else {
}
if (iapm) {
}
/* free children list */
if (usb_ia->ia_children_dips) {
}
if (usb_ia->ia_child_events) {
}
}
done:
return (DDI_SUCCESS);
}
/*
* usb_ia_create_children:
*/
static void
{
uint_t i;
"usb_ia_attach_child_drivers: port = %d, address = %d",
/*
* create all children if not already present
*/
for (i = 0; i < n_ifs; i++) {
continue;
}
(void) usba_bind_driver(cdip);
}
}
}
/*
* event support
*/
static int
{
"usb_ia_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),
{
int ifno;
if (ifno < 0) {
ifno = 0;
}
"usb_ia_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 (perform registration) */
}
static int
{
"usb_ia_busop_remove_eventcall: dip=0x%p, rdip=0x%p "
(void *)cb->ndi_evtcb_cookie);
"(dip=%s%d rdip=%s%d event=%s)",
cb->ndi_evtcb_cookie));
/* remove event registration from our event set */
}
static int
void *bus_impldata)
{
"usb_ia_busop_post_event: dip=0x%p, rdip=0x%p "
"cookie=0x%p, impl=0x%p",
"(dip=%s%d rdip=%s%d event=%s)",
/* post event to all children registered for this event */
cookie, bus_impldata));
}
/*
* usb_ia_restore_device_state
* set the original configuration of the device
*/
static int
{
"usb_ia_restore_device_state: usb_ia = %p", (void *)usb_ia);
/* First bring the device to full power */
(void) pm_busy_component(dip, 0);
/* change the device state from suspended to disconnected */
(void) pm_idle_component(dip, 0);
return (USB_FAILURE);
}
/*
* if the device had remote wakeup earlier,
* enable it again
*/
if (iapm->uc_wakeup_enabled) {
}
(void) pm_idle_component(dip, 0);
return (USB_SUCCESS);
}
/*
* usb_ia_event_cb()
* handle disconnect and connect events
*/
static void
void *arg, void *bus_impldata)
{
int i, tag;
"usb_ia_event_cb: dip=0x%p, cookie=0x%p, "
"arg=0x%p, impl=0x%p",
"(dip=%s%d event=%s)",
switch (tag) {
"usb_ia_event_cb: Device already disconnected");
} else {
/* we are disconnected so set our state now */
usb_ia->ia_child_events[i] &= ~
}
/* pass disconnect event to all the children */
(void) ndi_event_run_callbacks(
}
break;
/* set our state *after* suspending children */
/* pass pre_suspend event to all the children */
usb_ia->ia_child_events[i] &= ~
}
break;
/*
* Check to see if this child has missed the disconnect
* event before it registered for event cb
*/
if (usb_ia->ia_child_events[i] &
usb_ia->ia_child_events[i] &=
usb_ia->ia_children_dips[i];
/* post the missed disconnect */
(void) ndi_event_do_callback(
}
}
/* pass reconnect event to all the children */
(void) ndi_event_run_callbacks(
}
break;
/*
* Check to see if this child has missed the pre-suspend
* event before it registered for event cb
*/
if (usb_ia->ia_child_events[i] &
usb_ia->ia_child_events[i] &=
/* post the missed pre-suspend event */
(void) ndi_event_do_callback(
}
}
/* pass post_resume event to all the children */
break;
}
}
/*
* create the pm components required for power management
*/
static void
{
"usb_ia_create_pm_components: Begin");
/* Allocate the PM state structure */
/*
* By not enabling parental notification, PM enforces
* "strict parental dependency" meaning, usb_ia won't
* power off until any of its children are in full power.
*/
/*
* there are 3 scenarios:
* 1. a well behaved device should have remote wakeup
* at interface and device level. If the interface
* wakes up, usb_ia will wake up
* 2. if the device doesn't have remote wake up and
* the interface has, PM will still work, ie.
* the interfaces wakes up and usb_ia wakes up
* 3. if neither the interface nor device has remote
* wakeup, the interface will wake up when it is opened
* and goes to sleep after being closed for a while
* In this case usb_ia should also go to sleep shortly
* thereafter
* In all scenarios it doesn't really matter whether
* remote wakeup at the device level is enabled or not
* but we do it anyways
*/
USB_SUCCESS) {
"usb_ia_create_pm_components: "
"Remote Wakeup Enabled");
}
USB_SUCCESS) {
}
"usb_ia_create_pm_components: End");
}
/*
* usb_ia_obtain_state:
*/
static usb_ia_t *
{
return (statep);
}