ehci_isoch_util.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 EHCI driver isochronous code, which handles all
* Checking of status of USB transfers, error recovery and callbacks.
*/
/* Adjustable variables for the size of isoc pools */
/*
* pool functions
*/
int ehci_get_itd_pool_size();
/*
* Isochronous Transfer Wrapper Functions
*/
static ehci_isoc_xwrapper_t *ehci_allocate_itw(
void ehci_deallocate_itw(
static void ehci_free_itw_dma(
/*
* transfer descriptor functions
*/
static ehci_itd_t *ehci_allocate_itd(
void ehci_deallocate_itd(
static void ehci_deallocate_itds_for_itw(
void ehci_insert_itd_on_itw(
ehci_itd_t *itd);
ehci_itd_t *itd);
ehci_itd_t *itd);
/*
* Isochronous in resource functions
*/
/*
* memory addr functions
*/
ehci_itd_t *addr);
/*
* Error parsing functions
*/
void ehci_parse_isoc_error(
ehci_itd_t *itd);
static usb_cr_t ehci_parse_itd_error(
ehci_itd_t *itd);
static usb_cr_t ehci_parse_sitd_error(
ehci_itd_t *itd);
/*
* print functions
*/
void ehci_print_itd(
ehci_itd_t *itd);
void ehci_print_sitd(
ehci_itd_t *itd);
/*
* ehci_allocate_isoc_pools:
*
* Transfer Descriptors. Must be aligned to a 32 byte boundary.
*/
int
{
int result;
int i;
"ehci_allocate_isoc_pools:");
/* Byte alignment */
/* Allocate the itd pool DMA handle */
0,
if (result != DDI_SUCCESS) {
"ehci_allocate_isoc_pools: Alloc handle failed");
return (DDI_FAILURE);
}
/* The host controller will be little endian */
/* Allocate the memory */
ehci_itd_pool_size * sizeof (ehci_itd_t),
&dev_attr,
0,
if (result != DDI_SUCCESS) {
"ehci_allocate_isoc_pools: Alloc memory failed");
return (DDI_FAILURE);
}
/* Map the ITD pool into the I/O address space */
NULL,
NULL,
&ccount);
ehci_itd_pool_size * sizeof (ehci_itd_t));
/* Process the result */
if (result == DDI_DMA_MAPPED) {
/* The cookie count should be 1 */
if (ccount != 1) {
"ehci_allocate_isoc_pools: More than 1 cookie");
return (DDI_FAILURE);
}
} else {
"ehci_allocate_isoc_pools: Result = %d", result);
return (DDI_FAILURE);
}
/*
* DMA addresses for ITD pools are bound
*/
/* Initialize the ITD pool */
for (i = 0; i < ehci_itd_pool_size; i++) {
}
return (DDI_SUCCESS);
}
int
{
return (ehci_itd_pool_size);
}
/*
* Isochronous Transfer Wrapper Functions
*/
/*
* ehci_allocate_itw_resources:
*
* Allocate an iTW and n iTD from the iTD buffer pool and places it into the
* ITW. It does an all or nothing transaction.
*
* Calculates the number of iTD needed based on pipe speed.
* For HIGH speed device, 1 iTD is needed for 8 to 24 packets, depending on
* the multiplier for "HIGH BANDWIDTH" transfers look at 4.7 in EHCI spec.
*
* Returns NULL if there is insufficient resources otherwise ITW.
*/
{
"ehci_allocate_itw_resources: Unable to allocate ITW");
} else {
"ehci_allocate_itw_resources: itd_count = 0x%d", itd_count);
USB_SUCCESS) {
} else {
}
}
return (itw);
}
/*
* ehci_allocate_itw:
*
* Creates a Isochronous Transfer Wrapper (itw) and populate it with this
* endpoint's data. This involves the allocation of DMA resources.
*
* ITW Fields not set by this function:
* - will be populated itds are allocated
* num_ids
* itd_head
* itd_tail
* curr_xfer_reqp
* curr_isoc_pktp
* itw_itd_free_list
* - Should be set by the calling function
* itw_handle_callback_value
*/
static ehci_isoc_xwrapper_t *
{
int result;
"ehci_allocate_itw: length = 0x%lx flags = 0x%x",
/* Allocate space for the transfer wrapper */
"ehci_allocate_itw: kmem_zalloc failed");
return (NULL);
}
/* Allocate the DMA handle */
0,
&itw->itw_dmahandle);
if (result != DDI_SUCCESS) {
"ehci_create_transfer_wrapper: Alloc handle failed");
return (NULL);
}
/* no need for swapping the raw data in the buffers */
/* Allocate the memory */
&dev_attr,
NULL,
&itw->itw_accesshandle);
if (result != DDI_SUCCESS) {
"ehci_create_transfer_wrapper: dma_mem_alloc fail");
return (NULL);
}
/* Bind the handle */
NULL,
NULL,
&itw->itw_cookie,
&ccount);
if (result == DDI_DMA_MAPPED) {
/* The cookie count should be 1 */
if (ccount != 1) {
"ehci_create_transfer_wrapper: More than 1 cookie");
return (NULL);
}
} else {
return (NULL);
}
/* Store a back pointer to the pipe private structure */
} else {
}
/*
* Store transfer information
* itw_buf has been allocated and will be set later
*/
/*
* Store the endpoint information that will be used by the
* transfer descriptors later.
*/
/* Get and Store 32bit ID */
"ehci_create_itw: itw = 0x%p real_length = 0x%lx",
(void *)itw, real_length);
return (itw);
}
/*
* ehci_deallocate_itw:
*
* Deallocate of a Isochronous Transaction Wrapper (TW) and this involves the
* freeing of DMA resources.
*/
void
{
"ehci_deallocate_itw: itw = 0x%p", (void *)itw);
/*
* If the transfer wrapper has no Host Controller (HC)
* Transfer Descriptors (ITD) associated with it, then
* remove the transfer wrapper.
*/
if (itw->itw_itd_head) {
return;
}
/* Make sure we return all the unused itd'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 {
}
}
}
}
/* Free this iTWs dma resources */
}
/*
* ehci_free_itw_dma:
*
* Free the Isochronous Transfer Wrapper dma resources.
*/
/*ARGSUSED*/
static void
{
int rval;
"ehci_free_itw_dma: itw = 0x%p", (void *)itw);
/* Free 32bit ID */
/* Free transfer wrapper */
}
/*
* transfer descriptor functions
*/
/*
* ehci_allocate_itd:
*
* Allocate a Transfer Descriptor (iTD) from the iTD buffer pool.
*/
static ehci_itd_t *
{
int i, state;
/*
* Search for a blank Transfer Descriptor (iTD)
* in the iTD buffer pool.
*/
for (i = 0; i < ehci_itd_pool_size; i ++) {
if (state == EHCI_ITD_FREE) {
break;
}
}
if (i >= ehci_itd_pool_size) {
"ehci_allocate_itd: ITD exhausted");
return (NULL);
}
"ehci_allocate_itd: Allocated %d", i);
/* Create a new dummy for the end of the ITD list */
"ehci_allocate_itd: itd 0x%p", (void *)itd);
/* Mark the newly allocated ITD as a empty */
return (itd);
}
/*
* ehci_deallocate_itd:
*
* Deallocate a Host Controller's (HC) Transfer Descriptor (ITD).
*
*/
void
{
"ehci_deallocate_itd: old_itd = 0x%p", (void *)old_itd);
/* If it has been marked RECLAIM it has already been removed */
}
/* Make sure the ITD is not in the PFL */
/* Remove the itd from the itw */
}
}
} else {
}
}
"Dealloc_itd: itd 0x%p", (void *)old_itd);
}
/*
* ehci_calc_num_itds:
*
* Calculates how many ITDs are needed for this request.
* The calculation is based on weather it is an HIGH speed
*
* spans frames.
*/
{
/* Allocate the appropriate isoc resources */
/* Multiplier needs to be passed in somehow */
if (pkt_count % multiplier) {
itd_count++;
}
} else {
}
return (itd_count);
}
/*
* ehci_allocate_itds_for_itw:
*
* Allocate n Transfer Descriptors (TD) from the TD buffer pool and places it
* into the TW.
*
* 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 < itd_count; i += 1) {
"ehci_allocate_itds_for_itw: "
"Unable to allocate %d ITDs",
break;
}
if (i > 0) {
}
}
return (error);
}
/*
* ehci_deallocate_itds_for_itw:
*
* Free all allocated resources for Transaction Wrapper (TW).
* Does not free the iTW itself.
*/
static void
{
"ehci_free_itw_itd_resources: itw = 0x%p", (void *)itw);
/* Save the pointer to the next itd before destroying it */
}
}
/*
* ehci_insert_itd_on_itw:
*
* The transfer wrapper keeps a list of all Transfer Descriptors (iTD) that
* are allocated for this transfer. Insert a iTD onto this list.
*/
void ehci_insert_itd_on_itw(
{
/*
* Set the next pointer to NULL because
* this is the last ITD on list.
*/
} else {
/* Add the itd to the end of the list */
}
}
/*
* ehci_insert_itd_into_active_list:
*
* Add current ITD into the active ITD list in reverse order.
* When he done list is created, remove it in the reverse order.
*/
void
{
}
/*
* ehci_remove_itd_from_active_list:
*
* Remove current ITD from the active ITD list.
*/
void
{
"ehci_remove_itd_from_active_list: "
"ehci_active_itd_list = 0x%p itd = 0x%p",
return;
}
if (curr_itd) {
} else {
break;
}
}
} else {
"ehci_remove_itd_from_active_list: "
"Unable to find ITD in active_itd_list");
}
}
/*
* ehci_create_done_itd_list:
*
* Traverse the active list and create a done list and remove them
* from the active list.
*/
{
"ehci_create_done_itd_list:");
/*
* Get the current frame number.
* Only process itd that were inserted before the current
* frame number.
*/
while (curr_itd) {
/* Get next itd from the active itd list */
/*
* If haven't past the frame number that the ITD was
* suppose to be executed, don't touch it. Just in
* case it is being processed by the HCD and cause
* a race condition.
*/
/* Get the ITD state */
if (((state == EHCI_ITD_ACTIVE) &&
(itd_frame_number < current_frame_number)) ||
((state == EHCI_ITD_RECLAIM) &&
/* Remove this ITD from active ITD list */
/*
* Create the done list in reverse order, since the
* active list was also created in reverse order.
*/
}
}
return (done_itd_list);
}
/*
* ehci_insert_isoc_to_pfl:
*
* Insert a ITD request into the Host Controller's isochronous list.
* All the ITDs in the ITW will be added the PFL at once. Either all
* of them will make it or none of them will.
*/
int
{
"ehci_insert_isoc_to_pfl: "
"isoc flags 0x%x itw = 0x%p",
/*
* Enter critical, while programming the usb frame number
* and inserting current isochronous TD into the ED's list.
*/
ddic = ddi_enter_critical();
/* Get the current frame number */
/*
* Check the given isochronous flags and get the frame number
* to insert the itd into.
*/
switch (isoc_reqp->isoc_attributes &
/* Starting frame number is specified */
/* Get the starting usb frame number */
} else {
/* Check for the Starting usb frame number */
if ((isoc_reqp->isoc_frame_no == 0) ||
((isoc_reqp->isoc_frame_no +
/* Exit the critical */
"ehci_insert_isoc_to_pfl:"
"Invalid starting frame number");
return (USB_INVALID_START_FRAME);
}
/* Get the starting usb frame number */
pp->pp_next_frame_number = 0;
}
break;
case USB_ATTRS_ISOC_XFER_ASAP:
/* ehci has to specify starting frame number */
if ((pp->pp_next_frame_number) &&
/*
* Get the next usb frame number.
*/
} else {
/*
* Add appropriate offset to the current usb
* frame number and use it as a starting frame
* number.
*/
}
}
break;
default:
/* Exit the critical */
"ehci_insert_isoc_to_pfl: Either starting "
"frame number or ASAP flags are not set, attrs = 0x%x",
return (USB_NO_FRAME_NUMBER);
}
} else {
}
while (itd) {
/* Find the appropriate frame list to put the itd into */
/* Set the link_ref correctly as ITD or SITD. */
addr |= port_status;
/* Save which frame the ITD was inserted into */
/* Get the next ITD in the ITW */
}
/* Exit the critical */
"ehci_insert_isoc_to_pfl: "
"current frame number 0x%llx start frame number 0x%llx num itds %d",
(unsigned long long)current_frame_number,
/*
* Increment this saved frame number by current number
* of data packets needs to be transfer.
*/
/*
* Set EHCI_ISOC_XFER_CONTINUE flag in order to send other
* isochronous packets, part of the current isoch request
* in the subsequent frames.
*/
return (USB_SUCCESS);
}
/*
* ehci_remove_isoc_to_pfl:
*
* Remove an ITD request from the Host Controller's isochronous list.
* If we can't find it, something has gone wrong.
*/
void
{
"ehci_remove_isoc_from_pfl:");
/* Get the address of the current itd */
/*
* Remove this ITD from the PFL
* But first we need to find it in the PFL
*/
"ehci_remove_isoc_from_pfl: itd = 0x%p pfl number 0x%x",
(void *)curr_itd, pfl_number);
while ((next_addr & EHCI_ITD_LINK_PTR) !=
(curr_itd_addr & EHCI_ITD_LINK_PTR)) {
if ((link_ref == EHCI_ITD_LINK_REF_ITD) ||
(link_ref == EHCI_ITD_LINK_REF_SITD)) {
(next_addr & EHCI_ITD_LINK_PTR));
} else {
break;
}
}
/*
* If the next itd is the current itd, that means we found it.
* Set the previous's ITD link ptr to the Curr_ITD's link ptr.
* But do not touch the Curr_ITD's link ptr.
*/
if ((next_addr & EHCI_ITD_LINK_PTR) ==
(curr_itd_addr & EHCI_ITD_LINK_PTR)) {
/* This means PFL points to this ITD */
} else {
/* Set the previous ITD's itd_link_ptr */
}
} else {
"ehci_remove_isoc_from_pfl: Unable to find ITD in PFL");
}
}
/*
* Isochronous in resource functions
*/
/*
* ehci_allocate_periodic_in_resource
*
* Allocate interrupt request structure for the interrupt IN transfer.
*/
int
{
"ehci_allocate_isoc_in_resource:"
flags);
/* Get the client periodic in request pointer */
if (clone_isoc_reqp == NULL) {
"ehci_allocate_isoc_in_resource: Isochronous"
"request structure allocation failed");
return (USB_NO_RESOURCES);
}
/*
* Save the client's isochronous request pointer and
* length of isochronous transfer in transfer wrapper.
* The dup'ed request is saved in pp_client_periodic_in_reqp
*/
ph->p_req_count++;
return (USB_SUCCESS);
}
/*
* ehci_deallocate_isoc_in_resource
*
* Deallocate interrupt request structure for the interrupt IN transfer.
*/
void
{
"ehci_deallocate_isoc_in_resource: "
/* Check the current periodic in request pointer */
if (isoc_reqp) {
ph->p_req_count--;
/* Set periodic in pipe state to idle */
}
}
/*
* ehci_itd_cpu_to_iommu:
*
* This function converts for the given Transfer Descriptor (ITD) CPU address
* to IO address.
*/
{
return (NULL);
}
(ehcip->ehci_itd_pool_addr))));
sizeof (ehci_itd_t) * ehci_itd_pool_size);
return (td);
}
/*
* ehci_itd_iommu_to_cpu:
*
* This function converts for the given Transfer Descriptor (ITD) IO address
* to CPU address.
*/
{
return (NULL);
}
return (itd);
}
/*
* Error parsing functions
*/
void ehci_parse_isoc_error(
{
} else {
"ehci_parse_sitd_error: Error %d Device Address %d"
}
}
}
/* ARGSUSED */
static usb_cr_t ehci_parse_itd_error(
{
uint32_t i;
for (i = 0; i < EHCI_ITD_CTRL_LIST_SIZE; i++) {
if (index == 0xffffffff) {
continue;
}
if (status & EHCI_ITD_XFER_DATA_BUFFER_ERR) {
"ehci_parse_itd_error: BUFFER Underrun");
} else {
"ehci_parse_itd_error: BUFFER Overrun");
}
}
if (status & EHCI_ITD_XFER_BABBLE) {
"ehci_parse_itd_error: BABBLE DETECTED");
}
if (status & EHCI_ITD_XFER_ERROR) {
"ehci_parse_itd_error: XACT ERROR");
}
if (status & EHCI_ITD_XFER_ACTIVE) {
"ehci_parse_itd_error: NOT ACCESSED");
}
/* Write the status of isoc data packet */
/* counts total number of error packets in this req */
"ehci_parse_itd_error: Error %d Device Address %d "
}
}
return (error);
}
static usb_cr_t ehci_parse_sitd_error(
{
switch (status) {
case EHCI_SITD_XFER_ACTIVE:
"ehci_check_for_sitd_error: NOT ACCESSED");
break;
case EHCI_SITD_XFER_ERROR:
"ehci_check_for_sitd_error: TT ERROR");
break;
"ehci_check_for_sitd_error: BUFFER Underrun");
} else {
"ehci_check_for_sitd_error: BUFFER Overrun");
}
break;
case EHCI_SITD_XFER_BABBLE:
"ehci_check_for_sitd_error: BABBLE");
break;
"ehci_check_for_sitd_error: XACT ERROR");
break;
"ehci_check_for_sitd_error: MISSED UFRAME");
break;
default:
"ehci_check_for_sitd_error: NO ERROR");
break;
}
/* This is HCD specific and may not have this information */
residue =
/*
* Subtract the residue from the isoc_pkt_descr that
* was set when this ITD was inserted.
*/
/* Write the status of isoc data packet */
return (error);
}
/*
* debug print functions
*/
void
{
"ehci_print_itd: itd = 0x%p", (void *)itd);
"\titd_ctrl0: 0x%x ",
"\titd_ctrl1: 0x%x ",
"\titd_ctrl2: 0x%x ",
"\titd_ctrl3: 0x%x ",
"\titd_ctrl4: 0x%x ",
"\titd_ctrl5: 0x%x ",
"\titd_ctrl6: 0x%x ",
"\titd_ctrl7: 0x%x ",
"\titd_buffer0: 0x%x ",
"\titd_buffer1: 0x%x ",
"\titd_buffer2: 0x%x ",
"\titd_buffer3: 0x%x ",
"\titd_buffer4: 0x%x ",
"\titd_buffer5: 0x%x ",
"\titd_buffer6: 0x%x ",
/* HCD private fields */
"\titd_trans_wrapper: 0x%x ",
"\titd_itw_next_itd: 0x%x ",
"\titd_state: 0x%x ",
"\titd_index: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x ",
"\titd_frame_number: 0x%x ",
"\titd_reclaim_number: 0x%x ",
"\titd_next_active_itd: 0x%x ",
}
void
{
"ehci_print_itd: itd = 0x%p", (void *)itd);
"\tsitd_ctrl: 0x%x ",
"\tsitd_uframe_sched: 0x%x ",
"\tsitd_xfer_state: 0x%x ",
"\tsitd_buffer0: 0x%x ",
"\tsitd_buffer1: 0x%x ",
"\tsitd_prev_sitd: 0x%x ",
/* HCD private fields */
"\titd_trans_wrapper: 0x%x ",
"\titd_itw_next_itd: 0x%x ",
"\titd_state: 0x%x ",
"\titd_frame_number: 0x%x ",
"\titd_reclaim_number: 0x%x ",
"\titd_next_active_itd: 0x%x ",
}