root/usr/src/uts/common/io/usb/hcd/ehci/ehci_isoch_util.c
/*
 * 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
 * or http://www.opensolaris.org/os/licensing.
 * 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 (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved.
 * Copyright (c) 2016 by Delphix. All rights reserved.
 */

/*
 * 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.
 */
#include <sys/usb/hcd/ehci/ehcid.h>
#include <sys/usb/hcd/ehci/ehci_xfer.h>
#include <sys/usb/hcd/ehci/ehci_util.h>

/* Adjustable variables for the size of isoc pools */
int ehci_itd_pool_size = EHCI_ITD_POOL_SIZE;

/*
 * pool functions
 */
int ehci_allocate_isoc_pools(
        ehci_state_t            *ehcip);
int ehci_get_itd_pool_size();

/*
 * Isochronous Transfer Wrapper Functions
 */
ehci_isoc_xwrapper_t *ehci_allocate_itw_resources(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        size_t                  itw_length,
        usb_flags_t             usb_flags,
        size_t                  pkt_count);
static ehci_isoc_xwrapper_t *ehci_allocate_itw(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        size_t                  length,
        usb_flags_t             usb_flags);
void ehci_deallocate_itw(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw);
static void ehci_free_itw_dma(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw);

/*
 * transfer descriptor functions
 */
static ehci_itd_t *ehci_allocate_itd(
        ehci_state_t            *ehcip);
void ehci_deallocate_itd(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *old_itd);
uint_t ehci_calc_num_itds(
        ehci_isoc_xwrapper_t    *itw,
        size_t                  pkt_count);
int ehci_allocate_itds_for_itw(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        uint_t                  itd_count);
static void ehci_deallocate_itds_for_itw(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw);
void ehci_insert_itd_on_itw(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd);
void ehci_insert_itd_into_active_list(
        ehci_state_t            *ehcip,
        ehci_itd_t              *itd);
void ehci_remove_itd_from_active_list(
        ehci_state_t            *ehcip,
        ehci_itd_t              *itd);
ehci_itd_t *ehci_create_done_itd_list(
        ehci_state_t            *ehcip);
int ehci_insert_isoc_to_pfl(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw);
void ehci_remove_isoc_from_pfl(
        ehci_state_t            *ehcip,
        ehci_itd_t              *curr_itd);


/*
 * Isochronous in resource functions
 */
int ehci_allocate_isoc_in_resource(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *tw,
        usb_flags_t             flags);
void ehci_deallocate_isoc_in_resource(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw);

/*
 * memory addr functions
 */
uint32_t ehci_itd_cpu_to_iommu(
        ehci_state_t            *ehcip,
        ehci_itd_t              *addr);
ehci_itd_t *ehci_itd_iommu_to_cpu(
        ehci_state_t            *ehcip,
        uintptr_t               addr);

/*
 * Error parsing functions
 */
void ehci_parse_isoc_error(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd);
static usb_cr_t ehci_parse_itd_error(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd);
static usb_cr_t ehci_parse_sitd_error(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd);

/*
 * print functions
 */
void ehci_print_itd(
        ehci_state_t            *ehcip,
        ehci_itd_t              *itd);
void ehci_print_sitd(
        ehci_state_t            *ehcip,
        ehci_itd_t              *itd);


/*
 * ehci_allocate_isoc_pools:
 *
 * Allocate the system memory for itd which are for low/full/high speed
 * Transfer Descriptors. Must be aligned to a 32 byte boundary.
 */
int
ehci_allocate_isoc_pools(ehci_state_t   *ehcip)
{
        ddi_device_acc_attr_t           dev_attr;
        size_t                          real_length;
        int                             result;
        uint_t                          ccount;
        int                             i;

        USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl,
            "ehci_allocate_isoc_pools:");

        /* Byte alignment */
        ehcip->ehci_dma_attr.dma_attr_align = EHCI_DMA_ATTR_TD_QH_ALIGNMENT;

        /* Allocate the itd pool DMA handle */
        result = ddi_dma_alloc_handle(ehcip->ehci_dip,
            &ehcip->ehci_dma_attr,
            DDI_DMA_SLEEP,
            0,
            &ehcip->ehci_itd_pool_dma_handle);

        if (result != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_allocate_isoc_pools: Alloc handle failed");

                return (DDI_FAILURE);
        }

        /* The host controller will be little endian */
        dev_attr.devacc_attr_version            = DDI_DEVICE_ATTR_V0;
        dev_attr.devacc_attr_endian_flags       = DDI_STRUCTURE_LE_ACC;
        dev_attr.devacc_attr_dataorder          = DDI_STRICTORDER_ACC;

        /* Allocate the memory */
        result = ddi_dma_mem_alloc(ehcip->ehci_itd_pool_dma_handle,
            ehci_itd_pool_size * sizeof (ehci_itd_t),
            &dev_attr,
            DDI_DMA_CONSISTENT,
            DDI_DMA_SLEEP,
            0,
            (caddr_t *)&ehcip->ehci_itd_pool_addr,
            &real_length,
            &ehcip->ehci_itd_pool_mem_handle);

        if (result != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_allocate_isoc_pools: Alloc memory failed");

                return (DDI_FAILURE);
        }

        /* Map the ITD pool into the I/O address space */
        result = ddi_dma_addr_bind_handle(
            ehcip->ehci_itd_pool_dma_handle,
            NULL,
            (caddr_t)ehcip->ehci_itd_pool_addr,
            real_length,
            DDI_DMA_RDWR | DDI_DMA_CONSISTENT,
            DDI_DMA_SLEEP,
            NULL,
            &ehcip->ehci_itd_pool_cookie,
            &ccount);

        bzero((void *)ehcip->ehci_itd_pool_addr,
            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) {
                        USB_DPRINTF_L2(PRINT_MASK_ATTA, ehcip->ehci_log_hdl,
                            "ehci_allocate_isoc_pools: More than 1 cookie");

                        return (DDI_FAILURE);
                }
        } else {
                USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl,
                    "ehci_allocate_isoc_pools: Result = %d", result);

                ehci_decode_ddi_dma_addr_bind_handle_result(ehcip, result);

                return (DDI_FAILURE);
        }

        /*
         * DMA addresses for ITD pools are bound
         */
        ehcip->ehci_dma_addr_bind_flag |= EHCI_ITD_POOL_BOUND;

        /* Initialize the ITD pool */
        for (i = 0; i < ehci_itd_pool_size; i++) {
                Set_ITD(ehcip->ehci_itd_pool_addr[i].itd_state,
                    EHCI_ITD_FREE);
        }

        return (DDI_SUCCESS);
}


