ehci_isoch.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.
*/
/*
* Isochronous initialization functions
*/
int ehci_isoc_init(
void ehci_isoc_cleanup(
void ehci_isoc_pipe_cleanup(
static void ehci_wait_for_isoc_completion(
/*
* Isochronous request functions
*/
int ehci_insert_isoc_req(
static int ehci_insert_itd_req(
static int ehci_insert_sitd_req(
static int ehci_insert_isoc_with_frame_number(
static void ehci_remove_isoc_itds(
static void ehci_mark_reclaim_isoc(
static void ehci_reclaim_isoc(
/*
* Isochronronous handling functions.
*/
static void ehci_handle_isoc(
ehci_itd_t *itd);
static void ehci_handle_itd(
void *tw_handle_callback_value);
static void ehci_sendup_itd_message(
ehci_itd_t *td,
void ehci_hcdi_isoc_callback(
/*
* Isochronous initialization functions
*/
/*
* Initialize all the needed resources needed by isochronous pipes.
*/
int
{
return (ehci_allocate_isoc_pools(ehcip));
}
/*
* Cleanup isochronous resources.
*/
void
{
/* Free all the buffers */
for (i = 0; i < ehci_get_itd_pool_size(); i ++) {
if ((ctrl != EHCI_ITD_FREE) &&
(ctrl != EHCI_ITD_DUMMY) &&
(itd->itd_trans_wrapper)) {
itw = (ehci_isoc_xwrapper_t *)
/* Obtain the pipe private structure */
}
}
/*
* If EHCI_ITD_POOL_BOUND flag is set, then unbind
* the handle for ITD pools.
*/
if ((ehcip->ehci_dma_addr_bind_flag &
}
}
/* Free the ITD pool */
if (ehcip->ehci_itd_pool_dma_handle) {
}
}
/*
* ehci_isoc_pipe_cleanup
*
* Cleanup ehci isoc pipes.
*/
void ehci_isoc_pipe_cleanup(
"ehci_isoc_pipe_cleanup: ph = 0x%p", (void *)ph);
/* Stop all further processing */
/*
* Wait for processing all completed transfers
* and send result upstream/
*/
/* Go ahead and remove all remaining itds if there are any */
switch (pipe_state) {
case EHCI_PIPE_STATE_CLOSE:
break;
case EHCI_PIPE_STATE_RESET:
/* Set completion reason */
completion_reason = (pipe_state ==
/* Set pipe state to idle */
break;
}
/*
* Do the callback for the original client
* periodic IN request.
*/
}
}
/*
* ehci_wait_for_transfers_completion:
*
* Wait for processing all completed transfers and to send results
* to upstream.
*/
static void
{
return;
}
/* Get the number of clock ticks to wait */
if (pp->pp_itw_head) {
"ehci_wait_for_isoc_completion: "
"No transfers completion confirmation received");
}
}
/*
* Isochronous request functions
*/
/*
* ehci_allocate_isoc_resources:
*
* Calculates the number of tds necessary for a isoch transfer, and
* allocates all the necessary resources.
*
* Returns NULL if there is insufficient resources otherwise ITW.
*/
{
int pipe_dir, i;
size_t itw_xfer_size = 0;
"ehci_allocate_isoc_resources: flags = 0x%x", usb_flags);
/*
* Check whether pipe is in halted state.
*/
"ehci_allocate_isoc_resources:"
"Pipe is in error state, need pipe reset to continue");
return (NULL);
}
/* Calculate the maximum isochronous transfer size we allow */
/* Get the packet descriptor and number of packets to send */
if (isoc_reqp) {
} else {
isoc_pkt_descr = ((usb_isoc_req_t *)
isoc_pkt_count = ((usb_isoc_req_t *)
isoc_pkts_length = ((usb_isoc_req_t *)
}
/* Calculate the size of the transfer. */
if (pipe_dir == USB_EP_DIR_IN) {
for (i = 0; i < isoc_pkt_count; i++) {
/*
* isoc_pkt_length is used as Transaction Length and
* according to EHCI spec Table 3-3, the maximum value
* allowed is 3072
*/
return (NULL);
}
}
if ((isoc_pkts_length) &&
(isoc_pkts_length != itw_xfer_size)) {
"ehci_allocate_isoc_resources: "
"isoc_pkts_length 0x%lx is not equal to the sum of "
"all pkt lengths 0x%lx in an isoc request",
return (NULL);
}
} else {
}
/* Check the size of isochronous request */
if (itw_xfer_size > max_isoc_xfer_size) {
"ehci_allocate_isoc_resources: Maximum isoc request "
"size 0x%x Given isoc request size 0x%lx",
return (NULL);
}
"ehci_allocate_isoc_resources: length = 0x%lx", itw_xfer_size);
/* Allocate the itw for this request */
return (NULL);
}
if (pipe_dir == USB_EP_DIR_IN) {
USB_SUCCESS) {
return (NULL);
}
} else {
if (itw->itw_length) {
/* Copy the data into the buffer */
itw->itw_length);
}
}
return (itw);
}
/*
* ehci_insert_isoc_req:
*
* Insert an isochronous request into the Host Controller's
* isochronous list.
*/
int
{
int error;
"ehci_insert_isoc_req: flags = 0x%x port status = 0x%x",
/*
* Save address of first usb isochronous packet descriptor.
*/
} else {
}
/* Either all the isocs will be added or none of them will */
if (error != USB_SUCCESS) {
/*
* Deallocate all the ITDs, otherwise they will be
* lost forever.
*/
while (new_itd) {
}
if (pp->pp_cur_periodic_req_cnt) {
/*
* Set pipe state to stop polling and
* error to no resource. Don't insert
* any more isoch polling requests.
*/
} else {
/* Set periodic in pipe state to idle */
}
return (error);
}
/* Save how many packets and data actually went */
itw->itw_num_itds = 0;
itw->itw_length = 0;
}
/*
* Reset back to the address of first usb isochronous
* packet descriptor.
*/
/* Reset the CONTINUE flag */
return (error);
}
/*
* ehci_insert_itd_req:
*
* Insert an ITD request into the Host Controller's isochronous list.
*/
/* ARGSUSED */
static int
{
/*
* Get the current isochronous request and packet
* descriptor pointers.
*/
"ehci_insert_itd_req: itw_curr_xfer_reqp = 0x%p page = 0x%x,"
/* Insert all the isochronous TDs */
count = 0;
/* Grab a new itd */
if (multi > EHCI_ITD_CTRL_MULTI_MASK) {
"ehci_insert_itd_req: Wrong multi value.");
return (USB_FAILURE);
}
/* Fill 8 transaction for every iTD */
for (xactcount = 0, pageselected = 0;
xact_status = 0;
if (pageselected < EHCI_ITD_BUFFER_LIST_SIZE) {
} else {
"ehci_insert_itd_req: "
"Error in buffer pointer.");
return (USB_FAILURE);
}
/* Set IOC on the last TD. */
}
"ehci_insert_itd_req: count = 0x%x multi = %d"
"status = 0x%x page = 0x%x index = %d "
"pageselected = %d isoc_pkt_length = 0x%lx",
/* Fill in the new itd */
if (curr_isoc_xfer_offset >= EHCI_4K_ALIGN) {
pageselected ++;
page += EHCI_4K_ALIGN;
}
count ++;
break;
}
}
}
/*
* Add this itd to the itw before we add it in the PFL
* If adding it to the PFL fails, we will have to cleanup.
*/
}
return (USB_SUCCESS);
}
/*
* ehci_insert_sitd_req:
*
* Insert an SITD request into the Host Controller's isochronous list.
*/
/* ARGSUSED */
static int
{
/*
* Get the current isochronous request and packet
* descriptor pointers.
*/
/* Set the ctrl field */
ctrl = 0;
} else {
}
/* Set the micro frame schedule */
uframe_sched = 0;
/* Set the default page information */
page1 = 0;
/*
* Save the number of isochronous TDs needs
* to be insert to complete current isochronous request.
*/
/* Insert all the isochronous TDs */
for (count = 0, curr_isoc_xfer_offset = 0;
/* Set the transfer state information */
xfer_state = 0;
/* Set the size to the max packet size */
} else {
/* Set the size to the packet length */
xfer_state |= (isoc_pkt_length <<
}
/* Set IOC on the last TD. */
}
if (isoc_pkt_length % MAX_UFRAME_SITD_XFER) {
ssplit_count++;
}
if (ssplit_count > 1) {
} else {
}
/* Grab a new sitd */
/* Fill in the new sitd */
/*
* Add this itd to the itw before we add it in the PFL
* If adding it to the PFL fails, we will have to cleanup.
*/
}
return (USB_SUCCESS);
}
/*
* ehci_remove_isoc_itds:
*
* Remove all itds from the PFL.
*/
static void
{
"ehci_remove_isoc_itds: pp = 0x%p", (void *)pp);
while (curr_itw) {
"ehci_remove_isoc_itds: itw = 0x%p num itds = %d",
while (curr_itd) {
}
}
}
/*
* ehci_mark_reclaim_isoc:
*
* Set active ITDs to RECLAIM.
* Return number of ITD that need to be processed.
*/
static void
{
int i;
"ehci_mark_reclaim_isoc: pp = 0x%p", (void *)pp);
return;
}
/* Get the current frame number. */
/* Traverse the list of transfer descriptors */
while (curr_itw) {
"ehci_mark_reclaim_isoc: itw = 0x%p num itds = %d",
while (curr_itd) {
for (i = 0; i < EHCI_ITD_CTRL_LIST_SIZE; i++) {
EHCI_ITD_CTRL0 + i);
/* If still active, deactivate it */
if (isActive) {
ctrl &= ~EHCI_ITD_XFER_ACTIVE;
EHCI_ITD_CTRL0 + i,
ctrl);
break;
}
}
} else {
/* If it is still active deactivate it */
if (isActive) {
ctrl);
}
}
/*
* If the itd was active put it on the reclaim status,
* so the interrupt handler will know not to process it.
* Otherwise leave it alone and let the interrupt
* handler process it normally.
*/
if (isActive) {
}
}
}
}
/*
* ehci_reclaim_isoc:
*
* "Reclaim" itds that were marked as RECLAIM.
*/
static void
{
"ehci_reclaim_isoc: itd = 0x%p", (void *)itd);
/*
* These are itds that were marked "RECLAIM"
* by the pipe cleanup.
*
* Decrement the num_itds and the periodic in
* request count if necessary.
*/
} else {
}
}
/* Deallocate this transfer descriptor */
}
/*
* ehci_start_isoc_polling:
*
* Insert the number of periodic requests corresponding to polling
* interval as calculated during pipe open.
*/
int
{
int i, total_itws;
int error = USB_SUCCESS;
"ehci_start_isoc_polling:");
/* Allocate all the necessary resources for the IN transfer */
for (i = 0; i < total_itws; i += 1) {
/* There are not enough resources deallocate the ITWs */
}
return (error);
} else {
}
}
}
i = 0;
"ehci_start_isoc_polling: max = %d curr = %d itw = %p:",
(void *)itw_list);
if (error == USB_SUCCESS) {
} else {
/*
* Deallocate the remaining tw
* The current tw should have already been deallocated
*/
}
/*
* If this is the first req return an error.
* Otherwise return success.
*/
if (i != 0) {
error = USB_SUCCESS;
}
break;
}
i++;
}
return (error);
}
/*
* Isochronronous handling functions.
*/
/*
* ehci_traverse_active_isoc_list:
*/
void
{
"ehci_traverse_active_isoc_list:");
/* Sync ITD pool */
/* Traverse the list of done itds */
"ehci_traverse_active_isoc_list: current itd = 0x%p",
(void *)curr_itd);
while (curr_itd) {
/* Save the next_itd */
/* Get the transfer wrapper and the pp */
} else {
}
/* Get the ITD state */
/* Only process the ITDs marked as active. */
if (state == EHCI_ITD_ACTIVE) {
} else {
}
/*
* Deallocate the transfer wrapper if there are no more
* ITD's for the transfer wrapper. ehci_deallocate_itw()
* will not deallocate the tw for a periodic in endpoint
* since it will always have a ITD attached to it.
*/
/* Check any ISOC is waiting for transfers completion event */
"ehci_traverse_active_isoc_list: "
"Sent transfers completion event pp = 0x%p",
(void *)pp);
}
"ehci_traverse_active_isoc_list: state = 0x%x "
"pp = 0x%p itw = 0x%p itd = 0x%p next_itd = 0x%p",
(void *)next_itd);
}
}
static void
{
"ehci_handle_isoc:");
/* Obtain the pipe private structure */
}
/*
* ehci_handle_itd:
*
* Handle an (split) isochronous transfer descriptor.
* This function will deallocate the itd from the list as well.
*/
/* ARGSUSED */
static void
void *tw_handle_callback_value)
{
int error = USB_SUCCESS;
int i, index;
"ehci_handle_itd: pp=0x%p itw=0x%p itd=0x%p "
curr_isoc_reqp != NULL) {
for (i = 0; i < EHCI_ITD_CTRL_LIST_SIZE; i++) {
if (index == EHCI_ITD_UNUSED_INDEX) {
continue;
}
}
}
/*
* Decrement the ITDs counter and check whether all the isoc
* data has been send or received. If ITDs counter reaches
* zero then inform client driver about completion current
* isoc request. Otherwise wait for completion of other isoc
* ITDs or transactions on this pipe.
*/
if (--itw->itw_num_itds != 0) {
/* Deallocate this transfer descriptor */
return;
}
/*
* If this is a isoc in pipe, return the data to the client.
* For a isoc out pipe, there is no need to do anything.
*/
"ehci_handle_itd: Isoc out pipe, isoc_reqp=0x%p, data=0x%p",
/* Do the callback */
/* Deallocate this transfer descriptor */
return;
}
/* Decrement number of IN isochronous request count */
"ehci_handle_itd: pp_cur_periodic_req_cnt = 0x%x ",
/* Call ehci_sendup_itd_message to send message to upstream */
/* Deallocate this transfer descriptor */
/*
* If isochronous pipe state is still active, insert next isochronous
* request into the Host Controller's isochronous list.
*/
return;
}
USB_SUCCESS) {
USB_SUCCESS) {
itw->itw_num_itds = 0;
error = USB_FAILURE;
}
}
if ((error != USB_SUCCESS) ||
/*
* Set pipe state to stop polling and error to no
* resource. Don't insert any more isoch polling
* requests.
*/
} else {
/* Increment number of IN isochronous request count */
}
}
/*
* ehci_sendup_qtd_message:
* copy data, if necessary and do callback
*/
/* ARGSUSED */
static void
ehci_itd_t *td,
{
"ehci_sendup_itd_message:");
/* Copy the data into the mblk_t */
/* Get the message block */
if (length) {
/* Sync IO buffer */
/* Copy the data into the message */
/* Increment the write pointer */
} else {
"ehci_sendup_itd_message: Zero length packet");
}
}
/*
* ehci_hcdi_isoc_callback:
*
* Convenience wrapper around usba_hcdi_cb() other than root hub.
*/
void
{
uint_t pipe_state = 0;
"ehci_hcdi_isoc_callback: ph = 0x%p, itw = 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;
}
} else {
}
}