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
* 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
*/
/*
*/
/*
* 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.
*/
/*
* Prototype Declarations for cb_ops and dev_ops
*/
void **result);
/* extern */
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 */
};
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 */
};
&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 = {
};
/*
* Globals
*/
void *uhci_statep;
/*
* UHCI MSI tunable:
*
* By default MSI is enabled on all supported platforms.
*/
/*
* tunable, delay during attach in seconds
*/
int uhci_attach_wait = 0;
/* function prototypes */
int
_init(void)
{
int error;
/* Initialize the soft state structures */
UHCI_MAX_INSTS)) != 0) {
return (error);
}
/* Install the loadable module */
return (error);
}
/*
* Build the tree bottom shared by all instances
*/
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];
}
num_of_nodes *= 2;
for (k = 0; k < num_of_nodes; k++)
temp[k] = uhci_tree_bottom_nodes[k];
}
return (error);
}
int
{
}
int
_fini(void)
{
int error;
if (error == 0) {
/* Release per module resources */
}
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
{
int ret;
char *propval;
return (0);
return (ret);
}
static void
uhci_poll_intr(void *arg)
{
/* poll every msec */
for (;;) {
}
}
/*
* 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
{
int i, intr_types;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (uhci_cpr_resume(uhcip));
default:
return (DDI_FAILURE);
}
/* Get the instance and create soft state */
/* Allocate the soft state structure for this instance of the driver */
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
&uhci_errmask, &uhci_instance_debug, 0);
/* Set host controller soft state to initialization */
/* Save the dip and instance */
if (polled)
goto skip_intr;
/* Get supported interrupt types */
&intr_types) != DDI_SUCCESS) {
"uhci_attach: ddi_intr_get_supported_types failed");
return (DDI_FAILURE);
}
"uhci_attach: supported interrupt types 0x%x", intr_types);
!= DDI_SUCCESS) {
"uhci_attach: MSI registration failed, "
"trying FIXED interrupt \n");
} else {
"uhci_attach: Using MSI interrupt type\n");
}
}
!= DDI_SUCCESS) {
"uhci_attach: FIXED interrupt registration "
"failed\n");
return (DDI_FAILURE);
}
"uhci_attach: Using FIXED interrupt type\n");
}
/* Semaphore to serialize opens and closes */
/* Create prototype condition variable */
/* Initialize the DMA attributes */
/* Initialize the kstat structures */
/* Create the td and ed pools */
goto fail;
}
/* Map the registers */
goto fail;
}
/* Enable all interrupts */
if (polled) {
extern pri_t maxclsyspri;
"uhci_attach: running in simulated polled mode.");
/* create thread to poll */
/* Call ddi_intr_block_enable() for MSI interrupts */
} else {
/* Call ddi_intr_enable for MSI or FIXED interrupts */
for (i = 0; i < uhcip->uhci_intr_cnt; i++)
}
/* Initialize the controller */
goto fail;
}
/*
* At this point, the hardware will be okay.
* Initialize the usba_hcdi structure
*/
/*
* Make this HCD instance known to USBA
* (dma_attr must be passed for USBA busctl's)
*/
goto fail;
}
#ifndef __sparc
/*
* On NCR system, the driver seen failure of some commands
* while booting. This delay mysteriously solved the problem.
*/
#endif
/*
* Create another timeout handler to check whether any
* This gets called every second.
*/
(void *)uhcip, UHCI_ONE_SECOND);
/*
* Set HcInterruptEnable to enable all interrupts except Root
* Hub Status change and SOF interrupts.
*/
/* Test the SOF interrupt */
"No SOF interrupts have been received, this USB UHCI host"
" controller is unusable");
goto fail;
}
/* This should be the last step which might fail during attaching */
goto fail;
}
/* Display information in the banner */
"uhci_attach successful");
return (DDI_SUCCESS);
fail:
"failed to attach");
(void) uhci_cleanup(uhcip);
return (DDI_FAILURE);
}
/*
* uhci_add_intrs:
*
* Register FIXED or MSI interrupts.
*/
static int
int intr_type)
{
"uhci_add_intrs: interrupt type 0x%x", intr_type);
/* Get number of interrupts */
"uhci_add_intrs: ddi_intr_get_nintrs() failure, "
return (DDI_FAILURE);
}
/* Get number of available interrupts */
"uhci_add_intrs: ddi_intr_get_navail() failure, "
return (DDI_FAILURE);
}
"uhci_add_intrs: uhci_add_intrs: nintrs () "
}
/* Allocate an array of interrupt handles */
/* call ddi_intr_alloc() */
"uhci_add_intrs: ddi_intr_alloc() failed %d", ret);
return (DDI_FAILURE);
}
"uhci_add_intrs: Requested: %d, Received: %d\n",
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
"uhci_add_intrs: ddi_intr_get_pri() failed %d", ret);
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
"uhci_add_intrs: Supported Interrupt priority 0x%x",
/* Test for high level mutex */
"uhci_add_intrs: Hi level interrupt not supported");
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
/* Initialize the mutex */
/* Call ddi_intr_add_handler() */
for (i = 0; i < actual; i++) {
"uhci_add_intrs: ddi_intr_add_handler() "
"failed %d", ret);
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
}
"uhci_add_intrs: ddi_intr_get_cap() failed %d", ret);
for (i = 0; i < actual; i++) {
}
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:");
switch (cmd) {
case DDI_DETACH:
case DDI_SUSPEND:
return (uhci_cpr_suspend(uhcip));
default:
return (DDI_FAILURE);
}
}
/*
* uhci_rem_intrs:
*
* Unregister FIXED or MSI interrupts
*/
static void
{
int i;
/* Disable all interrupts */
} else {
for (i = 0; i < uhcip->uhci_intr_cnt; i++) {
}
}
/* Call ddi_intr_remove_handler() */
for (i = 0; i < uhcip->uhci_intr_cnt; i++) {
}
}
/*
* Function Name: uhci_reset
* Description: Reset entry point - called by the Kernel
* on the way down.
* The Toshiba laptop has been observed to hang
* The resetting uhci on the way down solves the
* problem.
* Output: DDI_SUCCESS / DDI_FAILURE
*/
/* ARGSUSED */
static int
{
/* Disable all HC ED list processing */
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
{
return (DDI_FAILURE);
/* Disable interrupts */
/* Stop the Host Controller */
Set_OpReg16(USBCMD, 0);
/* Clear all status bits */
return (DDI_SUCCESS);
}
/*
* uhci_info:
*/
/* ARGSUSED */
static int
{
int instance;
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* uhci_cleanup:
* Cleanup on attach failure or detach
*/
static int
{
return (USB_FAILURE);
}
if (uhcip->uhci_cmd_timeout_id) {
uhcip->uhci_cmd_timeout_id = 0;
(void) untimeout(timeout_id);
}
/* do interrupt cleanup */
if (uhcip->uhci_htable) {
}
/* cleanup kstat structures */
return (USB_SUCCESS);
}
/*
* uhci_cpr_suspend
*/
static int
{
int i;
"uhci_cpr_suspend:");
/* Call into the root hub and suspend it */
return (DDI_FAILURE);
}
/* Stop the Host Controller */
cmd_reg &= ~USBCMD_REG_HC_RUN;
/*
* 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++) {
break;
drv_usecwait(1000);
}
"uhci_cpr_suspend: waited %d milliseconds for hc to halt", i);
/* Disable interrupts */
/* Clear any scheduled pending interrupts */
/* Set Global Suspend bit */
/* Set host controller soft state to suspend */
return (USB_SUCCESS);
}
/*
* uhci_cpr_cleanup:
*
* Cleanup uhci specific information across resuming.
*/
static void
{
/* Reset software part of usb frame number */
uhcip->uhci_sw_frnum = 0;
}
/*
* uhci_cpr_resume
*/
static int
{
"uhci_cpr_resume: Restart the controller");
/* Cleanup uhci specific information across cpr */
/* Restart the controller */
"uhci_cpr_resume: uhci host controller resume failed ");
return (DDI_FAILURE);
}
/*
* Set HcInterruptEnable to enable all interrupts except Root
* Hub Status change and SOF interrupts.
*/
/* Now resume the root hub */
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* uhci_intr:
* uhci interrupt handling routine.
*/
static uint_t
{
"uhci_intr: Interrupt occurred, arg1 0x%p arg2 0x%p",
/* Any interrupt is not handled for the suspended device. */
return (DDI_INTR_UNCLAIMED);
}
/* Get the status of the interrupts */
"uhci_intr: intr_status = %x, intr_reg = %x",
/*
* 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)) {
"uhci_intr: interrupts disabled, unclaim");
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)) {
"uhci_intr: no interrupt status set, unclaim");
return (DDI_INTR_UNCLAIMED);
}
/* Update kstat values */
/* Acknowledge the interrupt */
/*
* If uhci controller has not been initialized, just clear the
* interrupter status and return claimed.
*/
"uhci_intr: uhci controller is not in the operational "
"state");
return (DDI_INTR_CLAIMED);
}
/*
* We configured the hw incorrectly, disable future interrupts.
*/
if ((intr_status & USBSTS_REG_HOST_SYS_ERR)) {
"uhci_intr: Sys Err Disabling Interrupt");
return (DDI_INTR_CLAIMED);
}
/*
* Check whether a frame number overflow occurred.
* if so, update the sw frame number.
*/
/*
* Check whether any commands got completed. If so, process them.
*/
/*
* This should not occur. It occurs only if a HC controller
* experiences internal problem.
*/
if (intr_status & USBSTS_REG_HC_HALTED) {
"uhci_intr: Controller halted");
}
/*
* Wake up all the threads which are waiting for the Start of Frame
*/
}
return (DDI_INTR_CLAIMED);
}
/*
* uhci_process_submitted_td_queue:
* Traverse thru the submitted queue and process the completed ones.
*/
void
{
/*
* Call the corresponding handle_td routine
*/
/* restart at the beginning again */
} else {
}
}
}
/*
* uhci_handle_intr_td:
* handles the completed interrupt transfer TD's.
*/
void
{
"uhci_handle_intr_td: intr_reqp = 0x%p", (void *)intr_reqp);
/* set tw->tw_claim flag, so that nobody else works on this td. */
/* Interrupt OUT */
/* process errors first */
/* get the actual xfered data size */
/* check data underrun error */
" Intr out pipe, data underrun occurred");
}
0 : bytes_xfered+1;
/*
* If error occurred or all data xfered, delete the current td,
* free tw, do the callback. Otherwise wait for the next td.
*/
"uhci_handle_intr_td: Intr out pipe error");
/* update the element pointer */
/* all data xfered */
"uhci_handle_intr_td: Intr out pipe,"
" all data xfered");
} else {
/* remove the current td and wait for the next one. */
return;
}
return;
}
/* Interrupt IN */
/* Get the actual received data size */
tw->tw_bytes_xfered = 0;
} else {
tw->tw_bytes_xfered++;
}
/* process errors first */
return;
}
/*
* Check for data underruns.
* For data underrun case, the host controller does not update
* element pointer. So, we update here.
*/
}
/*
* 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 */
/*
* Check usb flag whether USB_FLAGS_ONE_XFER flag is set
* and if so, free duplicate request.
*/
if (attrs & USB_ATTRS_ONE_XFER) {
return;
}
/* save it temporarily */
if (tw->tw_bytes_xfered != 0) {
}
/* Clear the tw->tw_claim flag */
/* allocate another interrupt periodic resource */
USB_SUCCESS) {
"uhci_insert_intr_req: Interrupt request structure"
"allocation failed");
return;
}
/* Insert another interrupt TD */
if (uhci_insert_hc_td(uhcip, 0,
"uhci_handle_intr_td: TD exhausted");
}
}
}
/*
* uhci_sendup_td_message:
*
* Get a message block and send the received message upstream.
*/
void
{
"uhci_sendup_td_message: bytes transferred=0x%x, "
"bytes pending=0x%x",
switch (UHCI_XFER_TYPE(ept)) {
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_ISOCH:
break;
default:
break;
}
/* Copy the data into the mblk_t */
/*
* 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.
*/
if (length) {
int rval, i;
/* Deal with isoc data by packets */
for (i = 0; i < tw->tw_ncookies; i++) {
rval = ddi_dma_sync(
}
} else {
/* Sync the streaming buffer */
/* Copy the data into the message */
}
/* Increment the write pointer */
} else {
"uhci_sendup_td_message: Zero length packet");
}
/* Do the callback */
}
/*
* uhci_handle_ctrl_td:
* Handle a control Transfer Descriptor (TD).
*/
void
{
"uhci_handle_ctrl_td: pp = 0x%p tw = 0x%p td = 0x%p "
/*
* 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.
*/
"uhci_handle_ctrl_td: Ctrl cmd failed, error = %x", error);
/* Return number of bytes xfered */
}
"uhci_handle_ctrl_td: Bytes transferred = %x",
} else {
}
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.
*/
/*
* 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.
*/
/*
* There is no data stage, then
* initiate status phase from the host.
*/
"uhci_handle_ctrl_td: No resources");
return;
}
} else {
/*
* 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.
*/
/*
* 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) {
} else {
}
/*
* 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_handle_ctrl_td: No resources");
return;
}
}
break;
case DATA:
/*
* 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.
*/
if (bytes_xfered == ZERO_LENGTH) {
bytes_xfered = 0;
} else {
bytes_xfered++;
}
tw->tw_bytes_pending = 0;
/*
* Controller does not update the queue head
* element pointer when a data underrun occurs.
*/
}
tw->tw_bytes_pending = 0;
}
/*
* If no more bytes are pending, insert status
* phase. Otherwise insert data phase.
*/
if (tw->tw_bytes_pending) {
if ((uhci_insert_hc_td(uhcip,
"uhci_handle_ctrl_td: No TD");
return;
}
break;
}
"uhci_handle_ctrl_td: TD exhausted");
return;
}
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.
*/
/*
* 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.
*/
(!(attrs & USB_ATTRS_SHORT_XFER_OK))) {
}
} else {
}
break;
default:
"uhci_handle_ctrl_td: Bad control state");
}
}
/*
* uhci_handle_intr_td_errors:
* Handles the errors encountered for the interrupt transfers.
*/
static void
{
"uhci_handle_intr_td_errors: td = 0x%p tw = 0x%p",
return;
}
}
/*
* uhci_handle_one_xfer_completion:
*/
static void
{
"uhci_handle_one_xfer_completion: td = 0x%p", (void *)td);
/* set state to idle */
/* now free duplicate current request */
ph->p_req_count--;
/* make client's request the current request */
/* Clear the tw->tw_claim flag */
}
/*
* uhci_parse_td_error
* Parses the Transfer Descriptors error
*/
{
"uhci_parse_td_error: status_bits=0x%x", status);
return (USB_CR_OK);
}
if (!status) {
return (USB_CR_OK);
}
"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) {
"uhci_parse_td_error: timeout & data toggle reset; "
}
if (status & UHCI_TD_BABBLE_ERR) {
"babble error");
return (USB_CR_UNSPECIFIED_ERR);
}
if (status & UHCI_TD_DATA_BUFFER_ERR) {
"buffer error");
}
if (status & UHCI_TD_STALLED) {
pp->pp_data_toggle = 0;
"uhci_parse_td_error: stall; data toggle reset; "
return (USB_CR_STALL);
}
if (status) {
"unspecified error=0x%x", status);
}
return (USB_CR_OK);
}
static dev_info_t *
{
}
/*
* cb_ops entry points
*/
static int
{
}
static int
{
}
static int
{
}