int
ehci_get_itd_pool_size()
{
        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 LOW/FULL speed devices, 1 iTD is needed for each packet.
 * 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_isoc_xwrapper_t *
ehci_allocate_itw_resources(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        size_t                  itw_length,
        usb_flags_t             usb_flags,
        size_t                  pkt_count)
{
        uint_t                  itd_count;
        ehci_isoc_xwrapper_t    *itw;

        itw = ehci_allocate_itw(ehcip, pp, itw_length, usb_flags);

        if (itw == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_allocate_itw_resources: Unable to allocate ITW");
        } else {
                itd_count = ehci_calc_num_itds(itw, pkt_count);
                USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_allocate_itw_resources: itd_count = 0x%d", itd_count);

                if (ehci_allocate_itds_for_itw(ehcip, itw, itd_count) ==
                    USB_SUCCESS) {
                        itw->itw_num_itds = itd_count;
                } else {
                        ehci_deallocate_itw(ehcip, pp, itw);
                        itw = NULL;
                }
        }

        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 *
ehci_allocate_itw(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        size_t                  length,
        usb_flags_t             usb_flags)
{
        ddi_device_acc_attr_t   dev_attr;
        int                     result;
        size_t                  real_length;
        uint_t                  ccount; /* Cookie count */
        usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
        usba_device_t           *usba_device = ph->p_usba_device;
        usb_ep_descr_t          *endpoint = &ph->p_ep;
        ehci_isoc_xwrapper_t    *itw;

        USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_allocate_itw: length = 0x%lx flags = 0x%x",
            length, usb_flags);

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        /* Allocate space for the transfer wrapper */
        itw = kmem_zalloc(sizeof (ehci_isoc_xwrapper_t), KM_NOSLEEP);

        if (itw == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_allocate_itw: kmem_zalloc failed");

                return (NULL);
        }

        ehcip->ehci_dma_attr.dma_attr_align = EHCI_DMA_ATTR_ALIGNMENT;

        /* Allocate the DMA handle */
        result = ddi_dma_alloc_handle(ehcip->ehci_dip,
            &ehcip->ehci_dma_attr,
            DDI_DMA_DONTWAIT,
            0,
            &itw->itw_dmahandle);

        if (result != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_create_transfer_wrapper: Alloc handle failed");

                kmem_free(itw, sizeof (ehci_isoc_xwrapper_t));

                return (NULL);
        }

        /* no need for swapping the raw data in the buffers */
        dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
        dev_attr.devacc_attr_endian_flags  = DDI_NEVERSWAP_ACC;
        dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;

        /* Allocate the memory */
        result = ddi_dma_mem_alloc(itw->itw_dmahandle,
            length,
            &dev_attr,
            DDI_DMA_CONSISTENT,
            DDI_DMA_DONTWAIT,
            NULL,
            (caddr_t *)&itw->itw_buf,
            &real_length,
            &itw->itw_accesshandle);

        if (result != DDI_SUCCESS) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_create_transfer_wrapper: dma_mem_alloc fail");

                ddi_dma_free_handle(&itw->itw_dmahandle);
                kmem_free(itw, sizeof (ehci_isoc_xwrapper_t));

                return (NULL);
        }

        ASSERT(real_length >= length);

        /* Bind the handle */
        result = ddi_dma_addr_bind_handle(itw->itw_dmahandle,
            NULL,
            (caddr_t)itw->itw_buf,
            real_length,
            DDI_DMA_RDWR|DDI_DMA_CONSISTENT,
            DDI_DMA_DONTWAIT,
            NULL,
            &itw->itw_cookie,
            &ccount);

        if (result == DDI_DMA_MAPPED) {
                /* The cookie count should be 1 */
                if (ccount != 1) {
                        USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                            "ehci_create_transfer_wrapper: More than 1 cookie");

                        result = ddi_dma_unbind_handle(itw->itw_dmahandle);
                        ASSERT(result == DDI_SUCCESS);

                        ddi_dma_mem_free(&itw->itw_accesshandle);
                        ddi_dma_free_handle(&itw->itw_dmahandle);
                        kmem_free(itw, sizeof (ehci_isoc_xwrapper_t));

                        return (NULL);
                }
        } else {
                ehci_decode_ddi_dma_addr_bind_handle_result(ehcip, result);

                ddi_dma_mem_free(&itw->itw_accesshandle);
                ddi_dma_free_handle(&itw->itw_dmahandle);
                kmem_free(itw, sizeof (ehci_isoc_xwrapper_t));

                return (NULL);
        }

        /* Store a back pointer to the pipe private structure */
        itw->itw_pipe_private = pp;
        if (pp->pp_itw_head == NULL) {
                pp->pp_itw_head = itw;
                pp->pp_itw_tail = itw;
        } else {
                pp->pp_itw_tail->itw_next = itw;
                pp->pp_itw_tail = itw;
        }

        /*
         * Store transfer information
         * itw_buf has been allocated and will be set later
         */
        itw->itw_length = length;
        itw->itw_flags = usb_flags;
        itw->itw_port_status = usba_device->usb_port_status;
        itw->itw_direction = endpoint->bEndpointAddress & USB_EP_DIR_MASK;

        /*
         * Store the endpoint information that will be used by the
         * transfer descriptors later.
         */
        mutex_enter(&usba_device->usb_mutex);
        itw->itw_hub_addr = usba_device->usb_hs_hub_addr;
        itw->itw_hub_port = usba_device->usb_hs_hub_port;
        itw->itw_endpoint_num = endpoint->bEndpointAddress & USB_EP_NUM_MASK;
        itw->itw_device_addr = usba_device->usb_addr;
        mutex_exit(&usba_device->usb_mutex);

        /* Get and Store 32bit ID */
        itw->itw_id = EHCI_GET_ID((void *)itw);
        ASSERT(itw->itw_id != 0);

        USB_DPRINTF_L3(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_create_itw: itw = 0x%p real_length = 0x%lx",
            (void *)itw, real_length);

        ehcip->ehci_periodic_req_count++;
        ehci_toggle_scheduler(ehcip);

        return (itw);
}


