ehci_xfer.c revision 112116d842e816e29d26a8fe28ed25d201063169
/*
* 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.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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 main EHCI driver code which handles all USB
* transfers, bandwidth allocations and other general functionalities.
*/
/* Adjustable variables for the size of the pools */
extern int ehci_qh_pool_size;
extern int ehci_qtd_pool_size;
/* Endpoint Descriptor (QH) related functions */
static void ehci_unpack_endpoint(
void ehci_insert_qh(
static void ehci_insert_async_qh(
static void ehci_insert_intr_qh(
static void ehci_modify_qh_status_bit(
static void ehci_halt_hs_qh(
static void ehci_halt_fls_ctrl_and_bulk_qh(
static void ehci_clear_tt_buffer(
static void ehci_halt_fls_intr_qh(
void ehci_remove_qh(
static void ehci_remove_async_qh(
static void ehci_remove_intr_qh(
static void ehci_insert_qh_on_reclaim_list(
void ehci_deallocate_qh(
/* Transfer Descriptor (QTD) related functions */
static int ehci_initialize_dummy(
void ehci_insert_ctrl_req(
void ehci_insert_bulk_req(
static int ehci_start_pipe_polling(
static int ehci_start_intr_polling(
static void ehci_set_periodic_pipe_polling(
void ehci_insert_intr_req(
int ehci_insert_qtd(
static ehci_qtd_t *ehci_allocate_qtd_from_pool(
static void ehci_fill_in_qtd(
static void ehci_insert_qtd_on_tw(
ehci_qtd_t *qtd);
static void ehci_insert_qtd_into_active_qtd_list(
static void ehci_traverse_qtds(
void ehci_deallocate_qtd(
ehci_qtd_t *addr);
/* Transfer Wrapper (TW) functions */
static void ehci_free_tw_td_resources(
static void ehci_start_xfer_timer(
void ehci_stop_xfer_timer(
static void ehci_xfer_timeout_handler(void *arg);
static void ehci_remove_tw_from_timeout_list(
void ehci_deallocate_tw(
void ehci_free_dma_resources(
static void ehci_free_tw(
/* Miscellaneous functions */
void ehci_pipe_cleanup(
static void ehci_wait_for_transfers_completion(
static void ehci_save_data_toggle(
void ehci_restore_data_toggle(
void ehci_hcdi_callback(
/*
* Endpoint Descriptor (QH) manipulations functions
*/
/*
* ehci_alloc_qh:
*
* Allocate an endpoint descriptor (QH)
*
* NOTE: This function is also called from POLLED MODE.
*/
{
int i, state;
/*
* If this is for a ISOC endpoint return null.
* Isochronous uses ITD put directly onto the PFL.
*/
if (ph) {
return (NULL);
}
}
/*
* The first 63 endpoints in the Endpoint Descriptor (QH)
* buffer pool are reserved for building interrupt lattice
* tree. Search for a blank endpoint descriptor in the QH
* buffer pool.
*/
for (i = EHCI_NUM_STATIC_NODES; i < ehci_qh_pool_size; i ++) {
if (state == EHCI_QH_FREE) {
break;
}
}
"ehci_alloc_qh: Allocated %d", i);
if (i == ehci_qh_pool_size) {
"ehci_alloc_qh: QH exhausted");
return (NULL);
} else {
"ehci_alloc_qh: Allocated address 0x%p", (void *)qh);
/* Check polled mode flag */
if (flag == EHCI_POLLED_MODE_FLAG) {
}
/* Unpack the endpoint descriptor into a control field */
if (ph) {
if ((ehci_initialize_dummy(ehcip,
qh)) == USB_NO_RESOURCES) {
return (NULL);
}
/* Change QH's state Active */
} else {
/* Change QH's state Static */
}
return (qh);
}
}
/*
* ehci_unpack_endpoint:
*
* Unpack the information in the pipe handle and create the first byte
* of the Host Controller's (HC) Endpoint Descriptor (QH).
*/
static void
{
"ehci_unpack_endpoint:");
/* Assign the endpoint's address */
/* Assign the speed */
switch (usb_port_status) {
case USBA_LOW_SPEED_DEV:
break;
case USBA_FULL_SPEED_DEV:
break;
case USBA_HIGH_SPEED_DEV:
break;
}
case USB_EP_ATTR_CONTROL:
/* Assign data toggle information */
if (usb_port_status != USBA_HIGH_SPEED_DEV) {
}
/* FALLTHRU */
case USB_EP_ATTR_BULK:
/* Maximum nak counter */
if (usb_port_status == USBA_HIGH_SPEED_DEV) {
/*
* Perform ping before executing control
* and bulk transactions.
*/
}
break;
case USB_EP_ATTR_INTR:
/* Set start split mask */
/*
* usb devices.
*/
if (usb_port_status != USBA_HIGH_SPEED_DEV) {
}
break;
}
/* Get the max transactions per microframe */
switch (xactions) {
case 0:
break;
case 1:
break;
case 2:
break;
default:
break;
}
/*
* address and port number.
*/
if (usb_port_status != USBA_HIGH_SPEED_DEV) {
/* Set start split transaction state */
}
/* Assign endpoint's maxpacketsize */
}
/*
* ehci_insert_qh:
*
* Add the Endpoint Descriptor (QH) into the Host Controller's
* (HC) appropriate endpoint list.
*/
void
{
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
/* ISOCH does not use QH, don't do anything but update count */
break;
}
}
/*
* ehci_insert_async_qh:
*
* Asynchronous schedule endpoint list.
*/
static void
{
"ehci_insert_async_qh:");
/* Make sure this QH is not already in the list */
/* Obtain a ptr to the head of the Async schedule list */
if (async_head_qh == NULL) {
/* Set this QH to be the "head" of the circular list */
/* Set new QH's link and previous pointer to itself */
/* Set the head ptr to the new endpoint */
/*
* For some reason this register might get nulled out by
* the Uli M1575 South Bridge. To workaround the hardware
* problem, check the value after write and retry if the
* last write fails.
*
* If the ASYNCLISTADDR remains "stuck" after
* EHCI_MAX_RETRY retries, then the M1575 is broken
* and is stuck in an inconsistent state and is about
* to crash the machine with a trn_oor panic when it
* does a DMA read from 0x0. It is better to panic
* now rather than wait for the trn_oor crash; this
* way Customer Service will have a clean signature
* that indicts the M1575 chip rather than a
* mysterious and hard-to-diagnose trn_oor panic.
*/
int retry = 0;
if (retry >= EHCI_MAX_RETRY)
" ASYNCLISTADDR write failed.");
"ehci_insert_async_qh: ASYNCLISTADDR "
"write failed, retry=%d", retry);
}
} else {
/* Ensure this QH's "H" bit is not set */
/* Set new QH's link and previous pointers */
/* Set next QH's prev pointer */
/* Set QH Head's link pointer points to new QH */
}
}
/*
* ehci_insert_intr_qh:
*
* Insert a interrupt endpoint into the Host Controller's (HC) interrupt
* lattice tree.
*/
static void
{
"ehci_insert_intr_qh:");
/* Make sure this QH is not already in the list */
/*
* The appropriate high speed node was found
* during the opening of the pipe.
*/
/* Find the lattice endpoint */
/* Find the next lattice endpoint */
/* Update the previous pointer */
/* Check next_lattice_qh value */
if (next_lattice_qh) {
/* Update this qh to point to the next one in the lattice */
/* Update the previous pointer of qh->qh_link_ptr */
}
} else {
/* Update qh's link pointer to terminate periodic list */
}
/* Insert this endpoint into the lattice */
}
/*
* ehci_modify_qh_status_bit:
*
* Modify the halt bit on the Host Controller (HC) Endpoint Descriptor (QH).
*
* If several threads try to halt the same pipe, they will need to wait on
* a condition variable. Only one thread is allowed to halt or unhalt the
* pipe at a time.
*
* Usually after a halt pipe, an unhalt pipe will follow soon after. There
* is an assumption that an Unhalt pipe will never occur without a halt pipe.
*/
static void
{
"ehci_modify_qh_status_bit: action=0x%x qh=0x%p",
/*
* If this pipe is in the middle of halting don't allow another
* thread to come in and modify the same pipe.
*/
&ehcip->ehci_int_mutex);
}
/* Sync the QH QTD pool to get up to date information */
if (action == CLEAR_HALT) {
/*
* If the halt bit is to be cleared, just clear it.
* there shouldn't be any race condition problems.
* If the host controller reads the bit before the
* driver has a chance to set the bit, the bit will
* be reread on the next frame.
*/
goto success;
}
/* Halt the the QH, but first check to see if it is already halted */
if (!(status & EHCI_QH_STS_HALTED)) {
/* Indicate that this pipe is in the middle of halting. */
/*
* A non-zero Cmask indicates that this QH is an interrupt
* endpoint. Check the endpoint speed to see if it is either
* FULL or LOW .
*/
split_intr_qh = ((smask != 0) &&
(eps != EHCI_QH_CTRL_ED_HIGH_SPEED));
if (eps == EHCI_QH_CTRL_ED_HIGH_SPEED) {
} else {
if (split_intr_qh) {
} else {
}
}
/* Indicate that this pipe is not in the middle of halting. */
}
/* Sync the QH QTD pool again to get the most up to date information */
if (!(status & EHCI_QH_STS_HALTED)) {
"ehci_modify_qh_status_bit: Failed to halt qh=0x%p",
(void *)qh);
/* Set host controller soft state to error */
}
/* Wake up threads waiting for this pipe to be halted. */
}
/*
* ehci_halt_hs_qh:
*
* Halts all types of HIGH SPEED QHs.
*/
static void
{
"ehci_halt_hs_qh:");
/* Remove this qh from the HCD's view, but do not reclaim it */
/*
* Wait for atleast one SOF, just in case the HCD is in the
* middle accessing this QH.
*/
(void) ehci_wait_for_sof(ehcip);
/* Sync the QH QTD pool to get up to date information */
/* Modify the status bit and halt this QH. */
~(EHCI_QH_STS_ACTIVE)) | EHCI_QH_STS_HALTED));
/* Insert this QH back into the HCD's view */
}
/*
* ehci_halt_fls_ctrl_and_bulk_qh:
*
*/
static void
{
"ehci_halt_fls_ctrl_and_bulk_qh:");
/* Remove this qh from the HCD's view, but do not reclaim it */
/*
* Wait for atleast one SOF, just in case the HCD is in the
* middle accessing this QH.
*/
(void) ehci_wait_for_sof(ehcip);
/* Sync the QH QTD pool to get up to date information */
/* Modify the status bit and halt this QH. */
~(EHCI_QH_STS_ACTIVE)) | EHCI_QH_STS_HALTED));
/* Check to see if the QH was in the middle of a transaction */
if ((split_status == EHCI_QH_STS_DO_COMPLETE_SPLIT) &&
(bytes_left != 0)) {
/* send ClearTTBuffer to this device's parent 2.0 hub */
}
/* Insert this QH back into the HCD's view */
}
/*
* ehci_clear_tt_buffer
*
* This function will sent a Clear_TT_Buffer request to the pipe's
* parent 2.0 hub.
*/
static void
{
int retry;
"ehci_clear_tt_buffer: ");
/* Get some information about the current pipe */
/*
* Create the wIndex for this request (usb spec 11.24.2.3)
* 3..0 Endpoint Number
* 10..4 Device Address
* 12..11 Endpoint Type
* 14..13 Reserved (must be 0)
* 15 Direction 1 = IN, 0 = OUT
*/
wValue = 0;
wValue |= 0x8000;
}
/* Manually fill in the request. */
/* Get the usba_device of the parent 2.0 hub. */
/* Get the default ctrl pipe for the parent 2.0 hub */
/* sync send the request to the default pipe */
&setup,
NULL,
break;
}
"ehci_clear_tt_buffer: Failed to clear tt buffer,"
"retry = %d, cr = %d, cb_flags = 0x%x\n",
}
if (retry >= 3) {
/*
* Ask the user to hotplug the 2.0 hub, to make sure that
* all the buffer is in sync since this command has failed.
*/
"Error recovery failure: Please hotplug the 2.0 hub at"
}
}
/*
* ehci_halt_fls_intr_qh:
*
*/
static void
{
"ehci_halt_fls_intr_qh:");
/*
* Ask the HC to deactivate the QH in a
*/
/*
* Wait at least EHCI_NUM_INTR_QH_LISTS+2 frame or until
* the QH has been halted.
*/
frames_past = 0;
(status != 0)) {
(void) ehci_wait_for_sof(ehcip);
}
/* Modify the status bit and halt this QH. */
for (i = 0; i < EHCI_NUM_INTR_QH_LISTS; i++) {
~(EHCI_QH_STS_ACTIVE)) | EHCI_QH_STS_HALTED));
(void) ehci_wait_for_sof(ehcip);
break;
}
}
"ehci_halt_fls_intr_qh: qh=0x%p frames past=%llu,"
" status=0x%x, 0x%x", (void *)qh,
(unsigned long long)(ehci_get_current_frame_number(ehcip) -
}
/*
* ehci_remove_qh:
*
* Remove the Endpoint Descriptor (QH) from the Host Controller's appropriate
* endpoint list.
*/
void
{
switch (attributes) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
/* ISOCH does not use QH, don't do anything but update count */
break;
}
}
/*
* ehci_remove_async_qh:
*
* Asynchronous schedule endpoint list.
*/
static void
{
"ehci_remove_async_qh:");
/* Make sure this QH is in the list */
/*
* If next QH and current QH are the same, then this is the last
* QH on the Asynchronous Schedule list.
*/
/*
* Null our pointer to the async sched list, but do not
* touch the host controller's list_addr.
*/
} else {
/* If this QH is the HEAD then find another one to replace it */
}
}
/* qh_prev to indicate it is no longer in the circular list */
if (reclaim) {
}
}
/*
* ehci_remove_intr_qh:
*
* Set up an interrupt endpoint to be removed from the Host Controller's (HC)
* interrupt lattice tree. The Endpoint Descriptor (QH) will be freed in the
* interrupt handler.
*/
static void
{
"ehci_remove_intr_qh:");
/* Make sure this QH is in the list */
if (next_qh) {
/* Update previous qh's link pointer */
/* Set the previous pointer of the next one */
}
} else {
/* Update previous qh's link pointer */
}
/* qh_prev to indicate it is no longer in the circular list */
if (reclaim) {
}
}
/*
* ehci_insert_qh_on_reclaim_list:
*
* Insert Endpoint onto the reclaim list
*/
static void
{
/*
* Read current usb frame number and add appropriate number of
* usb frames needs to wait before reclaiming current endpoint.
*/
/* Store 32-bit ID */
/* Insert the endpoint onto the reclamation list */
if (ehcip->ehci_reclaim_list) {
while (next_qh) {
}
} else {
}
}
/*
* ehci_deallocate_qh:
*
* Deallocate a Host Controller's (HC) Endpoint Descriptor (QH).
*
* NOTE: This function is also called from POLLED MODE.
*/
void
{
"ehci_deallocate_qh:");
if (first_dummy_qtd) {
if (second_dummy_qtd) {
}
}
"ehci_deallocate_qh: Deallocated 0x%p", (void *)old_qh);
}
/*
* ehci_qh_cpu_to_iommu:
*
* This function converts for the given Endpoint Descriptor (QH) CPU address
* to IO address.
*
* NOTE: This function is also called from POLLED MODE.
*/
{
sizeof (ehci_qh_t) * ehci_qh_pool_size);
return (qh);
}
/*
* ehci_qh_iommu_to_cpu:
*
* This function converts for the given Endpoint Descriptor (QH) IO address
* to CPU address.
*/
{
return (NULL);
}
return (qh);
}
/*
* Transfer Descriptor manipulations functions
*/
/*
* ehci_initialize_dummy:
*
* An Endpoint Descriptor (QH) has a dummy Transfer Descriptor (QTD) on the
* end of its QTD list. Initially, both the head and tail pointers of the QH
* point to the dummy QTD.
*/
static int
{
/* Allocate first dummy QTD */
if (first_dummy_qtd == NULL) {
return (USB_NO_RESOURCES);
}
/* Allocate second dummy QTD */
if (second_dummy_qtd == NULL) {
/* Deallocate first dummy QTD */
return (USB_NO_RESOURCES);
}
/* Next QTD pointer of an QH point to this new dummy QTD */
/* Set qh's dummy qtd field */
/* Set first_dummy's next qtd pointer */
return (USB_SUCCESS);
}
/*
* ehci_allocate_ctrl_resources:
*
* Calculates the number of tds necessary for a ctrl transfer, and allocates
* all the resources necessary.
*
* Returns NULL if there is insufficient resources otherwise TW.
*/
{
/* Add one more td for data phase */
if (ctrl_reqp->ctrl_wLength) {
qtd_count += 1;
}
/*
* If we have a control data phase, the data buffer starts
* on the next 4K page boundary. So the TW buffer is allocated
* to be larger than required. The buffer in the range of
* [SETUP_SIZE, EHCI_MAX_QTD_BUF_SIZE) is just for padding
* and not to be transferred.
*/
if (ctrl_reqp->ctrl_wLength) {
} else {
}
return (tw);
}
/*
* ehci_insert_ctrl_req:
*
* Create a Transfer Descriptor (QTD) and a data buffer for a control endpoint.
*/
/* ARGSUSED */
void
{
"ehci_insert_ctrl_req:");
/*
* Save current control request pointer and timeout values
* in transfer wrapper.
*/
/*
* Initialize the callback and any callback data for when
* the qtd completes.
*/
/*
* swap the setup bytes where necessary since we specified
* NEVERSWAP
*/
setup_packet[0] = bmRequestType;
/*
* The QTD's are placed on the QH one at a time.
* Once this QTD is placed on the done list, the
* data or status phase QTD will be enqueued.
*/
"ehci_insert_ctrl_req: pp 0x%p", (void *)pp);
/*
* If this control transfer has a data phase, record the
* direction. If the data phase is an OUT transaction,
* copy the data into the buffer of the transfer wrapper.
*/
if (wLength != 0) {
/* There is a data stage. Find the direction */
if (bmRequestType & USB_DEV_REQ_DEV_TO_HOST) {
} else {
/* Copy the data into the message */
wLength);
}
/*
* Create the QTD. If this is an OUT transaction,
* the data is already in the buffer of the TW.
* The transfer should start from EHCI_MAX_QTD_BUF_SIZE
* which is 4K aligned, though the ctrl phase only
* transfers a length of SETUP_SIZE. The padding data
* in the TW buffer are discarded.
*/
/*
* The direction of the STATUS QTD depends on
* the direction of the transfer.
*/
} else {
}
} else {
/*
* There is no data stage, then initiate
* status phase from the host.
*/
}
/* Start the timer for this control transfer */
}
/*
* ehci_allocate_bulk_resources:
*
* Calculates the number of tds necessary for a ctrl transfer, and allocates
* all the resources necessary.
*
* Returns NULL if there is insufficient resources otherwise TW.
*/
{
/* Check the size of bulk request */
"ehci_allocate_bulk_resources: Bulk request size 0x%x is "
return (NULL);
}
/* Get the required bulk packet size */
qtd_count += 1;
}
return (tw);
}
/*
* ehci_insert_bulk_req:
*
* Create a Transfer Descriptor (QTD) and a data buffer for a bulk
* endpoint.
*/
/* ARGSUSED */
void
{
int pipe_dir;
"ehci_insert_bulk_req: bulk_reqp = 0x%p flags = 0x%x",
/* Get the bulk pipe direction */
/* Get the required bulk packet size */
if (bulk_pkt_size) {
}
"ehci_insert_bulk_req: bulk_pkt_size = %d", bulk_pkt_size);
/*
* Save current bulk request pointer and timeout values
* in transfer wrapper.
*/
/*
* Initialize the callback and any callback
* data required when the qtd completes.
*/
}
}
/* Insert all the bulk QTDs */
/* Check for last qtd */
/* Check for inserting residue data */
if (residue) {
}
}
/* Insert the QTD onto the endpoint */
}
/* Start the timer for this bulk transfer */
}
/*
* ehci_start_periodic_pipe_polling:
*
* NOTE: This function is also called from POLLED MODE.
*/
int
{
int error = USB_SUCCESS;
"ehci_start_periodic_pipe_polling: ep%d",
/*
* Check and handle start polling on root hub interrupt pipe.
*/
USB_EP_ATTR_INTR)) {
return (error);
}
case EHCI_PIPE_STATE_IDLE:
/* Save the Original client's Periodic IN request */
/*
* This pipe is uninitialized or if a valid QTD is
* not found then insert a QTD on the interrupt IN
* endpoint.
*/
if (error != USB_SUCCESS) {
"ehci_start_periodic_pipe_polling: "
"Start polling failed");
return (error);
}
"ehci_start_periodic_pipe_polling: PP = 0x%p", (void *)pp);
#ifdef DEBUG
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
#endif
break;
case EHCI_PIPE_STATE_ACTIVE:
"ehci_start_periodic_pipe_polling: "
"Polling is already in progress");
error = USB_FAILURE;
break;
case EHCI_PIPE_STATE_ERROR:
"ehci_start_periodic_pipe_polling: "
"Pipe is halted and perform reset"
"before restart polling");
error = USB_FAILURE;
break;
default:
"ehci_start_periodic_pipe_polling: "
"Undefined state");
error = USB_FAILURE;
break;
}
return (error);
}
/*
* ehci_start_pipe_polling:
*
* Insert the number of periodic requests corresponding to polling
* interval as calculated during pipe open.
*/
static int
{
int error = USB_FAILURE;
"ehci_start_pipe_polling:");
/*
* For the start polling, pp_max_periodic_req_cnt will be zero
* and for the restart polling request, it will be non zero.
*
* In case of start polling request, find out number of requests
* required for the Interrupt IN endpoints corresponding to the
* endpoint polling interval. For Isochronous IN endpoints, it is
* always fixed since its polling interval will be one ms.
*/
if (pp->pp_max_periodic_req_cnt == 0) {
}
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
return (error);
}
static int
{
int i, total_tws;
int error = USB_SUCCESS;
/* Allocate all the necessary resources for the IN transfer */
for (i = 0; i < total_tws; i += 1) {
/* There are not enough resources, deallocate the TWs */
}
return (error);
} else {
}
}
}
"ehci_start_pipe_polling: max = %d curr = %d tw = %p:",
(void *)tw_list);
}
return (error);
}
/*
* ehci_set_periodic_pipe_polling:
*
* Calculate the number of periodic requests needed corresponding to the
* interrupt IN endpoints polling interval. Table below gives the number
* of periodic requests needed for the interrupt IN endpoints according
* to endpoint polling interval.
*
* Polling interval Number of periodic requests
*
* 1ms 4
* 2ms 2
* 4ms to 32ms 1
*/
static void
{
"ehci_set_periodic_pipe_polling:");
pp->pp_cur_periodic_req_cnt = 0;
/*
* Check usb flag whether USB_FLAGS_ONE_TIME_POLL flag is
* set and if so, set pp->pp_max_periodic_req_cnt to one.
*/
(pp->pp_client_periodic_in_reqp)) {
if (intr_reqp->intr_attributes &
return;
}
}
/*
* The ehci_adjust_polling_interval function will not fail
* at this instance since bandwidth allocation is already
* done. Here we are getting only the periodic interval.
*/
switch (interval) {
case EHCI_INTR_1MS_POLL:
break;
case EHCI_INTR_2MS_POLL:
break;
default:
break;
}
"ehci_set_periodic_pipe_polling: Max periodic requests = %d",
}
/*
* ehci_allocate_intr_resources:
*
* Calculates the number of tds necessary for a intr transfer, and allocates
* all the necessary resources.
*
* Returns NULL if there is insufficient resources otherwise TW.
*/
{
int pipe_dir;
"ehci_allocate_intr_resources:");
/* Get the length of interrupt transfer & alloc data */
if (intr_reqp) {
} else {
(((usb_intr_req_t *)pp->
}
/* Check the size of interrupt request */
if (tw_length > EHCI_MAX_QTD_XFER_SIZE) {
"ehci_allocate_intr_resources: Intr request size 0x%lx is "
return (NULL);
}
return (NULL);
}
if (pipe_dir == USB_EP_DIR_IN) {
USB_SUCCESS) {
}
} else {
if (tw_length) {
/* Copy the data into the buffer */
}
}
if (intr_reqp) {
}
/*
* Initialize the callback and any callback
* data required when the qtd completes.
*/
return (tw);
}
/*
* ehci_insert_intr_req:
*
* Insert an Interrupt request into the Host Controller's periodic list.
*/
/* ARGSUSED */
void
{
/* Insert another interrupt QTD */
/* Start the timer for this Interrupt transfer */
}
/*
* ehci_stop_periodic_pipe_polling:
*/
/* ARGSUSED */
int
{
"ehci_stop_periodic_pipe_polling: Flags = 0x%x", flags);
/*
* Check and handle stop polling on root hub interrupt pipe.
*/
USB_EP_ATTR_INTR)) {
return (USB_SUCCESS);
}
"ehci_stop_periodic_pipe_polling: "
"Polling already stopped");
return (USB_SUCCESS);
}
/* Set pipe state to pipe stop polling */
return (USB_SUCCESS);
}
/*
* ehci_insert_qtd:
*
* Insert a Transfer Descriptor (QTD) on an Endpoint Descriptor (QH).
* Always returns USB_SUCCESS for now. Once Isoch has been implemented,
* it may return USB_FAILURE.
*/
int
{
int error = USB_SUCCESS;
/* Allocate new dummy QTD */
/* Get the current and next dummy QTDs */
/* Update QH's dummy qtd field */
/* Update next dummy's next qtd pointer */
/*
* Fill in the current dummy qtd and
* add the new dummy to the end.
*/
/* Insert this qtd onto the tw */
/*
* Insert this qtd onto active qtd list.
* Don't insert polled mode qtd here.
*/
/* Insert this qtd onto active qtd list */
}
/* Print qh and qtd */
return (error);
}
/*
* ehci_allocate_qtd_from_pool:
*
* Allocate a Transfer Descriptor (QTD) from the QTD buffer pool.
*/
static ehci_qtd_t *
{
int i, ctrl;
/*
* Search for a blank Transfer Descriptor (QTD)
* in the QTD buffer pool.
*/
for (i = 0; i < ehci_qtd_pool_size; i ++) {
if (ctrl == EHCI_QTD_FREE) {
break;
}
}
if (i >= ehci_qtd_pool_size) {
"ehci_allocate_qtd_from_pool: QTD exhausted");
return (NULL);
}
"ehci_allocate_qtd_from_pool: Allocated %d", i);
/* Create a new dummy for the end of the QTD list */
"ehci_allocate_qtd_from_pool: qtd 0x%p", (void *)qtd);
/* Mark the newly allocated QTD as a dummy */
/* Mark the status of this new QTD to halted state */
/* Disable dummy QTD's next and alternate next pointers */
return (qtd);
}
/*
* ehci_fill_in_qtd:
*
* Fill in the fields of a Transfer Descriptor (QTD).
* The "Buffer Pointer" fields of a QTD are retrieved from the TW
* it is associated with.
*
* Note:
* qtd_dma_offs - the starting offset into the TW buffer, where the QTD
* should transfer from. It should be 4K aligned. And when
* a TW has more than one QTDs, the QTDs must be filled in
* increasing order.
* qtd_length - the total bytes to transfer.
*/
/*ARGSUSED*/
static void
{
uint_t i = 0;
int rem_len;
"ehci_fill_in_qtd: qtd 0x%p ctrl 0x%x bufoffs 0x%lx "
/* Assert that the qtd to be filled in is a dummy */
/* Change QTD's state Active */
/* Set the total length data transfer */
/*
* QTDs must be filled in increasing DMA offset order.
* tw_dma_offs is initialized to be 0 at TW creation and
* is only increased in this function.
*/
/*
* Save the starting dma buffer offset used and
* length of data that will be transfered in
* the current QTD.
*/
while (buf_len) {
/*
* Advance to the next DMA cookie until finding the cookie
* that qtd_dma_offs falls in.
* It is very likely this loop will never repeat more than
* once. It is here just to accommodate the case qtd_dma_offs
* is increased by multiple cookies during two consecutive
* calls into this function. In that case, the interim DMA
* buffer is allowed to be skipped.
*/
qtd_dma_offs) {
/*
* tw_dma_offs always points to the starting offset
* of a cookie
*/
tw->tw_cookie_idx++;
}
/*
* Counting the remained buffer length to be filled in
* the QTD for current DMA cookie
*/
/* Update the beginning of the buffer */
"ehci_fill_in_qtd: dmac_addr 0x%x dmac_size "
tw->tw_cookie_idx);
if (buf_len <= EHCI_MAX_QTD_BUF_SIZE) {
break;
} else {
}
i++;
}
/*
* Setup the alternate next qTD pointer if appropriate. The alternate
* qtd is currently pointing to a QTD that is not yet linked, but will
* be in the very near future. If a short_xfer occurs in this
* situation , the HC will automatically skip this QH. Eventually
* everything will be placed and the alternate_qtd will be valid QTD.
* For more information on alternate qtds look at section 3.5.2 in the
* EHCI spec.
*/
}
/*
* For control, bulk and interrupt QTD, now
* enable current QTD by setting active bit.
*/
/*
* For Control Xfer, qtd_ctrl_phase is a valid filed.
*/
if (qtd_ctrl_phase) {
}
/* Set the transfer wrapper */
}
/*
* ehci_insert_qtd_on_tw:
*
* The transfer wrapper keeps a list of all Transfer Descriptors (QTD) that
* are allocated for this transfer. Insert a QTD onto this list. The list
* of QTD's does not include the dummy QTD that is at the end of the list of
* QTD's for the endpoint.
*/
static void
{
/*
* Set the next pointer to NULL because
* this is the last QTD on list.
*/
} else {
/* Add the qtd to the end of the list */
}
}
/*
* ehci_insert_qtd_into_active_qtd_list:
*
* Insert current QTD into active QTD list.
*/
static void
{
/* Insert this QTD into QTD Active List */
if (curr_qtd) {
while (next_qtd) {
}
} else {
}
}
/*
* ehci_remove_qtd_from_active_qtd_list:
*
* Remove current QTD from the active QTD list.
*
* NOTE: This function is also called from POLLED MODE.
*/
void
{
}
if (prev_qtd) {
} else {
}
if (next_qtd) {
}
} else {
"ehci_remove_qtd_from_active_qtd_list: "
"Unable to find QTD in active_qtd_list");
}
}
/*
* ehci_traverse_qtds:
*
* Traverse the list of QTDs for given pipe using transfer wrapper. Since
* the endpoint is marked as Halted, the Host Controller (HC) is no longer
* accessing these QTDs. Remove all the QTDs that are attached to endpoint.
*/
static void
{
"ehci_traverse_qtds:");
/* Process the transfer wrappers for this pipe */
while (next_tw) {
/* Stop the the transfer timer */
/* Walk through each QTD for this transfer wrapper */
while (qtd) {
/* Remove this QTD from active QTD list */
/* Deallocate this QTD */
}
}
/* Clear current qtd pointer */
/* Update the next qtd pointer in the QH */
}
/*
* ehci_deallocate_qtd:
*
* Deallocate a Host Controller's (HC) Transfer Descriptor (QTD).
*
* NOTE: This function is also called from POLLED MODE.
*/
void
{
"ehci_deallocate_qtd: old_qtd = 0x%p", (void *)old_qtd);
/*
* Obtain the transaction wrapper and tw will be
* NULL for the dummy QTDs.
*/
tw = (ehci_trans_wrapper_t *)
}
/*
* If QTD's transfer wrapper is NULL, don't access its TW.
* Just free the QTD.
*/
if (tw) {
}
}
} else {
}
}
}
"Dealloc_qtd: qtd 0x%p", (void *)old_qtd);
}
/*
* ehci_qtd_cpu_to_iommu:
*
* This function converts for the given Transfer Descriptor (QTD) CPU address
* to IO address.
*
* NOTE: This function is also called from POLLED MODE.
*/
{
(uint32_t) (sizeof (ehci_qtd_t) *
(ehcip->ehci_qtd_pool_addr))));
sizeof (ehci_qtd_t) * ehci_qtd_pool_size);
return (td);
}
/*
* ehci_qtd_iommu_to_cpu:
*
* This function converts for the given Transfer Descriptor (QTD) IO address
* to CPU address.
*
* NOTE: This function is also called from POLLED MODE.
*/
{
return (NULL);
}
return (qtd);
}
/*
* ehci_allocate_tds_for_tw_resources:
*
* Allocate n Transfer Descriptors (TD) from the TD buffer pool and places it
* into the TW. Also chooses the correct alternate qtd when required. It is
* used for hardware short transfer support. For more information on
* alternate qtds look at section 3.5.2 in the EHCI spec.
* Here is how each alternate qtd's are used:
*
* Bulk: used fully.
* Intr: xfers only require 1 QTD, so alternate qtds are never used.
* Ctrl: Should not use alternate QTD
* Isoch: Doesn't support short_xfer nor does it use QTD
*
* Returns USB_NO_RESOURCES if it was not able to allocate all the requested TD
* otherwise USB_SUCCESS.
*/
int
{
int i;
int error = USB_SUCCESS;
for (i = 0; i < qtd_count; i += 1) {
"ehci_allocate_qtds_for_tw: "
"Unable to allocate %lu QTDs",
break;
}
if (i > 0) {
}
/*
* Save the second one as a pointer to the new dummy 1.
* It is used later for the alt_qtd_ptr. Xfers with only
* one qtd do not need alt_qtd_ptr.
* The tds's are allocated and put into a stack, that is
* why the second qtd allocated will turn out to be the
* new dummy 1.
*/
}
}
return (error);
}
/*
* ehci_allocate_tw_resources:
*
* Allocate a Transaction Wrapper (TW) and n Transfer Descriptors (QTD)
* from the QTD buffer pool and places it into the TW. It does an all
* or nothing transaction.
*
* Returns NULL if there is insufficient resources otherwise TW.
*/
static ehci_trans_wrapper_t *
{
"ehci_allocate_tw_resources: Unable to allocate TW");
} else {
USB_SUCCESS) {
} else {
}
}
return (tw);
}
/*
* ehci_free_tw_td_resources:
*
* Free all allocated resources for Transaction Wrapper (TW).
* Does not free the TW itself.
*
* Returns NULL if there is insufficient resources otherwise TW.
*/
static void
{
"ehci_free_tw_td_resources: tw = 0x%p", (void *)tw);
/* Save the pointer to the next qtd before destroying it */
}
}
/*
* Transfer Wrapper functions
*
* ehci_create_transfer_wrapper:
*
* Create a Transaction Wrapper (TW) and this involves the allocating of DMA
* resources.
*/
static ehci_trans_wrapper_t *
{
int result;
int kmem_flag;
int (*dmamem_wait)(caddr_t);
"ehci_create_transfer_wrapper: length = 0x%lx flags = 0x%x",
/* SLEEP flag should not be used in interrupt context */
if (servicing_interrupt()) {
} else {
}
/* Allocate space for the transfer wrapper */
"ehci_create_transfer_wrapper: kmem_zalloc failed");
return (NULL);
}
/* zero-length packet doesn't need to allocate dma memory */
if (length == 0) {
goto dmadone;
}
/* allow sg lists for transfer wrapper dma memory */
/* Allocate the DMA handle */
if (result != DDI_SUCCESS) {
"ehci_create_transfer_wrapper: Alloc handle failed");
return (NULL);
}
/* no need for swapping the raw data */
/* Allocate the memory */
if (result != DDI_SUCCESS) {
"ehci_create_transfer_wrapper: dma_mem_alloc fail");
return (NULL);
}
/* Bind the handle */
if (result != DDI_DMA_MAPPED) {
return (NULL);
}
tw->tw_cookie_idx = 0;
tw->tw_dma_offs = 0;
/*
* Only allow one wrapper to be added at a time. Insert the
* new transaction wrapper into the list for this pipe.
*/
} else {
}
/* Store the transfer length */
/* Store a back pointer to the pipe private structure */
/* Store the transfer type - synchronous or asynchronous */
/* Get and Store 32bit ID */
"ehci_create_transfer_wrapper: tw = 0x%p, ncookies = %u",
return (tw);
}
/*
* ehci_start_xfer_timer:
*
* Start the timer for the control, bulk and for one time interrupt
* transfers.
*/
/* ARGSUSED */
static void
{
"ehci_start_xfer_timer: tw = 0x%p", (void *)tw);
/*
* The timeout handling is done only for control, bulk and for
* one time Interrupt transfers.
*
* NOTE: If timeout is zero; Assume infinite timeout and don't
* insert this transfer on the timeout list.
*/
if (tw->tw_timeout) {
/*
* Add this transfer wrapper to the head of the pipe's
* tw timeout list.
*/
if (pp->pp_timeout_list) {
}
}
}
/*
* ehci_stop_xfer_timer:
*
* Start the timer for the control, bulk and for one time interrupt
* transfers.
*/
void
{
"ehci_stop_xfer_timer: tw = 0x%p", (void *)tw);
/* Obtain the pipe private structure */
/* check if the timeout tw list is empty */
return;
}
switch (flag) {
case EHCI_REMOVE_XFER_IFLAST:
break;
}
/* FALLTHRU */
case EHCI_REMOVE_XFER_ALWAYS:
(pp->pp_timer_id)) {
/* Reset the timer id to zero */
pp->pp_timer_id = 0;
}
break;
default:
break;
}
}
/*
* ehci_xfer_timeout_handler:
*
* Control or bulk transfer timeout handler.
*/
static void
ehci_xfer_timeout_handler(void *arg)
{
"ehci_xfer_timeout_handler: ehcip = 0x%p, ph = 0x%p",
/*
* Check whether still timeout handler is valid.
*/
if (pp->pp_timer_id != 0) {
/* Reset the timer id to zero */
pp->pp_timer_id = 0;
} else {
return;
}
/* Get the transfer timeout list head */
while (tw) {
/* Get the transfer on the timeout list */
tw->tw_timeout--;
if (tw->tw_timeout <= 0) {
/* remove the tw from the timeout list */
/* remove QTDs from active QTD list */
while (qtd) {
/* Get the next QTD from the wrapper */
}
/*
* Preserve the order to the requests
* started time sequence.
*/
}
}
/*
* The timer should be started before the callbacks.
* There is always a chance that ehci interrupts come
* in when we release the mutex while calling the tw back.
* To keep an accurate timeout it should be restarted
* as soon as possible.
*/
/* Get the expired transfer timeout list head */
while (tw) {
/* Get the next tw on the expired transfer timeout list */
/*
* The error handle routine will release the mutex when
* calling back to USBA. But this will not cause any race.
* We do the callback and are relying on ehci_pipe_cleanup()
* to halt the queue head and clean up since we should not
* block in timeout context.
*/
}
}
/*
* ehci_remove_tw_from_timeout_list:
*
* Remove Control or bulk transfer from the timeout list.
*/
static void
{
"ehci_remove_tw_from_timeout_list: tw = 0x%p", (void *)tw);
/* Obtain the pipe private structure */
if (pp->pp_timeout_list) {
} else {
}
}
}
}
}
/*
* ehci_start_timer:
*
* Start the pipe's timer
*/
static void
{
"ehci_start_timer: ehcip = 0x%p, pp = 0x%p",
/*
* Start the pipe's timer only if currently timer is not
* running and if there are any transfers on the timeout
* list. This timer will be per pipe.
*/
}
}
/*
* ehci_deallocate_tw:
*
* Deallocate of a Transaction Wrapper (TW) and this involves the freeing of
* of DMA resources.
*/
void
{
"ehci_deallocate_tw: tw = 0x%p", (void *)tw);
/*
* If the transfer wrapper has no Host Controller (HC)
* Transfer Descriptors (QTD) associated with it, then
* remove the transfer wrapper.
*/
if (tw->tw_qtd_head) {
return;
}
/* Make sure we return all the unused qtd's to the pool as well */
/*
* If pp->pp_tw_head and pp->pp_tw_tail are pointing to
* given TW then set the head and tail equal to NULL.
* Otherwise search for this TW in the linked TW's list
* and then remove this TW from the list.
*/
} else {
}
} else {
}
}
}
}
/*
* Make sure that, this TW has been removed
* from the timeout list.
*/
/* Deallocate this TW */
}
/*
* ehci_free_dma_resources:
*
* Free dma resources of a Transfer Wrapper (TW) and also free the TW.
*
* NOTE: This function is also called from POLLED MODE.
*/
void
{
"ehci_free_dma_resources: ph = 0x%p", (void *)ph);
/* Process the Transfer Wrappers */
while (next_tw) {
"ehci_free_dma_resources: Free TW = 0x%p", (void *)tw);
}
/* Adjust the head and tail pointers */
}
/*
* ehci_free_tw:
*
* Free the Transfer Wrapper (TW).
*/
/*ARGSUSED*/
static void
{
int rval;
"ehci_free_tw: tw = 0x%p", (void *)tw);
/* Free 32bit ID */
}
/* Free transfer wrapper */
}
/*
* Miscellaneous functions
*/
/*
* ehci_allocate_intr_in_resource
*
* Allocate interrupt request structure for the interrupt IN transfer.
*/
/*ARGSUSED*/
int
{
"ehci_allocate_intr_in_resource:"
/* Get the client periodic in request pointer */
/*
* If it a periodic IN request and periodic request is NULL,
* allocate corresponding usb periodic IN request for the
* current periodic polling request and copy the information
* from the saved periodic request structure.
*/
if (client_periodic_in_reqp) {
/* Get the interrupt transfer length */
length = ((usb_intr_req_t *)
} else {
}
if (curr_intr_reqp == NULL) {
"ehci_allocate_intr_in_resource: Interrupt"
"request structure allocation failed");
return (USB_NO_RESOURCES);
}
/* For polled mode */
if (client_periodic_in_reqp == NULL) {
} else {
/* Check and save the timeout value */
}
ph->p_req_count++;
return (USB_SUCCESS);
}
/*
* ehci_pipe_cleanup
*
* Cleanup ehci pipe.
*/
void
{
"ehci_pipe_cleanup: ph = 0x%p", (void *)ph);
if (EHCI_ISOC_ENDPOINT(eptd)) {
return;
}
/*
* Set the QH's status to Halt condition.
* If another thread is halting this function will automatically
* wait. If a pipe close happens at this time
* we will be in lots of trouble.
* If we are in an interrupt thread, don't halt, because it may
* do a wait_for_sof.
*/
/*
* Wait for processing all completed transfers and
* to send results to upstream.
*/
/* Save the data toggle information */
/*
* Traverse the list of QTDs for this pipe using transfer
* wrapper. Process these QTDs depending on their status.
* And stop the timer of this pipe.
*/
/* Make sure the timer is not running */
/* Do callbacks for all unfinished requests */
/* Free DMA resources */
switch (pipe_state) {
case EHCI_PIPE_STATE_CLOSE:
break;
case EHCI_PIPE_STATE_RESET:
/* Set completion reason */
completion_reason = (pipe_state ==
/* Restore the data toggle information */
/*
* Clear the halt bit to restart all the
* transactions on this pipe.
*/
/* Set pipe state to idle */
break;
}
/*
* Do the callback for the original client
* periodic IN request.
*/
if ((EHCI_PERIODIC_ENDPOINT(eptd)) &&
USB_EP_DIR_IN)) {
}
}
/*
* ehci_wait_for_transfers_completion:
*
* Wait for processing all completed transfers and to send results
* to upstream.
*/
static void
{
"ehci_wait_for_transfers_completion: pp = 0x%p", (void *)pp);
return;
}
pp->pp_count_done_qtds = 0;
/* Process the transfer wrappers for this pipe */
while (next_tw) {
/*
* Walk through each QTD for this transfer wrapper.
* If a QTD still exists, then it is either on done
* list or on the QH's list.
*/
while (qtd) {
pp->pp_count_done_qtds++;
}
}
}
"ehci_wait_for_transfers_completion: count_done_qtds = 0x%x",
if (!pp->pp_count_done_qtds) {
return;
}
/* Get the number of clock ticks to wait */
if (pp->pp_count_done_qtds) {
"ehci_wait_for_transfers_completion:"
"No transfers completion confirmation received");
}
}
/*
* ehci_check_for_transfers_completion:
*
* Check whether anybody is waiting for transfers completion event. If so, send
* this event and also stop initiating any new transfers on this pipe.
*/
void
{
"ehci_check_for_transfers_completion: pp = 0x%p", (void *)pp);
(pp->pp_cur_periodic_req_cnt == 0)) {
/* Reset pipe error to zero */
/* Do callback for original request */
}
if (pp->pp_count_done_qtds) {
"ehci_check_for_transfers_completion:"
/* Decrement the done qtd count */
pp->pp_count_done_qtds--;
if (!pp->pp_count_done_qtds) {
"ehci_check_for_transfers_completion:"
"Sent transfers completion event pp = 0x%p",
(void *)pp);
/* Send the transfer completion signal */
}
}
}
/*
* ehci_save_data_toggle:
*
* Save the data toggle information.
*/
static void
{
"ehci_save_data_toggle: ph = 0x%p", (void *)ph);
/* Reset the pipe error value */
/* Return immediately if it is a control pipe */
return;
}
/* Get the data toggle information from the endpoint (QH) */
/*
* If error is STALL, then, set
* data toggle to zero.
*/
if (error == USB_CR_STALL) {
data_toggle = DATA0;
}
/*
* Save the data toggle information
* in the usb device structure.
*/
}
/*
* ehci_restore_data_toggle:
*
* Restore the data toggle information.
*/
void
{
uint_t data_toggle = 0;
"ehci_restore_data_toggle: ph = 0x%p", (void *)ph);
/* Return immediately if it is a control pipe */
return;
}
0);
/*
* Restore the data toggle bit depending on the
* previous data toggle information.
*/
if (data_toggle) {
} else {
}
}
/*
* ehci_handle_outstanding_requests
*
* Deallocate interrupt request structure for the interrupt IN transfer.
* Do the callbacks for all unfinished requests.
*
* NOTE: This function is also called from POLLED MODE.
*/
void
{
"ehci_handle_outstanding_requests: pp = 0x%p", (void *)pp);
/* Deallocate all pre-allocated interrupt requests */
while (next_tw) {
/* Deallocate current interrupt request */
if (curr_xfer_reqp) {
if ((EHCI_PERIODIC_ENDPOINT(eptd)) &&
/* Decrement periodic in request count */
} else {
}
}
}
}
/*
* ehci_deallocate_intr_in_resource
*
* Deallocate interrupt request structure for the interrupt IN transfer.
*/
void
{
"ehci_deallocate_intr_in_resource: "
/* Check the current periodic in request pointer */
if (curr_xfer_reqp) {
ph->p_req_count--;
/* Free pre-allocated interrupt requests */
/* Set periodic in pipe state to idle */
}
}
/*
* ehci_do_client_periodic_in_req_callback
*
* Do callback for the original client periodic IN request.
*/
void
{
"ehci_do_client_periodic_in_req_callback: "
/*
* Check for Interrupt/Isochronous IN, whether we need to do
* callback for the original client's periodic IN request.
*/
if (pp->pp_client_periodic_in_reqp) {
if (EHCI_ISOC_ENDPOINT(eptd)) {
} else {
}
}
}
/*
* ehci_hcdi_callback()
*
* Convenience wrapper around usba_hcdi_cb() other than root hub.
*/
void
{
uint_t pipe_state = 0;
"ehci_hcdi_callback: ph = 0x%p, tw = 0x%p, cr = 0x%x",
/* Set the pipe state as per completion reason */
switch (completion_reason) {
case USB_CR_OK:
break;
case USB_CR_NO_RESOURCES:
case USB_CR_NOT_SUPPORTED:
case USB_CR_PIPE_RESET:
case USB_CR_STOPPED_POLLING:
break;
case USB_CR_PIPE_CLOSING:
break;
default:
/* Set the pipe state to error */
break;
}
} else {
}
}