/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* EHCI Host Controller Driver (EHCI)
*
* The EHCI driver is a software driver which interfaces to the Universal
* Serial Bus layer (USBA) and the Host Controller (HC). The interface to
* the Host Controller is defined by the EHCI Host Controller Interface.
*
* This module contains the EHCI driver interrupt code, which handles all
* Checking of status of USB transfers, error recovery and callbacks.
*/
/*
* EHCI Interrupt Handling functions.
*/
static ehci_qtd_t *ehci_create_done_qtd_list(
static usb_cr_t ehci_parse_error(
ehci_qtd_t *qtd);
static usb_cr_t ehci_check_for_short_xfer(
ehci_qtd_t *qtd);
void ehci_handle_error(
static void ehci_cleanup_data_underrun(
ehci_qtd_t *qtd);
static void ehci_handle_normal_qtd(
void ehci_handle_ctrl_qtd(
void *);
void ehci_handle_bulk_qtd(
void *);
void ehci_handle_intr_qtd(
void *);
static void ehci_handle_one_xfer_completion(
static void ehci_sendup_qtd_message(
/*
* Interrupt Handling functions
*/
/*
* ehci_handle_ue:
*
* Handling of Unrecoverable Error interrupt (UE).
*/
void
{
"ehci_handle_ue: Handling of UE interrupt");
/*
* First check whether current UE error occurred due to USB or
* due to some other subsystem. This can be verified by reading
* usb frame numbers before & after a delay of few milliseconds.
* If usb frame number read after delay is greater than the one
* read before delay, then, USB subsystem is fine. In this case,
* disable UE error interrupt and return without shutdowning the
* USB subsystem.
*
* Otherwise, if usb frame number read after delay is less than
* or equal to one read before the delay, then, current UE error
* occurred from USB subsystem. In this case,go ahead with actual
* UE error recovery procedure.
*
* Get the current usb frame number before waiting for few
* milliseconds.
*/
/* Wait for few milliseconds */
/*
* Get the current usb frame number after waiting for
* milliseconds.
*/
"ehci_handle_ue: Before Frame Number 0x%llx "
"After Frame Number 0x%llx",
(unsigned long long)before_frame_number,
(unsigned long long)after_frame_number);
if (after_frame_number > before_frame_number) {
/* Disable UE interrupt */
return;
}
/*
* This UE is due to USB hardware error. Reset ehci controller
* and reprogram to bring it back to functional state.
*/
"Unrecoverable USB Hardware Error");
/* Disable UE interrupt */
/* Route all Root hub ports to Classic host controller */
/* Set host controller soft state to error */
}
}
/*
* ehci_handle_frame_list_rollover:
*
* Update software based usb frame number part on every frame number
* overflow interrupt.
*
* Refer ehci spec 1.0, section 2.3.2, page 21 for more details.
*
* NOTE: This function is also called from POLLED MODE.
*/
void
{
"ehci_handle_frame_list_rollover:"
"Frame Number Higher Part 0x%llx\n",
}
/*
* ehci_handle_endpoint_reclamation:
*
* Reclamation of Host Controller (HC) Endpoint Descriptors (QH).
*/
void
{
"ehci_handle_endpoint_reclamation:");
/*
* Deallocate all Endpoint Descriptors (QH) which are on the
* reclamation list. These QH's are already removed from the
* interrupt lattice tree.
*/
while (ehcip->ehci_reclaim_list) {
"ehci_handle_endpoint_reclamation:"
"current frame number 0x%llx endpoint frame number 0x%llx",
(unsigned long long)current_frame_number,
(unsigned long long)endpoint_frame_number);
/*
* Deallocate current endpoint only if endpoint's usb frame
* number is less than or equal to current usb frame number.
*
* If endpoint's usb frame number is greater than the current
* usb frame number, ignore rest of the endpoints in the list
* since rest of the endpoints are inserted into the reclaim
* list later than the current reclaim endpoint.
*/
break;
}
/* Get the next endpoint from the rec. list */
/* Free 32bit ID */
/* Deallocate the endpoint */
}
}
/*
* ehci_traverse_active_qtd_list:
*/
void
{
"ehci_traverse_active_qtd_list:");
/* Sync QH and QTD pool */
/* Create done qtd list */
/* Traverse the list of transfer descriptors */
while (curr_qtd) {
/* Get next qtd from the active qtd list */
/* Check for QTD state */
"ehci_traverse_active_qtd_list:\n\t"
/* Obtain the transfer wrapper for this QTD */
"ehci_traverse_active_qtd_list: "
/*
* A QTD that is marked as RECLAIM has already been
* processed by QTD timeout handler & client driver
* has been informed through exception callback.
*/
if (state != EHCI_QTD_RECLAIM) {
/* Look at the error status */
} else {
/* handle the error condition */
}
} else {
"ehci_traverse_active_qtd_list: "
"QTD State = %d", state);
}
/* Deallocate this transfer descriptor */
/*
* Deallocate the transfer wrapper if there are no more
* QTD's for the transfer wrapper. ehci_deallocate_tw()
* will not deallocate the tw for a periodic endpoint
* since it will always have a QTD attached to it.
*/
}
}
/*
* ehci_create_done_qtd_list:
*
* Create done qtd list from active qtd list.
*/
{
"ehci_create_done_qtd_list:");
while (curr_qtd) {
/* Get next qtd from the active qtd list */
/* Check this QTD has been processed by Host Controller */
/* Remove this QTD from active QTD list */
if (done_qtd_list) {
} else {
}
}
}
return (done_qtd_list);
}
/*
* ehci_parse_error:
*
* Parse the result for any errors.
*/
static usb_cr_t
{
"ehci_parse_error:");
/* Obtain the transfer wrapper from the QTD */
tw = (ehci_trans_wrapper_t *)
/* Obtain the pipe private structure */
/*
* Check the condition code of completed QTD and report errors
* if any. This checking will be done both for the general and
* the isochronous QTDs.
*/
USB_CR_OK) {
} else {
}
/* Stop the transfer timer */
return (error);
}
/*
* ehci_check_for_error:
*
* Check for any errors.
*
* NOTE: This function is also called from POLLED MODE.
*/
{
"ehci_check_for_error: qtd = 0x%p ctrl = 0x%x",
/*
* Find the usb device speed and get the corresponding
* error status mask.
*/
/* Exclude halted transaction error condition */
case EHCI_QTD_CTRL_NO_ERROR:
"ehci_check_for_error: No Error");
break;
"ehci_check_for_error: Not accessed");
break;
"ehci_check_for_error: Halted");
break;
"ehci_check_for_error: Babble Detected");
break;
case EHCI_QTD_CTRL_XACT_ERROR:
/*
* An xacterr bit of one is not necessarily an error,
* the transaction might have completed successfully
* after some retries.
*
* Try to detect the case when the queue is halted,
* because the error counter was decremented from one
* down to zero after a transaction error.
*/
EHCI_QTD_CTRL_ERR_COUNT_MASK) == 0) {
"ehci_check_for_error: Transaction Error");
} else {
"ehci_check_for_error: No Error");
}
break;
/*
* Data buffer error is not necessarily an error,
* the transaction might have completed successfully
* after some retries. It can be ignored if the
* queue is not halted.
*/
if (!(ctrl & EHCI_QTD_CTRL_HALTED_XACT)) {
"ehci_check_for_error: Data buffer overrun or "
"underrun ignored");
break;
}
"ehci_check_for_error: Buffer Overrun");
} else {
"ehci_check_for_error: Buffer Underrun");
}
break;
"ehci_check_for_error: Missed uFrame");
break;
"ehci_check_for_error: Periodic split-transaction "
"receives an error handshake");
break;
default:
"ehci_check_for_error: Unspecified Error");
break;
}
/*
* Check for halted transaction error condition.
* Under short xfer conditions, EHCI HC will not return an error
* or halt the QH. This is done manually later in
* ehci_check_for_short_xfer.
*/
"ehci_check_for_error: Halted");
}
}
if (error) {
"ehci_check_for_error: Error %d Device address %d "
"Endpoint number %d", error,
((qh_ctrl & EHCI_QH_CTRL_ED_NUMBER) >>
}
return (error);
}
/*
* ehci_check_for_short_xfer:
*
* Check to see if there was a short xfer condition.
*
* NOTE: This function is also called from POLLED MODE.
* But it doesn't do anything.
*/
static usb_cr_t
{
"ehci_check_for_short_xfer:");
return (error);
}
/*
* Check for short xfer error. If this is a control pipe, only check
* if it is in the data phase.
*/
switch (attributes) {
case USB_EP_ATTR_CONTROL:
break;
}
/* FALLTHROUGH */
case USB_EP_ATTR_BULK:
case USB_EP_ATTR_INTR:
/*
* If "Total bytes of xfer" in control field of
* Transfer Descriptor (QTD) is not equal to zero,
* device than requested. In that case, get the
* actual received data size.
*/
break;
case USB_EP_ATTR_ISOCH:
break;
}
if (residue) {
"ehci_check_for_short_xfer: residue=%d direction=0x%x",
if (xfer_attrs & USB_ATTRS_SHORT_XFER_OK) {
} else {
/* Halt the pipe to mirror OHCI behavior */
}
"ehci_check_for_short_xfer: requested data=%lu "
switch (attributes) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
case USB_EP_ATTR_INTR:
/* Save the actual received length */
break;
case USB_EP_ATTR_ISOCH:
default:
break;
}
} else {
"ehci_check_for_short_xfer: requested data=%lu "
switch (attributes) {
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
/* Increment the read pointer */
break;
case USB_EP_ATTR_INTR:
/* Increment the read pointer */
break;
case USB_EP_ATTR_ISOCH:
default:
break;
}
}
}
return (error);
}
/*
* ehci_handle_error:
*
* Inform USBA about occurred transaction errors by calling the USBA callback
* routine.
*/
void
{
"ehci_handle_error: error = 0x%x", error);
/* Print the values in the qtd */
/* Obtain the transfer wrapper from the QTD */
tw = (ehci_trans_wrapper_t *)
/* Obtain the pipe private structure */
/*
* Mark all QTDs belongs to this TW as RECLAIM
* so that we don't process them by mistake.
*/
while (tw_qtd) {
/* Set QTD state to RECLAIM */
/* Get the next QTD from the wrapper */
}
/*
* Special error handling
*/
switch (attributes) {
case USB_EP_ATTR_CONTROL:
USB_EP_ATTR_MASK) ==
break;
}
/* FALLTHROUGH */
case USB_EP_ATTR_BULK:
/*
* Call ehci_sendup_qtd_message
* to send message to upstream.
*/
return;
case USB_EP_ATTR_INTR:
if (curr_intr_reqp->intr_attributes &
}
/* Decrement periodic in request count */
break;
case USB_EP_ATTR_ISOCH:
break;
}
}
/* Check anybody is waiting for transfers completion event */
}
/*
* ehci_cleanup_data_underrun:
*
* Cleans up resources when a short xfer occurs. Will only do cleanup if
* this pipe supports alternate_qtds.
*
* NOTE: This function is also called from POLLED MODE.
*/
static void
{
"ehci_cleanup_data_underrun: qtd=0x%p, tw=0x%p",
/*
* Check if this transfer doesn't supports short_xfer or
* if this QTD is the last qtd in the tw. If so there is
* no need for cleanup.
*/
/* There is no need for cleanup */
return;
}
/* Start removing all the unused QTDs from the TW */
while (next_qtd) {
tw->tw_num_qtds--;
}
}
/*
* ehci_handle_normal_qtd:
*/
static void
{
"ehci_handle_normal_qtd:");
/* Obtain the pipe private structure */
/* Check anybody is waiting for transfers completion event */
}
/*
* ehci_handle_ctrl_qtd:
*
* Handle a control Transfer Descriptor (QTD).
*/
/* ARGSUSED */
void
void *tw_handle_callback_value)
{
"ehci_handle_ctrl_qtd: pp = 0x%p tw = 0x%p qtd = 0x%p state = 0x%x",
/*
* A control transfer consists of three phases:
*
* Setup
* Data (optional)
* Status
*
* There is a QTD per phase. A QTD for a given phase isn't
* enqueued until the previous phase is finished. EHCI
* spec allows more than one control transfer on a pipe
* within a frame. However, we've found that some devices
* can't handle this.
*/
tw->tw_num_qtds--;
case EHCI_CTRL_SETUP_PHASE:
"Setup complete: pp 0x%p qtd 0x%p",
break;
case EHCI_CTRL_DATA_PHASE:
"Data complete: pp 0x%p qtd 0x%p",
break;
case EHCI_CTRL_STATUS_PHASE:
/*
* On some particular hardware, status phase is seen to
* finish before data phase gets timeouted. Don't handle
* the transfer result here if not all qtds are finished.
* Let the timeout handler handle it.
*/
if (tw->tw_num_qtds != 0) {
"Status complete, but the transfer is not done: "
"tw 0x%p, qtd 0x%p, tw_num_qtd 0x%d",
break;
}
/*
* Call ehci_sendup_qtd_message
* to send message to upstream.
*/
} else {
}
"Status complete: pp 0x%p qtd 0x%p",
break;
}
}
/*
* ehci_handle_bulk_qtd:
*
* Handle a bulk Transfer Descriptor (QTD).
*/
/* ARGSUSED */
void
void *tw_handle_callback_value)
{
"ehci_handle_bulk_qtd:");
/*
* Decrement the QTDs counter and check whether all the bulk
* data has been send or received. If QTDs counter reaches
* zero then inform client driver about completion current
* bulk request. Other wise wait for completion of other bulk
* QTDs or transactions on this pipe.
*/
if (--tw->tw_num_qtds != 0) {
return;
}
/*
* If this is a bulk in pipe, return the data to the client.
* For a bulk out pipe, there is no need to do anything.
*/
if ((eptd->bEndpointAddress &
USB_EP_DIR_MASK) == USB_EP_DIR_OUT) {
"ehci_handle_bulk_qtd: Bulk out pipe");
/* Do the callback */
return;
}
/* Call ehci_sendup_qtd_message to send message to upstream */
}
/*
* ehci_handle_intr_qtd:
*
* Handle a interrupt Transfer Descriptor (QTD).
*/
/* ARGSUSED */
void
void *tw_handle_callback_value)
{
"ehci_handle_intr_qtd:");
/* Get the interrupt xfer attributes */
/*
* For a Interrupt OUT pipe, we just callback and we are done
*/
"ehci_handle_intr_qtd: Intr out pipe, intr_reqp=0x%p,"
"data=0x%p", (void *)curr_intr_reqp,
(void *)curr_intr_reqp->intr_data);
/* Do the callback */
return;
}
/* Decrement number of interrupt request count */
/*
* Check usb flag whether USB_FLAGS_ONE_XFER flag is set
* and if so, free duplicate request.
*/
if (attrs & USB_ATTRS_ONE_XFER) {
}
/* Call ehci_sendup_qtd_message to callback into client */
/*
* If interrupt pipe state is still active, insert next Interrupt
* request into the Host Controller's Interrupt list. Otherwise
* you are done.
*/
return;
}
USB_SUCCESS) {
USB_SUCCESS) {
error = USB_FAILURE;
}
}
if (error != USB_SUCCESS) {
/*
* Set pipe state to stop polling and error to no
* resource. Don't insert any more interrupt polling
* requests.
*/
} else {
/* Increment number of interrupt request count */
}
}
/*
* ehci_handle_one_xfer_completion:
*/
static void
{
"ehci_handle_one_xfer_completion: tw = 0x%p", (void *)tw);
/*
* For one xfer, we need to copy back data ptr
* and free current request
*/
intr_data = ((usb_intr_req_t *)
/* Now free duplicate current request */
ph->p_req_count--;
/* Make client's request the current request */
}
/*
* ehci_sendup_qtd_message:
* copy data, if necessary and do callback
*/
/* ARGSUSED */
static void
{
"ehci_sendup_qtd_message:");
/* Get the correct length */
else
/* Set the length of the buffer to skip */
}
/* Copy the data into the mblk_t */
/* Get the message block */
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
/* Isoc messages must not go through this path */
break;
}
if (length) {
/*
* 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.
*/
} else {
}
/* Sync IO buffer */
/* since we specified NEVERSWAP, we can just use bcopy */
/* Increment the write pointer */
} else {
"ehci_sendup_qtd_message: Zero length packet");
}
}