/*
 * ehci_deallocate_itw:
 *
 * Deallocate of a Isochronous Transaction Wrapper (TW) and this involves the
 * freeing of DMA resources.
 */
void
ehci_deallocate_itw(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw)
{
        ehci_isoc_xwrapper_t    *prev, *next;

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "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) {
                ASSERT(itw->itw_itd_tail != NULL);

                return;
        }

        ASSERT(itw->itw_itd_tail == NULL);

        /* Make sure we return all the unused itd's to the pool as well */
        ehci_deallocate_itds_for_itw(ehcip, itw);

        /*
         * 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.
         */
        if (pp->pp_itw_head == itw) {
                if (pp->pp_itw_tail == itw) {
                        pp->pp_itw_head = NULL;
                        pp->pp_itw_tail = NULL;
                } else {
                        pp->pp_itw_head = itw->itw_next;
                }
        } else {
                prev = pp->pp_itw_head;
                next = prev->itw_next;

                while (next && (next != itw)) {
                        prev = next;
                        next = next->itw_next;
                }

                if (next == itw) {
                        prev->itw_next = next->itw_next;

                        if (pp->pp_itw_tail == itw) {
                                pp->pp_itw_tail = prev;
                        }
                }
        }

        /* Free this iTWs dma resources */
        ehci_free_itw_dma(ehcip, pp, itw);

        ehcip->ehci_periodic_req_count--;
        ehci_toggle_scheduler(ehcip);
}


/*
 * ehci_free_itw_dma:
 *
 * Free the Isochronous Transfer Wrapper dma resources.
 */
/*ARGSUSED*/
static void
ehci_free_itw_dma(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw)
{
        int     rval;

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_free_itw_dma: itw = 0x%p", (void *)itw);

        ASSERT(itw != NULL);
        ASSERT(itw->itw_id != 0);

        /* Free 32bit ID */
        EHCI_FREE_ID((uint32_t)itw->itw_id);

        rval = ddi_dma_unbind_handle(itw->itw_dmahandle);
        ASSERT(rval == DDI_SUCCESS);

        ddi_dma_mem_free(&itw->itw_accesshandle);
        ddi_dma_free_handle(&itw->itw_dmahandle);

        /* Free transfer wrapper */
        kmem_free(itw, sizeof (ehci_isoc_xwrapper_t));
}


/*
 * transfer descriptor functions
 */
/*
 * ehci_allocate_itd:
 *
 * Allocate a Transfer Descriptor (iTD) from the iTD buffer pool.
 */
static ehci_itd_t *
ehci_allocate_itd(ehci_state_t  *ehcip)
{
        int             i, state;
        ehci_itd_t      *itd;

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        /*
         * Search for a blank Transfer Descriptor (iTD)
         * in the iTD buffer pool.
         */
        for (i = 0; i < ehci_itd_pool_size; i ++) {
                state = Get_ITD(ehcip->ehci_itd_pool_addr[i].itd_state);
                if (state == EHCI_ITD_FREE) {
                        break;
                }
        }

        if (i >= ehci_itd_pool_size) {
                USB_DPRINTF_L2(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
                    "ehci_allocate_itd: ITD exhausted");

                return (NULL);
        }

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_allocate_itd: Allocated %d", i);

        /* Create a new dummy for the end of the ITD list */
        itd = &ehcip->ehci_itd_pool_addr[i];

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_allocate_itd: itd 0x%p", (void *)itd);

        /* Mark the newly allocated ITD as a empty */
        Set_ITD(itd->itd_state, EHCI_ITD_DUMMY);

        return (itd);
}


/*
 * ehci_deallocate_itd:
 *
 * Deallocate a Host Controller's (HC) Transfer Descriptor (ITD).
 *
 */
