uhci.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
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.
*/
/*
* Universal Host Controller Driver (UHCI)
*
* The UHCI driver is a driver which interfaces to the Universal
* Serial Bus Architecture (USBA) and the Host Controller (HC). The interface to
* the Host Controller is defined by the Universal Host Controller Interface.
* This file contains code for auto-configuration entry points and interrupt
* handling.
*/
#include <sys/usb/hcd/uhci/uhcid.h>
#include <sys/usb/hcd/uhci/uhcihub.h>
#include <sys/usb/hcd/uhci/uhciutil.h>
/*
* Prototype Declarations for cb_ops and dev_ops
*/
static int uhci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int uhci_add_intrs(uhci_state_t *uhcip, int intr_type);
static int uhci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static void uhci_rem_intrs(uhci_state_t *uhcip);
static int uhci_open(dev_t *devp, int flags, int otyp, cred_t *credp);
static int uhci_close(dev_t dev, int flag, int otyp, cred_t *credp);
static int uhci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp);
static int uhci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd);
static int uhci_quiesce(dev_info_t *dip);
static int uhci_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg,
void **result);
/* extern */
int usba_hubdi_root_hub_power(dev_info_t *dip, int comp, int level);
static struct cb_ops uhci_cb_ops = {
uhci_open, /* Open */
uhci_close, /* Close */
nodev, /* Strategy */
nodev, /* Print */
nodev, /* Dump */
nodev, /* Read */
nodev, /* Write */
uhci_ioctl, /* Ioctl */
nodev, /* Devmap */
nodev, /* Mmap */
nodev, /* Segmap */
nochpoll, /* Poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* Streamtab */
D_MP /* Driver compatibility flag */
};
static struct dev_ops uhci_ops = {
DEVO_REV, /* Devo_rev */
0, /* Refcnt */
uhci_info, /* Info */
nulldev, /* Identify */
nulldev, /* Probe */
uhci_attach, /* Attach */
uhci_detach, /* Detach */
uhci_reset, /* Reset */
&uhci_cb_ops, /* Driver operations */
&usba_hubdi_busops, /* Bus operations */
usba_hubdi_root_hub_power, /* Power */
uhci_quiesce /* quiesce */
};
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"USB UHCI Controller Driver", /* Name of the module. */
&uhci_ops, /* Driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
/*
* Globals
*/
void *uhci_statep;
uint_t uhci_errlevel = USB_LOG_L2;
uint_t uhci_errmask = PRINT_MASK_ALL;
uint_t uhci_instance_debug = (uint_t)-1;
uint_t uhci_td_pool_size = 256; /* Num TDs */
uint_t uhci_qh_pool_size = 130; /* Num QHs */
ushort_t uhci_tree_bottom_nodes[NUM_FRAME_LST_ENTRIES];
/*
* UHCI MSI tunable:
*
* By default MSI is enabled on all supported platforms.
*/
boolean_t uhci_enable_msi = B_TRUE;
/*
* tunable, delay during attach in seconds
*/
int uhci_attach_wait = 0;
/* function prototypes */
static void uhci_handle_intr_td_errors(uhci_state_t *uhcip, uhci_td_t *td,
uhci_trans_wrapper_t *tw, uhci_pipe_private_t *pp);
static void uhci_handle_one_xfer_completion(uhci_state_t *uhcip,
usb_cr_t usb_err, uhci_td_t *td);
static uint_t uhci_intr(caddr_t arg1, caddr_t arg2);
static int uhci_cleanup(uhci_state_t *uhcip);
static int uhci_cpr_suspend(uhci_state_t *uhcip);
static int uhci_cpr_resume(uhci_state_t *uhcip);
int
_init(void)
{
int error;
ushort_t i, j, k, *temp, num_of_nodes;
/* Initialize the soft state structures */
if ((error = ddi_soft_state_init(&uhci_statep, sizeof (uhci_state_t),
UHCI_MAX_INSTS)) != 0) {
return (error);
}
/* Install the loadable module */
if ((error = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&uhci_statep);
return (error);
}
/*
* Build the tree bottom shared by all instances
*/
temp = kmem_zalloc(NUM_FRAME_LST_ENTRIES * 2, KM_SLEEP);
num_of_nodes = 1;
for (i = 0; i < log_2(NUM_FRAME_LST_ENTRIES); i++) {
for (j = 0, k = 0; k < num_of_nodes; k++, j++) {
uhci_tree_bottom_nodes[j++] = temp[k];
uhci_tree_bottom_nodes[j] = temp[k] + pow_2(i);
}
num_of_nodes *= 2;
for (k = 0; k < num_of_nodes; k++)
temp[k] = uhci_tree_bottom_nodes[k];
}
kmem_free(temp, (NUM_FRAME_LST_ENTRIES*2));
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int error;
error = mod_remove(&modlinkage);
if (error == 0) {
/* Release per module resources */
ddi_soft_state_fini(&uhci_statep);
}
return (error);
}
/*
* The following simulated polling is for debugging purposes only.
* It is activated on x86 by setting usb-polling=true in GRUB or uhci.conf.
*/
static int
uhci_is_polled(dev_info_t *dip)
{
int ret;
char *propval;
if (ddi_prop_lookup_string(DDI_DEV_T_ANY, dip, 0,
"usb-polling", &propval) != DDI_SUCCESS)
return (0);
ret = (strcmp(propval, "true") == 0);
ddi_prop_free(propval);
return (ret);
}
static void
uhci_poll_intr(void *arg)
{
/* poll every msec */
for (;;) {
(void) uhci_intr(arg, NULL);
delay(drv_usectohz(1000));
}
}
/*
* Host Controller Driver (HCD) Auto configuration entry points
*/
/*
* Function Name : uhci_attach:
* Description : Attach entry point - called by the Kernel.
* Allocates of per controller data structure.
* Initializes the controller.
* Output : DDI_SUCCESS / DDI_FAILURE
*/
static int
uhci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
int instance, polled;
int i, intr_types;
uhci_state_t *uhcip = NULL;
usba_hcdi_register_args_t hcdi_args;
USB_DPRINTF_L4(PRINT_MASK_ATTA, NULL, "uhci_attach:");
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
uhcip = uhci_obtain_state(dip);
return (uhci_cpr_resume(uhcip));
default:
return (DDI_FAILURE);
}
/* Get the instance and create soft state */
instance = ddi_get_instance(dip);
/* Allocate the soft state structure for this instance of the driver */
if (ddi_soft_state_zalloc(uhci_statep, instance) != 0) {
return (DDI_FAILURE);
}
if ((uhcip = ddi_get_soft_state(uhci_statep, instance)) == NULL) {
return (DDI_FAILURE);
}
uhcip->uhci_log_hdl = usb_alloc_log_hdl(dip, "uhci", &uhci_errlevel,
&uhci_errmask, &uhci_instance_debug, 0);
/* Set host controller soft state to initialization */
uhcip->uhci_hc_soft_state = UHCI_CTLR_INIT_STATE;
/* Save the dip and instance */
uhcip->uhci_dip = dip;
uhcip->uhci_instance = instance;
polled = uhci_is_polled(dip);
if (polled)
goto skip_intr;
/* Get supported interrupt types */
if (ddi_intr_get_supported_types(uhcip->uhci_dip,
&intr_types) != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: ddi_intr_get_supported_types failed");
usb_free_log_hdl(uhcip->uhci_log_hdl);
ddi_soft_state_free(uhci_statep, instance);
return (DDI_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: supported interrupt types 0x%x", intr_types);
if ((intr_types & DDI_INTR_TYPE_MSI) && uhci_enable_msi) {
if (uhci_add_intrs(uhcip, DDI_INTR_TYPE_MSI)
!= DDI_SUCCESS) {
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: MSI registration failed, "
"trying FIXED interrupt \n");
} else {
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: Using MSI interrupt type\n");
uhcip->uhci_intr_type = DDI_INTR_TYPE_MSI;
}
}
if (!(uhcip->uhci_htable) && (intr_types & DDI_INTR_TYPE_FIXED)) {
if (uhci_add_intrs(uhcip, DDI_INTR_TYPE_FIXED)
!= DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: FIXED interrupt registration "
"failed\n");
usb_free_log_hdl(uhcip->uhci_log_hdl);
ddi_soft_state_free(uhci_statep, instance);
return (DDI_FAILURE);
}
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: Using FIXED interrupt type\n");
uhcip->uhci_intr_type = DDI_INTR_TYPE_FIXED;
}
skip_intr:
/* Semaphore to serialize opens and closes */
sema_init(&uhcip->uhci_ocsem, 1, NULL, SEMA_DRIVER, NULL);
/* Create prototype condition variable */
cv_init(&uhcip->uhci_cv_SOF, NULL, CV_DRIVER, NULL);
/* Initialize the DMA attributes */
uhci_set_dma_attributes(uhcip);
/* Initialize the kstat structures */
uhci_create_stats(uhcip);
/* Create the td and ed pools */
if (uhci_allocate_pools(uhcip) != USB_SUCCESS) {
goto fail;
}
/* Map the registers */
if (uhci_map_regs(uhcip) != USB_SUCCESS) {
goto fail;
}
/* Enable all interrupts */
if (polled) {
extern pri_t maxclsyspri;
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach: running in simulated polled mode.");
/* create thread to poll */
(void) thread_create(NULL, 0, uhci_poll_intr, uhcip, 0, &p0,
TS_RUN, maxclsyspri);
} else if (uhcip->uhci_intr_cap & DDI_INTR_FLAG_BLOCK) {
/* Call ddi_intr_block_enable() for MSI interrupts */
(void) ddi_intr_block_enable(uhcip->uhci_htable,
uhcip->uhci_intr_cnt);
} else {
/* Call ddi_intr_enable for MSI or FIXED interrupts */
for (i = 0; i < uhcip->uhci_intr_cnt; i++)
(void) ddi_intr_enable(uhcip->uhci_htable[i]);
}
/* Initialize the controller */
if (uhci_init_ctlr(uhcip) != USB_SUCCESS) {
goto fail;
}
/*
* At this point, the hardware will be okay.
* Initialize the usba_hcdi structure
*/
uhcip->uhci_hcdi_ops = uhci_alloc_hcdi_ops(uhcip);
/*
* Make this HCD instance known to USBA
* (dma_attr must be passed for USBA busctl's)
*/
hcdi_args.usba_hcdi_register_version = HCDI_REGISTER_VERSION;
hcdi_args.usba_hcdi_register_dip = dip;
hcdi_args.usba_hcdi_register_ops = uhcip->uhci_hcdi_ops;
hcdi_args.usba_hcdi_register_dma_attr = &uhcip->uhci_dma_attr;
hcdi_args.usba_hcdi_register_iblock_cookie =
(ddi_iblock_cookie_t)(uintptr_t)uhcip->uhci_intr_pri;
if (usba_hcdi_register(&hcdi_args, 0) != USB_SUCCESS) {
goto fail;
}
#ifndef __sparc
/*
* On NCR system, the driver seen failure of some commands
* while booting. This delay mysteriously solved the problem.
*/
delay(drv_usectohz(uhci_attach_wait*1000000));
#endif
/*
* Create another timeout handler to check whether any
* control/bulk/interrupt commands failed.
* This gets called every second.
*/
uhcip->uhci_cmd_timeout_id = timeout(uhci_cmd_timeout_hdlr,
(void *)uhcip, UHCI_ONE_SECOND);
mutex_enter(&uhcip->uhci_int_mutex);
/*
* Set HcInterruptEnable to enable all interrupts except Root
* Hub Status change and SOF interrupts.
*/
Set_OpReg16(USBINTR, ENABLE_ALL_INTRS);
/* Test the SOF interrupt */
if (uhci_wait_for_sof(uhcip) != USB_SUCCESS) {
USB_DPRINTF_L0(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"No SOF interrupts have been received, this USB UHCI host"
" controller is unusable");
mutex_exit(&uhcip->uhci_int_mutex);
goto fail;
}
mutex_exit(&uhcip->uhci_int_mutex);
/* This should be the last step which might fail during attaching */
if (uhci_init_root_hub(uhcip) != USB_SUCCESS) {
goto fail;
}
/* Display information in the banner */
ddi_report_dev(dip);
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_attach successful");
return (DDI_SUCCESS);
fail:
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"failed to attach");
(void) uhci_cleanup(uhcip);
return (DDI_FAILURE);
}
/*
* uhci_add_intrs:
*
* Register FIXED or MSI interrupts.
*/
static int
uhci_add_intrs(uhci_state_t *uhcip,
int intr_type)
{
int actual, avail, intr_size, count = 0;
int i, flag, ret;
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: interrupt type 0x%x", intr_type);
/* Get number of interrupts */
ret = ddi_intr_get_nintrs(uhcip->uhci_dip, intr_type, &count);
if ((ret != DDI_SUCCESS) || (count == 0)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: ddi_intr_get_nintrs() failure, "
"ret: %d, count: %d", ret, count);
return (DDI_FAILURE);
}
/* Get number of available interrupts */
ret = ddi_intr_get_navail(uhcip->uhci_dip, intr_type, &avail);
if ((ret != DDI_SUCCESS) || (avail == 0)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: ddi_intr_get_navail() failure, "
"ret: %d, count: %d", ret, count);
return (DDI_FAILURE);
}
if (avail < count) {
USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: uhci_add_intrs: nintrs () "
"returned %d, navail returned %d\n", count, avail);
}
/* Allocate an array of interrupt handles */
intr_size = count * sizeof (ddi_intr_handle_t);
uhcip->uhci_htable = kmem_zalloc(intr_size, KM_SLEEP);
flag = (intr_type == DDI_INTR_TYPE_MSI) ?
DDI_INTR_ALLOC_STRICT:DDI_INTR_ALLOC_NORMAL;
/* call ddi_intr_alloc() */
ret = ddi_intr_alloc(uhcip->uhci_dip, uhcip->uhci_htable,
intr_type, 0, count, &actual, flag);
if ((ret != DDI_SUCCESS) || (actual == 0)) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: ddi_intr_alloc() failed %d", ret);
kmem_free(uhcip->uhci_htable, intr_size);
return (DDI_FAILURE);
}
if (actual < count) {
USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: Requested: %d, Received: %d\n",
count, actual);
for (i = 0; i < actual; i++)
(void) ddi_intr_free(uhcip->uhci_htable[i]);
kmem_free(uhcip->uhci_htable, intr_size);
return (DDI_FAILURE);
}
uhcip->uhci_intr_cnt = actual;
if ((ret = ddi_intr_get_pri(uhcip->uhci_htable[0],
&uhcip->uhci_intr_pri)) != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: ddi_intr_get_pri() failed %d", ret);
for (i = 0; i < actual; i++)
(void) ddi_intr_free(uhcip->uhci_htable[i]);
kmem_free(uhcip->uhci_htable, intr_size);
return (DDI_FAILURE);
}
USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: Supported Interrupt priority 0x%x",
uhcip->uhci_intr_pri);
/* Test for high level mutex */
if (uhcip->uhci_intr_pri >= ddi_intr_get_hilevel_pri()) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: Hi level interrupt not supported");
for (i = 0; i < actual; i++)
(void) ddi_intr_free(uhcip->uhci_htable[i]);
kmem_free(uhcip->uhci_htable, intr_size);
return (DDI_FAILURE);
}
/* Initialize the mutex */
mutex_init(&uhcip->uhci_int_mutex, NULL, MUTEX_DRIVER,
DDI_INTR_PRI(uhcip->uhci_intr_pri));
/* Call ddi_intr_add_handler() */
for (i = 0; i < actual; i++) {
if ((ret = ddi_intr_add_handler(uhcip->uhci_htable[i],
uhci_intr, (caddr_t)uhcip,
(caddr_t)(uintptr_t)i)) != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: ddi_intr_add_handler() "
"failed %d", ret);
for (i = 0; i < actual; i++)
(void) ddi_intr_free(uhcip->uhci_htable[i]);
mutex_destroy(&uhcip->uhci_int_mutex);
kmem_free(uhcip->uhci_htable, intr_size);
return (DDI_FAILURE);
}
}
if ((ret = ddi_intr_get_cap(uhcip->uhci_htable[0],
&uhcip->uhci_intr_cap)) != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_add_intrs: ddi_intr_get_cap() failed %d", ret);
for (i = 0; i < actual; i++) {
(void) ddi_intr_remove_handler(uhcip->uhci_htable[i]);
(void) ddi_intr_free(uhcip->uhci_htable[i]);
}
mutex_destroy(&uhcip->uhci_int_mutex);
kmem_free(uhcip->uhci_htable, intr_size);
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* Function Name: uhci_detach
* Description: Detach entry point - called by the Kernel.
* Deallocates all the memory
* Unregisters the interrupt handle and other resources.
* Output: DDI_SUCCESS / DDI_FAILURE
*/
static int
uhci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
uhci_state_t *uhcip = uhci_obtain_state(dip);
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_detach:");
switch (cmd) {
case DDI_DETACH:
return (uhci_cleanup(uhcip) == USB_SUCCESS ?
DDI_SUCCESS : DDI_FAILURE);
case DDI_SUSPEND:
return (uhci_cpr_suspend(uhcip));
default:
return (DDI_FAILURE);
}
}
/*
* uhci_rem_intrs:
*
* Unregister FIXED or MSI interrupts
*/
static void
uhci_rem_intrs(uhci_state_t *uhcip)
{
int i;
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_rem_intrs: interrupt type 0x%x", uhcip->uhci_intr_type);
/* Disable all interrupts */
if (uhcip->uhci_intr_cap & DDI_INTR_FLAG_BLOCK) {
(void) ddi_intr_block_disable(uhcip->uhci_htable,
uhcip->uhci_intr_cnt);
} else {
for (i = 0; i < uhcip->uhci_intr_cnt; i++) {
(void) ddi_intr_disable(uhcip->uhci_htable[i]);
}
}
/* Call ddi_intr_remove_handler() */
for (i = 0; i < uhcip->uhci_intr_cnt; i++) {
(void) ddi_intr_remove_handler(uhcip->uhci_htable[i]);
(void) ddi_intr_free(uhcip->uhci_htable[i]);
}
kmem_free(uhcip->uhci_htable,
uhcip->uhci_intr_cnt * sizeof (ddi_intr_handle_t));
}
/*
* Function Name: uhci_reset
* Description: Reset entry point - called by the Kernel
* on the way down.
* The Toshiba laptop has been observed to hang
* on reboot when BIOS is set to suspend/resume.
* The resetting uhci on the way down solves the
* problem.
* Output: DDI_SUCCESS / DDI_FAILURE
*/
/* ARGSUSED */
static int
uhci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
uhci_state_t *uhcip = uhci_obtain_state(dip);
/* Disable all HC ED list processing */
Set_OpReg16(USBINTR, DISABLE_ALL_INTRS);
Set_OpReg16(USBCMD, 0);
return (DDI_SUCCESS);
}
/*
* quiesce(9E) entry point.
*
* This function is called when the system is single-threaded at high
* PIL with preemption disabled. Therefore, this function must not be
* blocked.
*
* This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
* DDI_FAILURE indicates an error condition and should almost never happen.
*/
static int
uhci_quiesce(dev_info_t *dip)
{
uhci_state_t *uhcip = uhci_obtain_state(dip);
if (uhcip == NULL)
return (DDI_FAILURE);
/* Disable interrupts */
Set_OpReg16(USBINTR, DISABLE_ALL_INTRS);
/* Stop the Host Controller */
Set_OpReg16(USBCMD, 0);
/* Clear all status bits */
Set_OpReg16(USBSTS, Get_OpReg16(USBSTS) & UHCI_INTR_MASK);
return (DDI_SUCCESS);
}
/*
* uhci_info:
*/
/* ARGSUSED */
static int
uhci_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result)
{
dev_t dev;
int instance;
int error = DDI_FAILURE;
uhci_state_t *uhcip;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
dev = (dev_t)arg;
instance = UHCI_UNIT(dev);
uhcip = ddi_get_soft_state(uhci_statep, instance);
if (uhcip != NULL) {
*result = (void *)uhcip->uhci_dip;
if (*result != NULL) {
error = DDI_SUCCESS;
}
} else {
*result = NULL;
}
break;
case DDI_INFO_DEVT2INSTANCE:
dev = (dev_t)arg;
instance = UHCI_UNIT(dev);
*result = (void *)(uintptr_t)instance;
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* uhci_cleanup:
* Cleanup on attach failure or detach
*/
static int
uhci_cleanup(uhci_state_t *uhcip)
{
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_cleanup:");
if (usba_hubdi_unbind_root_hub(uhcip->uhci_dip) != USB_SUCCESS) {
return (USB_FAILURE);
}
mutex_enter(&uhcip->uhci_int_mutex);
if (uhcip->uhci_cmd_timeout_id) {
timeout_id_t timeout_id = uhcip->uhci_cmd_timeout_id;
uhcip->uhci_cmd_timeout_id = 0;
mutex_exit(&uhcip->uhci_int_mutex);
(void) untimeout(timeout_id);
mutex_enter(&uhcip->uhci_int_mutex);
}
uhci_uninit_ctlr(uhcip);
mutex_exit(&uhcip->uhci_int_mutex);
/* do interrupt cleanup */
if (uhcip->uhci_htable) {
uhci_rem_intrs(uhcip);
}
mutex_enter(&uhcip->uhci_int_mutex);
usba_hcdi_unregister(uhcip->uhci_dip);
uhci_unmap_regs(uhcip);
uhci_free_pools(uhcip);
mutex_exit(&uhcip->uhci_int_mutex);
mutex_destroy(&uhcip->uhci_int_mutex);
cv_destroy(&uhcip->uhci_cv_SOF);
sema_destroy(&uhcip->uhci_ocsem);
/* cleanup kstat structures */
uhci_destroy_stats(uhcip);
usba_free_hcdi_ops(uhcip->uhci_hcdi_ops);
usb_free_log_hdl(uhcip->uhci_log_hdl);
ddi_prop_remove_all(uhcip->uhci_dip);
ddi_soft_state_free(uhci_statep, uhcip->uhci_instance);
return (USB_SUCCESS);
}
/*
* uhci_cpr_suspend
*/
static int
uhci_cpr_suspend(uhci_state_t *uhcip)
{
uint16_t cmd_reg;
int i;
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_cpr_suspend:");
/* Call into the root hub and suspend it */
if (usba_hubdi_detach(uhcip->uhci_dip, DDI_SUSPEND) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
mutex_enter(&uhcip->uhci_int_mutex);
/* Stop the Host Controller */
cmd_reg = Get_OpReg16(USBCMD);
cmd_reg &= ~USBCMD_REG_HC_RUN;
Set_OpReg16(USBCMD, cmd_reg);
/*
* Wait for the duration of an SOF period until the host controller
* reaches the stopped state, indicated by the HCHalted bit in the
* USB status register.
*/
for (i = 0; i <= UHCI_TIMEWAIT / 1000; i++) {
if (Get_OpReg16(USBSTS) & USBSTS_REG_HC_HALTED)
break;
drv_usecwait(1000);
}
USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_cpr_suspend: waited %d milliseconds for hc to halt", i);
/* Disable interrupts */
Set_OpReg16(USBINTR, DISABLE_ALL_INTRS);
/* Clear any scheduled pending interrupts */
Set_OpReg16(USBSTS, USBSTS_REG_HC_HALTED |
USBSTS_REG_HC_PROCESS_ERR | USBSTS_REG_HOST_SYS_ERR |
USBSTS_REG_RESUME_DETECT | USBSTS_REG_USB_ERR_INTR |
USBSTS_REG_USB_INTR);
/* Set Global Suspend bit */
Set_OpReg16(USBCMD, USBCMD_REG_ENTER_GBL_SUSPEND);
/* Set host controller soft state to suspend */
uhcip->uhci_hc_soft_state = UHCI_CTLR_SUSPEND_STATE;
mutex_exit(&uhcip->uhci_int_mutex);
return (USB_SUCCESS);
}
/*
* uhci_cpr_cleanup:
*
* Cleanup uhci specific information across resuming.
*/
static void
uhci_cpr_cleanup(uhci_state_t *uhcip)
{
ASSERT(mutex_owned(&uhcip->uhci_int_mutex));
/* Reset software part of usb frame number */
uhcip->uhci_sw_frnum = 0;
}
/*
* uhci_cpr_resume
*/
static int
uhci_cpr_resume(uhci_state_t *uhcip)
{
USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_cpr_resume: Restart the controller");
mutex_enter(&uhcip->uhci_int_mutex);
/* Cleanup uhci specific information across cpr */
uhci_cpr_cleanup(uhcip);
mutex_exit(&uhcip->uhci_int_mutex);
/* Restart the controller */
if (uhci_init_ctlr(uhcip) != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl,
"uhci_cpr_resume: uhci host controller resume failed ");
return (DDI_FAILURE);
}
mutex_enter(&uhcip->uhci_int_mutex);
/*
* Set HcInterruptEnable to enable all interrupts except Root
* Hub Status change and SOF interrupts.
*/
Set_OpReg16(USBINTR, ENABLE_ALL_INTRS);
mutex_exit(&uhcip->uhci_int_mutex);
/* Now resume the root hub */
if (usba_hubdi_attach(uhcip->uhci_dip, DDI_RESUME) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* uhci_intr:
* uhci interrupt handling routine.
*/
static uint_t
uhci_intr(caddr_t arg1, caddr_t arg2)
{
ushort_t intr_status, cmd_reg, intr_reg;
uhci_state_t *uhcip = (uhci_state_t *)arg1;
USB_DPRINTF_L4(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: Interrupt occurred, arg1 0x%p arg2 0x%p",
(void *)arg1, (void *)arg2);
mutex_enter(&uhcip->uhci_int_mutex);
/* Any interrupt is not handled for the suspended device. */
if (uhcip->uhci_hc_soft_state == UHCI_CTLR_SUSPEND_STATE) {
mutex_exit(&uhcip->uhci_int_mutex);
return (DDI_INTR_UNCLAIMED);
}
/* Get the status of the interrupts */
intr_status = Get_OpReg16(USBSTS);
intr_reg = Get_OpReg16(USBINTR);
USB_DPRINTF_L3(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: intr_status = %x, intr_reg = %x",
intr_status, intr_reg);
/*
* If uhci interrupts are all disabled, the driver should return
* unclaimed.
* HC Process Error and Host System Error interrupts cannot be
* disabled by intr register, and need to be judged separately.
*/
if (((intr_reg & ENABLE_ALL_INTRS) == 0) &&
((intr_status & USBSTS_REG_HC_PROCESS_ERR) == 0) &&
((intr_status & USBSTS_REG_HOST_SYS_ERR) == 0)) {
USB_DPRINTF_L3(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: interrupts disabled, unclaim");
mutex_exit(&uhcip->uhci_int_mutex);
return (DDI_INTR_UNCLAIMED);
}
/*
* If the intr is not from our controller, just return unclaimed.
* HCHalted status bit cannot generate interrupts and should be
* ignored.
*/
if (!(intr_status & UHCI_INTR_MASK)) {
USB_DPRINTF_L3(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: no interrupt status set, unclaim");
mutex_exit(&uhcip->uhci_int_mutex);
return (DDI_INTR_UNCLAIMED);
}
/* Update kstat values */
uhci_do_intrs_stats(uhcip, intr_status);
/* Acknowledge the interrupt */
Set_OpReg16(USBSTS, intr_status);
/*
* If uhci controller has not been initialized, just clear the
* interrupter status and return claimed.
*/
if (uhcip->uhci_hc_soft_state != UHCI_CTLR_OPERATIONAL_STATE) {
USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: uhci controller is not in the operational "
"state");
mutex_exit(&uhcip->uhci_int_mutex);
return (DDI_INTR_CLAIMED);
}
/*
* We configured the hw incorrectly, disable future interrupts.
*/
if ((intr_status & USBSTS_REG_HOST_SYS_ERR)) {
USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: Sys Err Disabling Interrupt");
Set_OpReg16(USBINTR, DISABLE_ALL_INTRS);
uhcip->uhci_hc_soft_state = UHCI_CTLR_ERROR_STATE;
mutex_exit(&uhcip->uhci_int_mutex);
return (DDI_INTR_CLAIMED);
}
/*
* Check whether a frame number overflow occurred.
* if so, update the sw frame number.
*/
uhci_isoc_update_sw_frame_number(uhcip);
/*
* Check whether any commands got completed. If so, process them.
*/
uhci_process_submitted_td_queue(uhcip);
/*
* This should not occur. It occurs only if a HC controller
* experiences internal problem.
*/
if (intr_status & USBSTS_REG_HC_HALTED) {
USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_intr: Controller halted");
cmd_reg = Get_OpReg16(USBCMD);
Set_OpReg16(USBCMD, (cmd_reg | USBCMD_REG_HC_RUN));
}
/*
* Wake up all the threads which are waiting for the Start of Frame
*/
if (uhcip->uhci_cv_signal == B_TRUE) {
cv_broadcast(&uhcip->uhci_cv_SOF);
uhcip->uhci_cv_signal = B_FALSE;
}
mutex_exit(&uhcip->uhci_int_mutex);
return (DDI_INTR_CLAIMED);
}
/*
* uhci_process_submitted_td_queue:
* Traverse thru the submitted queue and process the completed ones.
*/
void
uhci_process_submitted_td_queue(uhci_state_t *uhcip)
{
uhci_td_t *head = uhcip->uhci_outst_tds_head;
uhci_trans_wrapper_t *tw;
while (head != NULL) {
if ((!(GetTD_status(uhcip, head) & UHCI_TD_ACTIVE)) &&
(head->tw->tw_claim == UHCI_NOT_CLAIMED)) {
tw = head->tw;
/*
* Call the corresponding handle_td routine
*/
(*tw->tw_handle_td)(uhcip, head);
/* restart at the beginning again */
head = uhcip->uhci_outst_tds_head;
} else {
head = head->outst_td_next;
}
}
}
/*
* uhci_handle_intr_td:
* handles the completed interrupt transfer TD's.
*/
void
uhci_handle_intr_td(uhci_state_t *uhcip, uhci_td_t *td)
{
usb_req_attrs_t attrs;
uint_t bytes_xfered;
usb_cr_t usb_err;
uhci_trans_wrapper_t *tw = td->tw;
uhci_pipe_private_t *pp = tw->tw_pipe_private;
usb_intr_req_t *intr_reqp =
(usb_intr_req_t *)tw->tw_curr_xfer_reqp;
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_intr_td: intr_reqp = 0x%p", (void *)intr_reqp);
ASSERT(mutex_owned(&uhcip->uhci_int_mutex));
/* set tw->tw_claim flag, so that nobody else works on this td. */
tw->tw_claim = UHCI_INTR_HDLR_CLAIMED;
/* Interrupt OUT */
if (UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_OUT) {
/* process errors first */
usb_err = uhci_parse_td_error(uhcip, pp, td);
/* get the actual xfered data size */
bytes_xfered = GetTD_alen(uhcip, td);
/* check data underrun error */
if ((usb_err == USB_CR_OK) && (bytes_xfered !=
GetTD_mlen(uhcip, td))) {
USB_DPRINTF_L2(PRINT_MASK_LISTS,
uhcip->uhci_log_hdl, "uhci_handle_intr_td:"
" Intr out pipe, data underrun occurred");
usb_err = USB_CR_DATA_UNDERRUN;
}
bytes_xfered = (bytes_xfered == ZERO_LENGTH) ?
0 : bytes_xfered+1;
tw->tw_bytes_xfered += bytes_xfered;
uhci_do_byte_stats(uhcip, tw->tw_bytes_xfered,
ph->p_ep.bmAttributes, ph->p_ep.bEndpointAddress);
/*
* If error occurred or all data xfered, delete the current td,
* free tw, do the callback. Otherwise wait for the next td.
*/
if (usb_err != USB_CR_OK) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_intr_td: Intr out pipe error");
/* update the element pointer */
SetQH32(uhcip, pp->pp_qh->element_ptr, GetTD32(
uhcip, tw->tw_hctd_tail->link_ptr));
} else if (tw->tw_bytes_xfered == tw->tw_length) {
/* all data xfered */
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_intr_td: Intr out pipe,"
" all data xfered");
} else {
/* remove the current td and wait for the next one. */
uhci_delete_td(uhcip, td);
tw->tw_claim = UHCI_NOT_CLAIMED;
return;
}
uhci_delete_td(uhcip, td);
uhci_hcdi_callback(uhcip, pp, ph, tw, usb_err);
uhci_deallocate_tw(uhcip, tw->tw_pipe_private, tw);
return;
}
/* Interrupt IN */
/* Get the actual received data size */
tw->tw_bytes_xfered = GetTD_alen(uhcip, td);
if (tw->tw_bytes_xfered == ZERO_LENGTH) {
tw->tw_bytes_xfered = 0;
} else {
tw->tw_bytes_xfered++;
}
/* process errors first */
if (GetTD_status(uhcip, td) & TD_STATUS_MASK) {
SetQH32(uhcip, pp->pp_qh->element_ptr,
GetTD32(uhcip, td->link_ptr));
uhci_handle_intr_td_errors(uhcip, td, tw, pp);
return;
}
/*
* Check for data underruns.
* For data underrun case, the host controller does not update
* element pointer. So, we update here.
*/
if (GetTD_alen(uhcip, td) != GetTD_mlen(uhcip, td)) {
SetQH32(uhcip, pp->pp_qh->element_ptr,
GetTD32(uhcip, td->link_ptr));
}
/*
* Call uhci_sendup_td_message to send message upstream.
* The function uhci_sendup_td_message returns USB_NO_RESOURCES
* if allocb fails and also sends error message to upstream by
* calling USBA callback function. Under error conditions just
* drop the current message.
*/
/* Get the interrupt xfer attributes */
attrs = intr_reqp->intr_attributes;
/*
* Check usb flag whether USB_FLAGS_ONE_XFER flag is set
* and if so, free duplicate request.
*/
if (attrs & USB_ATTRS_ONE_XFER) {
uhci_handle_one_xfer_completion(uhcip, USB_CR_OK, td);
return;
}
/* save it temporarily */
if (tw->tw_bytes_xfered != 0) {
uhci_sendup_td_message(uhcip, USB_CR_OK, tw);
}
/* Clear the tw->tw_claim flag */
tw->tw_claim = UHCI_NOT_CLAIMED;
uhci_delete_td(uhcip, td);
/* allocate another interrupt periodic resource */
if (pp->pp_state == UHCI_PIPE_STATE_ACTIVE) {
if (uhci_allocate_periodic_in_resource(uhcip, pp, tw, 0) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_insert_intr_req: Interrupt request structure"
"allocation failed");
uhci_hcdi_callback(uhcip, pp, ph,
tw, USB_CR_NO_RESOURCES);
return;
}
/* Insert another interrupt TD */
if (uhci_insert_hc_td(uhcip, 0,
tw->tw_length, pp, tw, PID_IN, attrs) != USB_SUCCESS) {
uhci_deallocate_periodic_in_resource(uhcip, pp, tw);
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_intr_td: TD exhausted");
uhci_hcdi_callback(uhcip, pp, ph,
tw, USB_CR_NO_RESOURCES);
}
}
}
/*
* uhci_sendup_td_message:
*
* Get a message block and send the received message upstream.
*/
void
uhci_sendup_td_message(
uhci_state_t *uhcip,
usb_cr_t usb_err,
uhci_trans_wrapper_t *tw)
{
mblk_t *mp = NULL;
size_t length = 0;
size_t skip_len = 0;
uchar_t *buf;
usb_opaque_t curr_xfer_reqp = tw->tw_curr_xfer_reqp;
uhci_pipe_private_t *pp = tw->tw_pipe_private;
usb_ep_descr_t *ept = &pp->pp_pipe_handle->p_ep;
ASSERT(mutex_owned(&uhcip->uhci_int_mutex));
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_sendup_td_message: bytes transferred=0x%x, "
"bytes pending=0x%x",
tw->tw_bytes_xfered, tw->tw_bytes_pending);
length = tw->tw_bytes_xfered;
switch (UHCI_XFER_TYPE(ept)) {
case USB_EP_ATTR_CONTROL:
skip_len = UHCI_CTRL_EPT_MAX_SIZE; /* length to skip */
mp = ((usb_ctrl_req_t *)curr_xfer_reqp)->ctrl_data;
break;
case USB_EP_ATTR_INTR:
mp = ((usb_intr_req_t *)curr_xfer_reqp)->intr_data;
break;
case USB_EP_ATTR_BULK:
mp = ((usb_bulk_req_t *)curr_xfer_reqp)->bulk_data;
break;
case USB_EP_ATTR_ISOCH:
length = tw->tw_length;
mp = ((usb_isoc_req_t *)curr_xfer_reqp)->isoc_data;
break;
default:
break;
}
/* Copy the data into the mblk_t */
buf = (uchar_t *)tw->tw_buf + skip_len;
ASSERT(mp != NULL);
/*
* Update kstat byte counts
* The control endpoints don't have direction bits so in
* order for control stats to be counted correctly an IN
* bit must be faked on a control read.
*/
uhci_do_byte_stats(uhcip, length, ept->bmAttributes,
(UHCI_XFER_TYPE(ept) == USB_EP_ATTR_CONTROL) ?
USB_EP_DIR_IN : ept->bEndpointAddress);
if (length) {
int rval, i;
uchar_t *p = mp->b_rptr;
if (UHCI_XFER_TYPE(ept) == USB_EP_ATTR_ISOCH) {
/* Deal with isoc data by packets */
for (i = 0; i < tw->tw_ncookies; i++) {
rval = ddi_dma_sync(
tw->tw_isoc_bufs[i].dma_handle, 0,
tw->tw_isoc_bufs[i].length,
DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
ddi_rep_get8(tw->tw_isoc_bufs[i].mem_handle,
p, (uint8_t *)tw->tw_isoc_bufs[i].buf_addr,
tw->tw_isoc_bufs[i].length,
DDI_DEV_AUTOINCR);
p += tw->tw_isoc_bufs[i].length;
}
} else {
/* Sync the streaming buffer */
rval = ddi_dma_sync(tw->tw_dmahandle, 0,
(skip_len + length), DDI_DMA_SYNC_FORCPU);
ASSERT(rval == DDI_SUCCESS);
/* Copy the data into the message */
ddi_rep_get8(tw->tw_accesshandle,
mp->b_rptr, buf, length, DDI_DEV_AUTOINCR);
}
/* Increment the write pointer */
mp->b_wptr += length;
} else {
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_sendup_td_message: Zero length packet");
}
/* Do the callback */
uhci_hcdi_callback(uhcip, pp, pp->pp_pipe_handle, tw, usb_err);
}
/*
* uhci_handle_ctrl_td:
* Handle a control Transfer Descriptor (TD).
*/
void
uhci_handle_ctrl_td(uhci_state_t *uhcip, uhci_td_t *td)
{
ushort_t direction;
ushort_t bytes_for_xfer;
ushort_t bytes_xfered;
ushort_t MaxPacketSize;
usb_cr_t error;
uhci_trans_wrapper_t *tw = td->tw;
uhci_pipe_private_t *pp = tw->tw_pipe_private;
usba_pipe_handle_data_t *usb_pp = pp->pp_pipe_handle;
usb_ep_descr_t *eptd = &usb_pp->p_ep;
usb_ctrl_req_t *reqp = (usb_ctrl_req_t *)tw->tw_curr_xfer_reqp;
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: pp = 0x%p tw = 0x%p td = 0x%p "
"state = 0x%x len = 0x%lx", (void *)pp, (void *)tw,
(void *)td, tw->tw_ctrl_state, tw->tw_length);
ASSERT(mutex_owned(&uhcip->uhci_int_mutex));
error = uhci_parse_td_error(uhcip, pp, td);
/*
* In case of control transfers, the device can send NAK when it
* is busy. If a NAK is received, then send the status TD again.
*/
if (error != USB_CR_OK) {
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: Ctrl cmd failed, error = %x", error);
SetQH32(uhcip, pp->pp_qh->element_ptr,
GetTD32(uhcip, td->link_ptr));
uhci_delete_td(uhcip, td);
/* Return number of bytes xfered */
if (GetTD_alen(uhcip, td) != ZERO_LENGTH) {
tw->tw_bytes_xfered = GetTD_alen(uhcip, td) + 1;
}
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: Bytes transferred = %x",
tw->tw_bytes_xfered);
if ((tw->tw_ctrl_state == DATA) &&
(tw->tw_direction == PID_IN)) {
uhci_sendup_td_message(uhcip, error, tw);
} else {
uhci_hcdi_callback(uhcip, pp, usb_pp, tw, error);
uhci_deallocate_tw(uhcip, pp, tw);
}
return;
}
/*
* A control transfer consists of three phases:
* - Setup
* - Data (optional)
* - Status
*
* There is a TD per phase. A TD for a given phase isn't
* enqueued until the previous phase is finished.
*/
switch (tw->tw_ctrl_state) {
case SETUP:
/*
* Enqueue either the data or the status
* phase depending on the length.
*/
pp->pp_data_toggle = 1;
uhci_delete_td(uhcip, td);
/*
* If the length is 0, move to the status.
* If length is not 0, then we have some data
* to move on the bus to device either IN or OUT.
*/
if ((tw->tw_length - SETUP_SIZE) == 0) {
/*
* There is no data stage, then
* initiate status phase from the host.
*/
if ((uhci_insert_hc_td(uhcip, 0, 0, pp, tw, PID_IN,
reqp->ctrl_attributes)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_LISTS,
uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: No resources");
uhci_hcdi_callback(uhcip, pp, usb_pp, tw,
USB_CR_NO_RESOURCES);
return;
}
tw->tw_ctrl_state = STATUS;
} else {
uint_t xx;
/*
* Each USB device can send/receive 8/16/32/64
* depending on wMaxPacketSize's implementation.
* We need to insert 'N = Number of byte/
* MaxpktSize" TD's in the lattice to send/
* receive the data. Though the USB protocol
* allows to insert more than one TD in the same
* frame, we are inserting only one TD in one
* frame. This is bcos OHCI has seen some problem
* when multiple TD's are inserted at the same time.
*/
tw->tw_length -= UHCI_CTRL_EPT_MAX_SIZE;
MaxPacketSize = eptd->wMaxPacketSize;
/*
* We dont know the maximum packet size that
* the device can handle(MaxPAcketSize=0).
* In that case insert a data phase with
* eight bytes or less.
*/
if (MaxPacketSize == 0) {
xx = (tw->tw_length > 8) ? 8 : tw->tw_length;
} else {
xx = (tw->tw_length > MaxPacketSize) ?
MaxPacketSize : tw->tw_length;
}
tw->tw_tmp = xx;
/*
* Create the TD. If this is an OUT
* transaction, the data is already
* in the buffer of the TW.
* Get first 8 bytes of the command only.
*/
if ((uhci_insert_hc_td(uhcip,
UHCI_CTRL_EPT_MAX_SIZE, xx,
pp, tw, tw->tw_direction,
reqp->ctrl_attributes)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_LISTS,
uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: No resources");
uhci_hcdi_callback(uhcip, pp, usb_pp, tw,
USB_CR_NO_RESOURCES);
return;
}
tw->tw_ctrl_state = DATA;
}
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"Setup complete: pp 0x%p td 0x%p", (void *)pp, (void *)td);
break;
case DATA:
uhci_delete_td(uhcip, td);
MaxPacketSize = eptd->wMaxPacketSize;
/*
* Decrement pending bytes and increment the total
* number bytes transferred by the actual number of bytes
* transferred in this TD. If the number of bytes transferred
* is less than requested, that means an underrun has
* occurred. Set the tw_tmp varible to indicate UNDER run.
*/
bytes_xfered = GetTD_alen(uhcip, td);
if (bytes_xfered == ZERO_LENGTH) {
bytes_xfered = 0;
} else {
bytes_xfered++;
}
tw->tw_bytes_pending -= bytes_xfered;
tw->tw_bytes_xfered += bytes_xfered;
if (bytes_xfered < tw->tw_tmp) {
tw->tw_bytes_pending = 0;
tw->tw_tmp = UHCI_UNDERRUN_OCCURRED;
/*
* Controller does not update the queue head
* element pointer when a data underrun occurs.
*/
SetQH32(uhcip, pp->pp_qh->element_ptr,
GetTD32(uhcip, td->link_ptr));
}
if (bytes_xfered > tw->tw_tmp) {
tw->tw_bytes_pending = 0;
tw->tw_tmp = UHCI_OVERRUN_OCCURRED;
}
/*
* If no more bytes are pending, insert status
* phase. Otherwise insert data phase.
*/
if (tw->tw_bytes_pending) {
bytes_for_xfer = (tw->tw_bytes_pending >
MaxPacketSize) ? MaxPacketSize :
tw->tw_bytes_pending;
tw->tw_tmp = bytes_for_xfer;
if ((uhci_insert_hc_td(uhcip,
UHCI_CTRL_EPT_MAX_SIZE + tw->tw_bytes_xfered,
bytes_for_xfer, pp, tw,
tw->tw_direction,
reqp->ctrl_attributes)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_LISTS,
uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: No TD");
uhci_hcdi_callback(uhcip, pp, usb_pp,
tw, USB_NO_RESOURCES);
return;
}
tw->tw_ctrl_state = DATA;
break;
}
pp->pp_data_toggle = 1;
direction = (tw->tw_direction == PID_IN) ? PID_OUT : PID_IN;
if ((uhci_insert_hc_td(uhcip, 0, 0, pp, tw, direction,
reqp->ctrl_attributes)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: TD exhausted");
uhci_hcdi_callback(uhcip, pp, usb_pp, tw,
USB_NO_RESOURCES);
return;
}
tw->tw_ctrl_state = STATUS;
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"Data complete: pp 0x%p td 0x%p", (void *)pp, (void *)td);
break;
case STATUS:
/*
* Send the data to the client if it is a DATA IN,
* else send just return status for DATA OUT commnads.
* And set the tw_claim flag.
*/
tw->tw_claim = UHCI_INTR_HDLR_CLAIMED;
if ((tw->tw_length != 0) && (tw->tw_direction == PID_IN)) {
usb_req_attrs_t attrs = ((usb_ctrl_req_t *)
tw->tw_curr_xfer_reqp)->ctrl_attributes;
/*
* Call uhci_sendup_td_message to send message
* upstream. The function uhci_sendup_td_message
* returns USB_NO_RESOURCES if allocb fails and
* also sends error message to upstream by calling
* USBA callback function.
*
* Under error conditions just drop the current msg.
*/
if ((tw->tw_tmp == UHCI_UNDERRUN_OCCURRED) &&
(!(attrs & USB_ATTRS_SHORT_XFER_OK))) {
error = USB_CR_DATA_UNDERRUN;
} else if (tw->tw_tmp == UHCI_OVERRUN_OCCURRED) {
error = USB_CR_DATA_OVERRUN;
}
uhci_sendup_td_message(uhcip, error, tw);
} else {
uhci_do_byte_stats(uhcip, tw->tw_length,
eptd->bmAttributes, eptd->bEndpointAddress);
uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_CR_OK);
}
USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"Status complete: pp 0x%p td 0x%p", (void *)pp, (void *)td);
uhci_delete_td(uhcip, td);
uhci_deallocate_tw(uhcip, pp, tw);
break;
default:
USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_handle_ctrl_td: Bad control state");
uhci_hcdi_callback(uhcip, pp, usb_pp, tw,
USB_CR_UNSPECIFIED_ERR);
}
}
/*
* uhci_handle_intr_td_errors:
* Handles the errors encountered for the interrupt transfers.
*/
static void
uhci_handle_intr_td_errors(uhci_state_t *uhcip, uhci_td_t *td,
uhci_trans_wrapper_t *tw, uhci_pipe_private_t *pp)
{
usb_cr_t usb_err;
usb_intr_req_t *intr_reqp =
(usb_intr_req_t *)tw->tw_curr_xfer_reqp;
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_intr_td_errors: td = 0x%p tw = 0x%p",
(void *)td, (void *)tw);
usb_err = uhci_parse_td_error(uhcip, pp, td);
if (intr_reqp->intr_attributes & USB_ATTRS_ONE_XFER) {
uhci_handle_one_xfer_completion(uhcip, usb_err, td);
return;
}
uhci_delete_td(uhcip, td);
uhci_sendup_td_message(uhcip, usb_err, tw);
uhci_deallocate_tw(uhcip, tw->tw_pipe_private, tw);
}
/*
* uhci_handle_one_xfer_completion:
*/
static void
uhci_handle_one_xfer_completion(
uhci_state_t *uhcip,
usb_cr_t usb_err,
uhci_td_t *td)
{
uhci_trans_wrapper_t *tw = td->tw;
uhci_pipe_private_t *pp = tw->tw_pipe_private;
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
usb_intr_req_t *intr_reqp =
(usb_intr_req_t *)tw->tw_curr_xfer_reqp;
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_handle_one_xfer_completion: td = 0x%p", (void *)td);
ASSERT(intr_reqp->intr_attributes & USB_ATTRS_ONE_XFER);
/* set state to idle */
pp->pp_state = UHCI_PIPE_STATE_IDLE;
((usb_intr_req_t *)(pp->pp_client_periodic_in_reqp))->
intr_data = ((usb_intr_req_t *)(tw->tw_curr_xfer_reqp))->intr_data;
((usb_intr_req_t *)tw->tw_curr_xfer_reqp)->intr_data = NULL;
/* now free duplicate current request */
usb_free_intr_req((usb_intr_req_t *)tw->tw_curr_xfer_reqp);
mutex_enter(&ph->p_mutex);
ph->p_req_count--;
mutex_exit(&ph->p_mutex);
/* make client's request the current request */
tw->tw_curr_xfer_reqp = pp->pp_client_periodic_in_reqp;
pp->pp_client_periodic_in_reqp = NULL;
uhci_sendup_td_message(uhcip, usb_err, tw);
/* Clear the tw->tw_claim flag */
tw->tw_claim = UHCI_NOT_CLAIMED;
uhci_delete_td(uhcip, td);
uhci_deallocate_tw(uhcip, pp, tw);
}
/*
* uhci_parse_td_error
* Parses the Transfer Descriptors error
*/
usb_cr_t
uhci_parse_td_error(uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_td_t *td)
{
uint_t status;
status = GetTD_status(uhcip, td) & TD_STATUS_MASK;
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_parse_td_error: status_bits=0x%x", status);
if (UHCI_XFER_TYPE(&pp->pp_pipe_handle->p_ep) == USB_EP_ATTR_ISOCH) {
return (USB_CR_OK);
}
if (!status) {
return (USB_CR_OK);
}
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_parse_td_error: status_bits=0x%x", status);
if (status & UHCI_TD_BITSTUFF_ERR) {
return (USB_CR_BITSTUFFING);
}
if (status & UHCI_TD_CRC_TIMEOUT) {
pp->pp_data_toggle = GetTD_dtogg(uhcip, td);
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_parse_td_error: timeout & data toggle reset; "
"data toggle: %x", pp->pp_data_toggle);
return ((GetTD_PID(uhcip, td) == PID_IN) ? USB_CR_DEV_NOT_RESP :
USB_CR_TIMEOUT);
}
if (status & UHCI_TD_BABBLE_ERR) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"babble error");
return (USB_CR_UNSPECIFIED_ERR);
}
if (status & UHCI_TD_DATA_BUFFER_ERR) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"buffer error");
return ((GetTD_PID(uhcip, td) == PID_IN) ?
USB_CR_BUFFER_OVERRUN : USB_CR_BUFFER_UNDERRUN);
}
if (status & UHCI_TD_STALLED) {
pp->pp_data_toggle = 0;
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_parse_td_error: stall; data toggle reset; "
"data toggle: %x", pp->pp_data_toggle);
return (USB_CR_STALL);
}
if (status) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"unspecified error=0x%x", status);
}
return (USB_CR_OK);
}
static dev_info_t *
uhci_get_dip(dev_t dev)
{
int instance = UHCI_UNIT(dev);
uhci_state_t *uhcip = ddi_get_soft_state(uhci_statep, instance);
return (uhcip ? uhcip->uhci_dip : NULL);
}
/*
* cb_ops entry points
*/
static int
uhci_open(dev_t *devp, int flags, int otyp, cred_t *credp)
{
dev_info_t *dip = uhci_get_dip(*devp);
return (usba_hubdi_open(dip, devp, flags, otyp, credp));
}
static int
uhci_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
dev_info_t *dip = uhci_get_dip(dev);
return (usba_hubdi_close(dip, dev, flag, otyp, credp));
}
static int
uhci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp)
{
dev_info_t *dip = uhci_get_dip(dev);
return (usba_hubdi_ioctl(dip, dev, cmd, arg, mode, credp, rvalp));
}