void
ehci_deallocate_itd(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *old_itd)
{
        ehci_itd_t      *itd, *next_itd;

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_deallocate_itd: old_itd = 0x%p", (void *)old_itd);

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        /* If it has been marked RECLAIM it has already been removed */
        if (Get_ITD(old_itd->itd_state) != EHCI_ITD_RECLAIM) {
                ehci_remove_isoc_from_pfl(ehcip, old_itd);
        }

        /* Make sure the ITD is not in the PFL */
        ASSERT(Get_ITD_FRAME(old_itd->itd_frame_number) == 0);

        /* Remove the itd from the itw */
        itd = itw->itw_itd_head;
        if (old_itd != itd) {
                next_itd = ehci_itd_iommu_to_cpu(ehcip,
                    Get_ITD(itd->itd_itw_next_itd));

                while (next_itd != old_itd) {
                        itd = next_itd;
                        next_itd = ehci_itd_iommu_to_cpu(ehcip,
                            Get_ITD(itd->itd_itw_next_itd));
                }

                Set_ITD(itd->itd_itw_next_itd, old_itd->itd_itw_next_itd);

                if (itd->itd_itw_next_itd == 0) {
                        itw->itw_itd_tail = itd;
                }
        } else {
                itw->itw_itd_head = ehci_itd_iommu_to_cpu(
                    ehcip, Get_ITD(old_itd->itd_itw_next_itd));

                if (itw->itw_itd_head == NULL) {
                        itw->itw_itd_tail = NULL;
                }
        }

        bzero((char *)old_itd, sizeof (ehci_itd_t));
        Set_ITD(old_itd->itd_state, EHCI_ITD_FREE);

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "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
 * transaction of a FULL/LOW transaction.
 *
 * For FULL/LOW transaction more itds are necessary if it
 * spans frames.
 */
uint_t
ehci_calc_num_itds(
        ehci_isoc_xwrapper_t    *itw,
        size_t                  pkt_count)
{
        uint_t                  multiplier, itd_count;

        /* Allocate the appropriate isoc resources */
        if (itw->itw_port_status == USBA_HIGH_SPEED_DEV) {
                /* Multiplier needs to be passed in somehow */
                multiplier = 1 * 8;
                itd_count = pkt_count / multiplier;
                if (pkt_count % multiplier) {
                        itd_count++;
                }
        } else {
                itd_count = (uint_t)pkt_count;
        }

        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
ehci_allocate_itds_for_itw(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        uint_t                  itd_count)
{
        ehci_itd_t              *itd;
        uint32_t                itd_addr;
        int                     i;
        int                     error = USB_SUCCESS;

        for (i = 0; i < itd_count; i += 1) {
                itd = ehci_allocate_itd(ehcip);
                if (itd == NULL) {
                        error = USB_NO_RESOURCES;
                        USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                            "ehci_allocate_itds_for_itw: "
                            "Unable to allocate %d ITDs",
                            itd_count);
                        break;
                }
                if (i > 0) {
                        itd_addr = ehci_itd_cpu_to_iommu(ehcip,
                            itw->itw_itd_free_list);
                        Set_ITD(itd->itd_link_ptr, itd_addr);
                }
                Set_ITD_INDEX(itd, 0, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 1, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 2, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 3, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 4, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 5, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 6, EHCI_ITD_UNUSED_INDEX);
                Set_ITD_INDEX(itd, 7, EHCI_ITD_UNUSED_INDEX);
                itw->itw_itd_free_list = itd;
        }

        return (error);
}


/*
 * ehci_deallocate_itds_for_itw:
 *
 * Free all allocated resources for Transaction Wrapper (TW).
 * Does not free the iTW itself.
 */
static void
ehci_deallocate_itds_for_itw(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw)
{
        ehci_itd_t              *itd = NULL;
        ehci_itd_t              *temp_itd = NULL;

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_free_itw_itd_resources: itw = 0x%p", (void *)itw);

        itd = itw->itw_itd_free_list;
        while (itd != NULL) {
                /* Save the pointer to the next itd before destroying it */
                temp_itd = ehci_itd_iommu_to_cpu(ehcip,
                    Get_ITD(itd->itd_link_ptr));
                ehci_deallocate_itd(ehcip, itw, itd);
                itd = temp_itd;
        }
        itw->itw_itd_free_list = NULL;
}


/*
 * 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(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd)
{
        /*
         * Set the next pointer to NULL because
         * this is the last ITD on list.
         */
        Set_ITD(itd->itd_itw_next_itd, 0);

        if (itw->itw_itd_head == NULL) {
                ASSERT(itw->itw_itd_tail == NULL);
                itw->itw_itd_head = itd;
                itw->itw_itd_tail = itd;
        } else {
                ehci_itd_t *dummy = (ehci_itd_t *)itw->itw_itd_tail;

                ASSERT(dummy != NULL);
                ASSERT(Get_ITD(itd->itd_state) == EHCI_ITD_ACTIVE);

                /* Add the itd to the end of the list */
                Set_ITD(dummy->itd_itw_next_itd,
                    ehci_itd_cpu_to_iommu(ehcip, itd));

                itw->itw_itd_tail = itd;
        }

        Set_ITD(itd->itd_trans_wrapper, (uint32_t)itw->itw_id);
}


/*
 * ehci_insert_itd_into_active_list:
 *
 * Add current ITD into the active ITD list in reverse order.
 * When the done list is created, remove it in the reverse order.
 */
void
ehci_insert_itd_into_active_list(
        ehci_state_t            *ehcip,
        ehci_itd_t              *itd)
{
        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
        ASSERT(itd != NULL);

        Set_ITD(itd->itd_next_active_itd,
            ehci_itd_cpu_to_iommu(ehcip, ehcip->ehci_active_itd_list));
        ehcip->ehci_active_itd_list = itd;
}


/*
 * ehci_remove_itd_from_active_list:
 *
 * Remove current ITD from the active ITD list.
 */
void
ehci_remove_itd_from_active_list(
        ehci_state_t            *ehcip,
        ehci_itd_t              *itd)
{
        ehci_itd_t              *curr_itd, *next_itd;

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
        ASSERT(itd != NULL);

        USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_remove_itd_from_active_list: "
            "ehci_active_itd_list = 0x%p itd = 0x%p",
            (void *)ehcip->ehci_active_itd_list, (void *)itd);

        curr_itd = ehcip->ehci_active_itd_list;

        if (curr_itd == itd) {
                ehcip->ehci_active_itd_list =
                    ehci_itd_iommu_to_cpu(ehcip, itd->itd_next_active_itd);
                itd->itd_next_active_itd = 0;

                return;
        }

        next_itd = ehci_itd_iommu_to_cpu(ehcip, curr_itd->itd_next_active_itd);
        while (next_itd != itd) {
                curr_itd = next_itd;
                if (curr_itd) {
                        next_itd = ehci_itd_iommu_to_cpu(ehcip,
                            curr_itd->itd_next_active_itd);
                } else {
                        break;
                }
        }

        if ((curr_itd) && (next_itd == itd)) {
                Set_ITD(curr_itd->itd_next_active_itd,
                    Get_ITD(itd->itd_next_active_itd));
                Set_ITD(itd->itd_next_active_itd, 0);
        } else {
                USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "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_itd_t *
ehci_create_done_itd_list(
        ehci_state_t            *ehcip)
{
        usb_frame_number_t      current_frame_number;
        usb_frame_number_t      itd_frame_number, itd_reclaim_number;
        ehci_itd_t              *curr_itd = NULL, *next_itd = NULL;
        ehci_itd_t              *done_itd_list = NULL;
        uint_t                  state;

        USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
            "ehci_create_done_itd_list:");

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        /*
         * Get the current frame number.
         * Only process itd that were inserted before the current
         * frame number.
         */
        current_frame_number = ehci_get_current_frame_number(ehcip);

        curr_itd = ehcip->ehci_active_itd_list;

        while (curr_itd) {
                /* Get next itd from the active itd list */
                next_itd = ehci_itd_iommu_to_cpu(ehcip,
                    Get_ITD(curr_itd->itd_next_active_itd));

                /*
                 * 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.
                 */
                itd_frame_number = Get_ITD_FRAME(curr_itd->itd_frame_number);
                itd_reclaim_number =
                    Get_ITD_FRAME(curr_itd->itd_reclaim_number);

                /* Get the ITD state */
                state = Get_ITD(curr_itd->itd_state);

                if (((state == EHCI_ITD_ACTIVE) &&
                    (itd_frame_number < current_frame_number)) ||
                    ((state == EHCI_ITD_RECLAIM) &&
                    (itd_reclaim_number < current_frame_number))) {

                        /* Remove this ITD from active ITD list */
                        ehci_remove_itd_from_active_list(ehcip, curr_itd);

                        /*
                         * Create the done list in reverse order, since the
                         * active list was also created in reverse order.
                         */
                        Set_ITD(curr_itd->itd_next_active_itd,
                            ehci_itd_cpu_to_iommu(ehcip, done_itd_list));
                        done_itd_list = curr_itd;
                }

                curr_itd = next_itd;
        }

        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(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw)
{
        usb_isoc_req_t          *isoc_reqp = itw->itw_curr_xfer_reqp;
        usb_frame_number_t      current_frame_number, start_frame_number;
        uint_t                  ddic, pfl_number;
        ehci_periodic_frame_list_t *periodic_frame_list =
            ehcip->ehci_periodic_frame_list_tablep;
        uint32_t                addr, port_status;
        ehci_itd_t              *itd;

        USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_insert_isoc_to_pfl: "
            "isoc flags 0x%x itw = 0x%p",
            isoc_reqp->isoc_attributes, (void *)itw);

        /*
         * 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 */
        current_frame_number = ehci_get_current_frame_number(ehcip);

        /*
         * Check the given isochronous flags and get the frame number
         * to insert the itd into.
         */
        switch (isoc_reqp->isoc_attributes &
            (USB_ATTRS_ISOC_START_FRAME | USB_ATTRS_ISOC_XFER_ASAP)) {
        case USB_ATTRS_ISOC_START_FRAME:

                /* Starting frame number is specified */
                if (pp->pp_flag & EHCI_ISOC_XFER_CONTINUE) {
                        /* Get the starting usb frame number */
                        start_frame_number = pp->pp_next_frame_number;
                } else {
                        /* Check for the Starting usb frame number */
                        if ((isoc_reqp->isoc_frame_no == 0) ||
                            ((isoc_reqp->isoc_frame_no +
                            isoc_reqp->isoc_pkts_count) <
                            current_frame_number)) {

                                /* Exit the critical */
                                ddi_exit_critical(ddic);

                                USB_DPRINTF_L2(PRINT_MASK_LISTS,
                                    ehcip->ehci_log_hdl,
                                    "ehci_insert_isoc_to_pfl:"
                                    "Invalid starting frame number");

                                return (USB_INVALID_START_FRAME);
                        }

                        /* Get the starting usb frame number */
                        start_frame_number = isoc_reqp->isoc_frame_no;

                        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) &&
                    (pp->pp_next_frame_number > current_frame_number)) {
                        /*
                         * Get the next usb frame number.
                         */
                        start_frame_number = pp->pp_next_frame_number;
                } else {
                        /*
                         * Add appropriate offset to the current usb
                         * frame number and use it as a starting frame
                         * number.
                         */
                        start_frame_number =
                            current_frame_number + EHCI_FRAME_OFFSET;
                }

                if (!(pp->pp_flag & EHCI_ISOC_XFER_CONTINUE)) {
                        isoc_reqp->isoc_frame_no = start_frame_number;
                }
                break;
        default:
                /* Exit the critical */
                ddi_exit_critical(ddic);

                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "ehci_insert_isoc_to_pfl: Either starting "
                    "frame number or ASAP flags are not set, attrs = 0x%x",
                    isoc_reqp->isoc_attributes);

                return (USB_NO_FRAME_NUMBER);
        }

        if (itw->itw_port_status == USBA_HIGH_SPEED_DEV) {
                port_status = EHCI_ITD_LINK_REF_ITD;
        } else {
                port_status = EHCI_ITD_LINK_REF_SITD;
        }

        itd = itw->itw_itd_head;
        while (itd) {
                /* Find the appropriate frame list to put the itd into */
                pfl_number = start_frame_number % EHCI_NUM_PERIODIC_FRAME_LISTS;

                addr = Get_PFLT(periodic_frame_list->
                    ehci_periodic_frame_list_table[pfl_number]);
                Set_ITD(itd->itd_link_ptr, addr);

                /* Set the link_ref correctly as ITD or SITD. */
                addr = ehci_itd_cpu_to_iommu(ehcip, itd) & EHCI_ITD_LINK_PTR;
                addr |= port_status;

                Set_PFLT(periodic_frame_list->
                    ehci_periodic_frame_list_table[pfl_number], addr);

                /* Save which frame the ITD was inserted into */
                Set_ITD_FRAME(itd->itd_frame_number, start_frame_number);

                ehci_insert_itd_into_active_list(ehcip, itd);

                /* Get the next ITD in the ITW */
                itd = ehci_itd_iommu_to_cpu(ehcip,
                    Get_ITD(itd->itd_itw_next_itd));

                start_frame_number++;
        }

        /* Exit the critical */
        ddi_exit_critical(ddic);

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_insert_isoc_to_pfl: "
            "current frame number 0x%llx start frame number 0x%llx num itds %d",
            (unsigned long long)current_frame_number,
            (unsigned long long)start_frame_number, itw->itw_num_itds);

        /*
         * Increment this saved frame number by current number
         * of data packets needs to be transfer.
         */
        pp->pp_next_frame_number = start_frame_number;

        /*
         * Set EHCI_ISOC_XFER_CONTINUE flag in order to send other
         * isochronous packets,  part of the current isoch request
         * in the subsequent frames.
         */
        pp->pp_flag |= EHCI_ISOC_XFER_CONTINUE;

        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(
        ehci_state_t            *ehcip,
        ehci_itd_t              *curr_itd)
{
        ehci_periodic_frame_list_t *periodic_frame_list;
        uint_t          pfl_number;
        uint32_t        next_addr, curr_itd_addr;
        uint32_t        link_ref;
        ehci_itd_t      *prev_itd = NULL;

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_remove_isoc_from_pfl:");

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        /* Get the address of the current itd */
        curr_itd_addr = ehci_itd_cpu_to_iommu(ehcip, curr_itd);

        /*
         * Remove this ITD from the PFL
         * But first we need to find it in the PFL
         */
        periodic_frame_list = ehcip->ehci_periodic_frame_list_tablep;
        pfl_number = Get_ITD_FRAME(curr_itd->itd_frame_number) %
            EHCI_NUM_PERIODIC_FRAME_LISTS;

        USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
            "ehci_remove_isoc_from_pfl: itd = 0x%p pfl number 0x%x",
            (void *)curr_itd, pfl_number);

        next_addr = Get_PFLT(periodic_frame_list->
            ehci_periodic_frame_list_table[pfl_number]);
        while ((next_addr & EHCI_ITD_LINK_PTR) !=
            (curr_itd_addr & EHCI_ITD_LINK_PTR)) {

                link_ref = next_addr & EHCI_ITD_LINK_REF;

                if ((link_ref == EHCI_ITD_LINK_REF_ITD) ||
                    (link_ref == EHCI_ITD_LINK_REF_SITD)) {

                        prev_itd = ehci_itd_iommu_to_cpu(ehcip,
                            (next_addr & EHCI_ITD_LINK_PTR));
                        next_addr = Get_ITD(prev_itd->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)) {

                next_addr = Get_ITD(curr_itd->itd_link_ptr);

                if (prev_itd == NULL) {
                        /* This means PFL points to this ITD */
                        Set_PFLT(periodic_frame_list->
                            ehci_periodic_frame_list_table[pfl_number],
                            next_addr);
                } else {
                        /* Set the previous ITD's itd_link_ptr */
                        Set_ITD(prev_itd->itd_link_ptr, next_addr);
                }

                Set_ITD_FRAME(curr_itd->itd_frame_number, 0);
        } else {
                ASSERT((next_addr & EHCI_ITD_LINK_PTR) ==
                    (curr_itd_addr & EHCI_ITD_LINK_PTR));
                USB_DPRINTF_L3(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
                    "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(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw,
        usb_flags_t             flags)
{
        usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
        usb_isoc_req_t          *orig_isoc_reqp, *clone_isoc_reqp;

        USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_allocate_isoc_in_resource:"
            "pp = 0x%p itw = 0x%p flags = 0x%x", (void *)pp, (void *)itw,
            flags);

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
        ASSERT(itw->itw_curr_xfer_reqp == NULL);

        /* Get the client periodic in request pointer */
        orig_isoc_reqp = (usb_isoc_req_t *)(pp->pp_client_periodic_in_reqp);

        ASSERT(orig_isoc_reqp != NULL);

        clone_isoc_reqp = usba_hcdi_dup_isoc_req(ph->p_dip,
            orig_isoc_reqp, flags);

        if (clone_isoc_reqp == NULL) {
                USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
                    "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
         */
        itw->itw_curr_xfer_reqp = orig_isoc_reqp;

        pp->pp_client_periodic_in_reqp = (usb_opaque_t)clone_isoc_reqp;

        mutex_enter(&ph->p_mutex);
        ph->p_req_count++;
        mutex_exit(&ph->p_mutex);

        pp->pp_state = EHCI_PIPE_STATE_ACTIVE;

        return (USB_SUCCESS);
}


/*
 * ehci_deallocate_isoc_in_resource
 *
 * Deallocate interrupt request structure for the interrupt IN transfer.
 */
void
ehci_deallocate_isoc_in_resource(
        ehci_state_t            *ehcip,
        ehci_pipe_private_t     *pp,
        ehci_isoc_xwrapper_t    *itw)
{
        usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
        uchar_t                 ep_attr = ph->p_ep.bmAttributes;
        usb_isoc_req_t          *isoc_reqp;

        USB_DPRINTF_L4(PRINT_MASK_LISTS,
            ehcip->ehci_log_hdl,
            "ehci_deallocate_isoc_in_resource: "
            "pp = 0x%p itw = 0x%p", (void *)pp, (void *)itw);

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
        ASSERT((ep_attr & USB_EP_ATTR_MASK) == USB_EP_ATTR_ISOCH);

        isoc_reqp = itw->itw_curr_xfer_reqp;

        /* Check the current periodic in request pointer */
        if (isoc_reqp) {
                itw->itw_curr_xfer_reqp = NULL;
                itw->itw_curr_isoc_pktp = NULL;

                mutex_enter(&ph->p_mutex);
                ph->p_req_count--;
                mutex_exit(&ph->p_mutex);

                usb_free_isoc_req(isoc_reqp);

                /* Set periodic in pipe state to idle */
                pp->pp_state = EHCI_PIPE_STATE_IDLE;
        }
}


/*
 * ehci_itd_cpu_to_iommu:
 *
 * This function converts for the given Transfer Descriptor (ITD) CPU address
 * to IO address.
 */
uint32_t
ehci_itd_cpu_to_iommu(
        ehci_state_t    *ehcip,
        ehci_itd_t      *addr)
{
        uint32_t        td;

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        if (addr == NULL)
                return (0);

        td = (uint32_t)ehcip->ehci_itd_pool_cookie.dmac_address +
            (uint32_t)((uintptr_t)addr -
            (uintptr_t)(ehcip->ehci_itd_pool_addr));

        ASSERT(((uint32_t) (sizeof (ehci_itd_t) *
            (addr - ehcip->ehci_itd_pool_addr))) ==
            ((uint32_t)((uintptr_t)addr - (uintptr_t)
            (ehcip->ehci_itd_pool_addr))));

        ASSERT(td >= ehcip->ehci_itd_pool_cookie.dmac_address);
        ASSERT(td <= ehcip->ehci_itd_pool_cookie.dmac_address +
            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.
 */
ehci_itd_t *
ehci_itd_iommu_to_cpu(
        ehci_state_t    *ehcip,
        uintptr_t       addr)
{
        ehci_itd_t      *itd;

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        if (addr == 0)
                return (NULL);

        itd = (ehci_itd_t *)((uintptr_t)
            (addr - ehcip->ehci_itd_pool_cookie.dmac_address) +
            (uintptr_t)ehcip->ehci_itd_pool_addr);

        ASSERT(itd >= ehcip->ehci_itd_pool_addr);
        ASSERT((uintptr_t)itd <= (uintptr_t)ehcip->ehci_itd_pool_addr +
            (uintptr_t)(sizeof (ehci_itd_t) * ehci_itd_pool_size));

        return (itd);
}


/*
 * Error parsing functions
 */
void ehci_parse_isoc_error(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd)
{
        usb_isoc_req_t          *isoc_reqp;
        usb_cr_t                error;

        ASSERT(mutex_owned(&ehcip->ehci_int_mutex));

        isoc_reqp = itw->itw_curr_xfer_reqp;

        if (itw->itw_port_status == USBA_HIGH_SPEED_DEV) {
                error = ehci_parse_itd_error(ehcip, itw, itd);
        } else {
                error = ehci_parse_sitd_error(ehcip, itw, itd);

                if (error != USB_CR_OK) {
                        isoc_reqp->isoc_error_count++;

                        USB_DPRINTF_L2(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_parse_sitd_error: Error %d Device Address %d"
                            " Endpoint number %d", error, itw->itw_device_addr,
                            itw->itw_endpoint_num);
                }

        }
}


/* ARGSUSED */
static usb_cr_t ehci_parse_itd_error(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd)
{
        uint32_t                status, index;
        usb_cr_t                error = USB_CR_OK;
        uint32_t                i;
        usb_isoc_req_t          *isoc_reqp;

        isoc_reqp = itw->itw_curr_xfer_reqp;

        for (i = 0; i < EHCI_ITD_CTRL_LIST_SIZE; i++) {
                index = Get_ITD_INDEX(itd, i);
                if (index == 0xffffffff) {

                        continue;
                }

                error = USB_CR_OK;

                status = Get_ITD_BODY(itd, EHCI_ITD_CTRL0 + i) &
                    EHCI_ITD_XFER_STATUS_MASK;

                if (status & EHCI_ITD_XFER_DATA_BUFFER_ERR) {
                        if (itw->itw_direction == USB_EP_DIR_OUT) {
                                USB_DPRINTF_L3(PRINT_MASK_INTR,
                                    ehcip->ehci_log_hdl,
                                    "ehci_parse_itd_error: BUFFER Underrun");

                                error = USB_CR_BUFFER_UNDERRUN;
                        } else {
                                USB_DPRINTF_L3(PRINT_MASK_INTR,
                                    ehcip->ehci_log_hdl,
                                    "ehci_parse_itd_error: BUFFER Overrun");

                                error = USB_CR_BUFFER_OVERRUN;
                        }
                }

                if (status & EHCI_ITD_XFER_BABBLE) {
                        USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_parse_itd_error: BABBLE DETECTED");

                        error = USB_CR_DATA_OVERRUN;
                }

                if (status & EHCI_ITD_XFER_ERROR) {
                        USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_parse_itd_error: XACT ERROR");

                        error = USB_CR_DEV_NOT_RESP;
                }

                if (status & EHCI_ITD_XFER_ACTIVE) {
                        USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_parse_itd_error: NOT ACCESSED");

                        error = USB_CR_NOT_ACCESSED;
                }

                itw->itw_curr_isoc_pktp->isoc_pkt_actual_length = 0;

                /* Write the status of isoc data packet */
                itw->itw_curr_isoc_pktp->isoc_pkt_status = error;

                /* counts total number of error packets in this req */
                if (error != USB_CR_OK) {
                        isoc_reqp->isoc_error_count++;
                        USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_parse_itd_error: Error %d Device Address %d "
                            "Endpoint number %d", error, itw->itw_device_addr,
                            itw->itw_endpoint_num);
                }

                itw->itw_curr_isoc_pktp++;
        }

        return (error);
}

static usb_cr_t ehci_parse_sitd_error(
        ehci_state_t            *ehcip,
        ehci_isoc_xwrapper_t    *itw,
        ehci_itd_t              *itd)
{
        uint32_t                status;
        usb_cr_t                error;
        usb_isoc_pkt_descr_t    *isoc_pkt_descr;
        uint32_t                residue;

        isoc_pkt_descr = itw->itw_curr_isoc_pktp;

        status = Get_ITD_BODY(itd, EHCI_SITD_XFER_STATE) &
            EHCI_SITD_XFER_STATUS_MASK;

        switch (status) {
        case EHCI_SITD_XFER_ACTIVE:
                USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                    "ehci_check_for_sitd_error: NOT ACCESSED");
                error = USB_CR_NOT_ACCESSED;

                break;
        case EHCI_SITD_XFER_ERROR:
                USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                    "ehci_check_for_sitd_error: TT ERROR");

                error = USB_CR_UNSPECIFIED_ERR;

                break;
        case EHCI_SITD_XFER_DATA_BUFFER_ERR:
                if (itw->itw_direction == USB_EP_DIR_OUT) {
                        USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_check_for_sitd_error: BUFFER Underrun");
                        error = USB_CR_BUFFER_UNDERRUN;
                } else {
                        USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                            "ehci_check_for_sitd_error: BUFFER Overrun");
                        error = USB_CR_BUFFER_OVERRUN;
                }

                break;
        case EHCI_SITD_XFER_BABBLE:
                USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                    "ehci_check_for_sitd_error: BABBLE");
                error = USB_CR_DATA_OVERRUN;

                break;
        case EHCI_SITD_XFER_XACT_ERROR:
                USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                    "ehci_check_for_sitd_error: XACT ERROR");

                error = USB_CR_DEV_NOT_RESP;
                break;
        case EHCI_SITD_XFER_MISSED_UFRAME:
                USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                    "ehci_check_for_sitd_error: MISSED UFRAME");

                error = USB_CR_NOT_ACCESSED;
                break;
        default:
                USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
                    "ehci_check_for_sitd_error: NO ERROR");
                error = USB_CR_OK;

                break;
        }

        /* This is HCD specific and may not have this information */
        residue =
            (Get_ITD_BODY(itd, EHCI_SITD_XFER_STATE) &
            EHCI_SITD_XFER_TOTAL_MASK) >>
            EHCI_SITD_XFER_TOTAL_SHIFT;

        /*
         * Subtract the residue from the isoc_pkt_descr that
         * was set when this ITD was inserted.
         */
        isoc_pkt_descr->isoc_pkt_actual_length -= residue;

        /* Write the status of isoc data packet */
        isoc_pkt_descr->isoc_pkt_status = error;

        itw->itw_curr_isoc_pktp++;

        return (error);
}


/*
 * debug print functions
 */
void
ehci_print_itd(
        ehci_state_t    *ehcip,
        ehci_itd_t      *itd)
{
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_print_itd: itd = 0x%p", (void *)itd);

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_link_ptr: 0x%x ", Get_ITD(itd->itd_link_ptr));

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl0: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL0]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl1: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL1]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl2: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL2]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl3: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL3]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl4: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL4]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl5: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL5]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl6: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL6]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_ctrl7: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_CTRL7]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer0: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER0]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer1: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER1]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer2: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER2]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer3: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER3]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer4: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER4]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer5: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER5]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_buffer6: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_ITD_BUFFER6]));

        /* HCD private fields */
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_trans_wrapper: 0x%x ",
            Get_ITD(itd->itd_trans_wrapper));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_itw_next_itd: 0x%x ",
            Get_ITD(itd->itd_itw_next_itd));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_state: 0x%x ",
            Get_ITD(itd->itd_state));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_index: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x 0x%x ",
            Get_ITD_INDEX(itd, 0), Get_ITD_INDEX(itd, 1),
            Get_ITD_INDEX(itd, 2), Get_ITD_INDEX(itd, 3),
            Get_ITD_INDEX(itd, 4), Get_ITD_INDEX(itd, 5),
            Get_ITD_INDEX(itd, 6), Get_ITD_INDEX(itd, 7));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_frame_number: 0x%x ",
            Get_ITD(itd->itd_frame_number));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_reclaim_number: 0x%x ",
            Get_ITD(itd->itd_reclaim_number));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_next_active_itd: 0x%x ",
            Get_ITD(itd->itd_next_active_itd));
}


void
ehci_print_sitd(
        ehci_state_t    *ehcip,
        ehci_itd_t      *itd)
{
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "ehci_print_itd: itd = 0x%p", (void *)itd);

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_link_ptr: 0x%x ", Get_ITD(itd->itd_link_ptr));

        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\tsitd_ctrl: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_SITD_CTRL]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\tsitd_uframe_sched: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_SITD_UFRAME_SCHED]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\tsitd_xfer_state: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_SITD_XFER_STATE]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\tsitd_buffer0: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_SITD_BUFFER0]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\tsitd_buffer1: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_SITD_BUFFER1]));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\tsitd_prev_sitd: 0x%x ",
            Get_ITD(itd->itd_body[EHCI_SITD_PREV_SITD]));

        /* HCD private fields */
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_trans_wrapper: 0x%x ",
            Get_ITD(itd->itd_trans_wrapper));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_itw_next_itd: 0x%x ",
            Get_ITD(itd->itd_itw_next_itd));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_state: 0x%x ",
            Get_ITD(itd->itd_state));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_frame_number: 0x%x ",
            Get_ITD(itd->itd_frame_number));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_reclaim_number: 0x%x ",
            Get_ITD(itd->itd_reclaim_number));
        USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
            "\titd_next_active_itd: 0x%x ",
            Get_ITD(itd->itd_next_active_itd));
}