ehci_xfer.c revision 77e515715b61e28fcf0c3f30936492888cecfd8b
/*
* 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 2008 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
/*
* EHCI Host Controller Driver (EHCI)
*
* The EHCI driver is a software driver which interfaces to the Universal
* Serial Bus layer (USBA) and the Host Controller (HC). The interface to
* the Host Controller is defined by the EHCI Host Controller Interface.
*
* This module contains the main EHCI driver code which handles all USB
* transfers, bandwidth allocations and other general functionalities.
*/
#include <sys/usb/hcd/ehci/ehcid.h>
#include <sys/usb/hcd/ehci/ehci_intr.h>
#include <sys/usb/hcd/ehci/ehci_util.h>
#include <sys/usb/hcd/ehci/ehci_isoch.h>
/* 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 */
ehci_qh_t *ehci_alloc_qh(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
uint_t flag);
static void ehci_unpack_endpoint(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
ehci_qh_t *qh);
void ehci_insert_qh(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
static void ehci_insert_async_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
static void ehci_insert_intr_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
static void ehci_modify_qh_status_bit(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
halt_bit_t action);
static void ehci_halt_hs_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_qh_t *qh);
static void ehci_halt_fls_ctrl_and_bulk_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_qh_t *qh);
static void ehci_clear_tt_buffer(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
ehci_qh_t *qh);
static void ehci_halt_fls_intr_qh(
ehci_state_t *ehcip,
ehci_qh_t *qh);
void ehci_remove_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
boolean_t reclaim);
static void ehci_remove_async_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
boolean_t reclaim);
static void ehci_remove_intr_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
boolean_t reclaim);
static void ehci_insert_qh_on_reclaim_list(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
void ehci_deallocate_qh(
ehci_state_t *ehcip,
ehci_qh_t *old_qh);
uint32_t ehci_qh_cpu_to_iommu(
ehci_state_t *ehcip,
ehci_qh_t *addr);
ehci_qh_t *ehci_qh_iommu_to_cpu(
ehci_state_t *ehcip,
uintptr_t addr);
/* Transfer Descriptor (QTD) related functions */
static int ehci_initialize_dummy(
ehci_state_t *ehcip,
ehci_qh_t *qh);
ehci_trans_wrapper_t *ehci_allocate_ctrl_resources(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
usb_ctrl_req_t *ctrl_reqp,
usb_flags_t usb_flags);
void ehci_insert_ctrl_req(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_ctrl_req_t *ctrl_reqp,
ehci_trans_wrapper_t *tw,
usb_flags_t usb_flags);
ehci_trans_wrapper_t *ehci_allocate_bulk_resources(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
usb_bulk_req_t *bulk_reqp,
usb_flags_t usb_flags);
void ehci_insert_bulk_req(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_bulk_req_t *bulk_reqp,
ehci_trans_wrapper_t *tw,
usb_flags_t flags);
int ehci_start_periodic_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_opaque_t periodic_in_reqp,
usb_flags_t flags);
static int ehci_start_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags);
static int ehci_start_intr_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags);
static void ehci_set_periodic_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
ehci_trans_wrapper_t *ehci_allocate_intr_resources(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_intr_req_t *intr_reqp,
usb_flags_t usb_flags);
void ehci_insert_intr_req(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw,
usb_flags_t flags);
int ehci_stop_periodic_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags);
int ehci_insert_qtd(
ehci_state_t *ehcip,
uint32_t qtd_ctrl,
size_t qtd_dma_offs,
size_t qtd_length,
uint32_t qtd_ctrl_phase,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw);
static ehci_qtd_t *ehci_allocate_qtd_from_pool(
ehci_state_t *ehcip);
static void ehci_fill_in_qtd(
ehci_state_t *ehcip,
ehci_qtd_t *qtd,
uint32_t qtd_ctrl,
size_t qtd_dma_offs,
size_t qtd_length,
uint32_t qtd_ctrl_phase,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw);
static void ehci_insert_qtd_on_tw(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw,
ehci_qtd_t *qtd);
static void ehci_insert_qtd_into_active_qtd_list(
ehci_state_t *ehcip,
ehci_qtd_t *curr_qtd);
void ehci_remove_qtd_from_active_qtd_list(
ehci_state_t *ehcip,
ehci_qtd_t *curr_qtd);
static void ehci_traverse_qtds(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
void ehci_deallocate_qtd(
ehci_state_t *ehcip,
ehci_qtd_t *old_qtd);
uint32_t ehci_qtd_cpu_to_iommu(
ehci_state_t *ehcip,
ehci_qtd_t *addr);
ehci_qtd_t *ehci_qtd_iommu_to_cpu(
ehci_state_t *ehcip,
uintptr_t addr);
/* Transfer Wrapper (TW) functions */
static ehci_trans_wrapper_t *ehci_create_transfer_wrapper(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
size_t length,
uint_t usb_flags);
int ehci_allocate_tds_for_tw(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw,
size_t qtd_count);
static ehci_trans_wrapper_t *ehci_allocate_tw_resources(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
size_t length,
usb_flags_t usb_flags,
size_t td_count);
static void ehci_free_tw_td_resources(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw);
static void ehci_start_xfer_timer(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw);
void ehci_stop_xfer_timer(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw,
uint_t flag);
static void ehci_xfer_timeout_handler(void *arg);
static void ehci_remove_tw_from_timeout_list(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw);
static void ehci_start_timer(ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
void ehci_deallocate_tw(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw);
void ehci_free_dma_resources(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
static void ehci_free_tw(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw);
/* Miscellaneous functions */
int ehci_allocate_intr_in_resource(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw,
usb_flags_t flags);
void ehci_pipe_cleanup(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
static void ehci_wait_for_transfers_completion(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
void ehci_check_for_transfers_completion(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
static void ehci_save_data_toggle(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
void ehci_restore_data_toggle(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph);
void ehci_handle_outstanding_requests(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp);
void ehci_deallocate_intr_in_resource(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw);
void ehci_do_client_periodic_in_req_callback(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
usb_cr_t completion_reason);
void ehci_hcdi_callback(
usba_pipe_handle_data_t *ph,
ehci_trans_wrapper_t *tw,
usb_cr_t completion_reason);
/*
* Endpoint Descriptor (QH) manipulations functions
*/
/*
* ehci_alloc_qh:
*
* Allocate an endpoint descriptor (QH)
*
* NOTE: This function is also called from POLLED MODE.
*/
ehci_qh_t *
ehci_alloc_qh(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
uint_t flag)
{
int i, state;
ehci_qh_t *qh;
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_alloc_qh: ph = 0x%p flag = 0x%x", (void *)ph, flag);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* If this is for a ISOC endpoint return null.
* Isochronous uses ITD put directly onto the PFL.
*/
if (ph) {
if (EHCI_ISOC_ENDPOINT((&ph->p_ep))) {
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 ++) {
state = Get_QH(ehcip->ehci_qh_pool_addr[i].qh_state);
if (state == EHCI_QH_FREE) {
break;
}
}
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_alloc_qh: Allocated %d", i);
if (i == ehci_qh_pool_size) {
USB_DPRINTF_L2(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_alloc_qh: QH exhausted");
return (NULL);
} else {
qh = &ehcip->ehci_qh_pool_addr[i];
bzero((void *)qh, sizeof (ehci_qh_t));
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_alloc_qh: Allocated address 0x%p", (void *)qh);
/* Check polled mode flag */
if (flag == EHCI_POLLED_MODE_FLAG) {
Set_QH(qh->qh_link_ptr, EHCI_QH_LINK_PTR_VALID);
Set_QH(qh->qh_ctrl, EHCI_QH_CTRL_ED_INACTIVATE);
}
/* Unpack the endpoint descriptor into a control field */
if (ph) {
if ((ehci_initialize_dummy(ehcip,
qh)) == USB_NO_RESOURCES) {
Set_QH(qh->qh_state, EHCI_QH_FREE);
return (NULL);
}
ehci_unpack_endpoint(ehcip, ph, qh);
Set_QH(qh->qh_curr_qtd, NULL);
Set_QH(qh->qh_alt_next_qtd,
EHCI_QH_ALT_NEXT_QTD_PTR_VALID);
/* Change QH's state Active */
Set_QH(qh->qh_state, EHCI_QH_ACTIVE);
} else {
Set_QH(qh->qh_status, EHCI_QH_STS_HALTED);
/* Change QH's state Static */
Set_QH(qh->qh_state, EHCI_QH_STATIC);
}
ehci_print_qh(ehcip, qh);
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(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
ehci_qh_t *qh)
{
usb_ep_descr_t *endpoint = &ph->p_ep;
uint_t maxpacketsize, addr, xactions;
uint_t ctrl = 0, status = 0, split_ctrl = 0;
usb_port_status_t usb_port_status;
usba_device_t *usba_device = ph->p_usba_device;
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_unpack_endpoint:");
mutex_enter(&usba_device->usb_mutex);
ctrl = usba_device->usb_addr;
usb_port_status = usba_device->usb_port_status;
mutex_exit(&usba_device->usb_mutex);
addr = endpoint->bEndpointAddress;
/* Assign the endpoint's address */
ctrl |= ((addr & USB_EP_NUM_MASK) << EHCI_QH_CTRL_ED_NUMBER_SHIFT);
/* Assign the speed */
switch (usb_port_status) {
case USBA_LOW_SPEED_DEV:
ctrl |= EHCI_QH_CTRL_ED_LOW_SPEED;
break;
case USBA_FULL_SPEED_DEV:
ctrl |= EHCI_QH_CTRL_ED_FULL_SPEED;
break;
case USBA_HIGH_SPEED_DEV:
ctrl |= EHCI_QH_CTRL_ED_HIGH_SPEED;
break;
}
switch (endpoint->bmAttributes & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
/* Assign data toggle information */
ctrl |= EHCI_QH_CTRL_DATA_TOGGLE;
if (usb_port_status != USBA_HIGH_SPEED_DEV) {
ctrl |= EHCI_QH_CTRL_CONTROL_ED_FLAG;
}
/* FALLTHRU */
case USB_EP_ATTR_BULK:
/* Maximum nak counter */
ctrl |= EHCI_QH_CTRL_MAX_NC;
if (usb_port_status == USBA_HIGH_SPEED_DEV) {
/*
* Perform ping before executing control
* and bulk transactions.
*/
status = EHCI_QH_STS_DO_PING;
}
break;
case USB_EP_ATTR_INTR:
/* Set start split mask */
split_ctrl = (pp->pp_smask & EHCI_QH_SPLIT_CTRL_INTR_MASK);
/*
* Set complete split mask for low/full speed
* usb devices.
*/
if (usb_port_status != USBA_HIGH_SPEED_DEV) {
split_ctrl |= ((pp->pp_cmask <<
EHCI_QH_SPLIT_CTRL_COMP_SHIFT) &
EHCI_QH_SPLIT_CTRL_COMP_MASK);
}
break;
}
/* Get the max transactions per microframe */
xactions = (endpoint->wMaxPacketSize &
USB_EP_MAX_XACTS_MASK) >> USB_EP_MAX_XACTS_SHIFT;
switch (xactions) {
case 0:
split_ctrl |= EHCI_QH_SPLIT_CTRL_1_XACTS;
break;
case 1:
split_ctrl |= EHCI_QH_SPLIT_CTRL_2_XACTS;
break;
case 2:
split_ctrl |= EHCI_QH_SPLIT_CTRL_3_XACTS;
break;
default:
split_ctrl |= EHCI_QH_SPLIT_CTRL_1_XACTS;
break;
}
/*
* For low/full speed devices, program high speed hub
* address and port number.
*/
if (usb_port_status != USBA_HIGH_SPEED_DEV) {
mutex_enter(&usba_device->usb_mutex);
split_ctrl |= ((usba_device->usb_hs_hub_addr
<< EHCI_QH_SPLIT_CTRL_HUB_ADDR_SHIFT) &
EHCI_QH_SPLIT_CTRL_HUB_ADDR);
split_ctrl |= ((usba_device->usb_hs_hub_port
<< EHCI_QH_SPLIT_CTRL_HUB_PORT_SHIFT) &
EHCI_QH_SPLIT_CTRL_HUB_PORT);
mutex_exit(&usba_device->usb_mutex);
/* Set start split transaction state */
status = EHCI_QH_STS_DO_START_SPLIT;
}
/* Assign endpoint's maxpacketsize */
maxpacketsize = endpoint->wMaxPacketSize & USB_EP_MAX_PKTSZ_MASK;
maxpacketsize = maxpacketsize << EHCI_QH_CTRL_MAXPKTSZ_SHIFT;
ctrl |= (maxpacketsize & EHCI_QH_CTRL_MAXPKTSZ);
Set_QH(qh->qh_ctrl, ctrl);
Set_QH(qh->qh_split_ctrl, split_ctrl);
Set_QH(qh->qh_status, status);
}
/*
* ehci_insert_qh:
*
* Add the Endpoint Descriptor (QH) into the Host Controller's
* (HC) appropriate endpoint list.
*/
void
ehci_insert_qh(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_insert_qh: qh=0x%p", (void *)pp->pp_qh);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
switch (ph->p_ep.bmAttributes & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
ehci_insert_async_qh(ehcip, pp);
ehcip->ehci_open_async_count++;
break;
case USB_EP_ATTR_INTR:
ehci_insert_intr_qh(ehcip, pp);
ehcip->ehci_open_periodic_count++;
break;
case USB_EP_ATTR_ISOCH:
/* ISOCH does not use QH, don't do anything but update count */
ehcip->ehci_open_periodic_count++;
break;
}
ehci_toggle_scheduler(ehcip);
}
/*
* ehci_insert_async_qh:
*
* Insert a control/bulk endpoint into the Host Controller's (HC)
* Asynchronous schedule endpoint list.
*/
static void
ehci_insert_async_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
ehci_qh_t *qh = pp->pp_qh;
ehci_qh_t *async_head_qh;
ehci_qh_t *next_qh;
uintptr_t qh_addr;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_insert_async_qh:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Make sure this QH is not already in the list */
ASSERT((Get_QH(qh->qh_prev) & EHCI_QH_LINK_PTR) == NULL);
qh_addr = ehci_qh_cpu_to_iommu(ehcip, qh);
/* Obtain a ptr to the head of the Async schedule list */
async_head_qh = ehcip->ehci_head_of_async_sched_list;
if (async_head_qh == NULL) {
/* Set this QH to be the "head" of the circular list */
Set_QH(qh->qh_ctrl,
(Get_QH(qh->qh_ctrl) | EHCI_QH_CTRL_RECLAIM_HEAD));
/* Set new QH's link and previous pointer to itself */
Set_QH(qh->qh_link_ptr, qh_addr | EHCI_QH_LINK_REF_QH);
Set_QH(qh->qh_prev, qh_addr);
ehcip->ehci_head_of_async_sched_list = qh;
/* Set the head ptr to the new endpoint */
Set_OpReg(ehci_async_list_addr, qh_addr);
/*
* 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.
*/
if ((ehcip->ehci_vendor_id == PCI_VENDOR_ULi_M1575) &&
(ehcip->ehci_device_id == PCI_DEVICE_ULi_M1575) &&
(qh_addr != Get_OpReg(ehci_async_list_addr))) {
int retry = 0;
Set_OpRegRetry(ehci_async_list_addr, qh_addr, retry);
if (retry >= EHCI_MAX_RETRY)
cmn_err(CE_PANIC, "ehci_insert_async_qh:"
" ASYNCLISTADDR write failed.");
USB_DPRINTF_L2(PRINT_MASK_ATTA, ehcip->ehci_log_hdl,
"ehci_insert_async_qh: ASYNCLISTADDR "
"write failed, retry=%d", retry);
}
} else {
ASSERT(Get_QH(async_head_qh->qh_ctrl) &
EHCI_QH_CTRL_RECLAIM_HEAD);
/* Ensure this QH's "H" bit is not set */
Set_QH(qh->qh_ctrl,
(Get_QH(qh->qh_ctrl) & ~EHCI_QH_CTRL_RECLAIM_HEAD));
next_qh = ehci_qh_iommu_to_cpu(ehcip,
Get_QH(async_head_qh->qh_link_ptr) & EHCI_QH_LINK_PTR);
/* Set new QH's link and previous pointers */
Set_QH(qh->qh_link_ptr,
Get_QH(async_head_qh->qh_link_ptr) | EHCI_QH_LINK_REF_QH);
Set_QH(qh->qh_prev, ehci_qh_cpu_to_iommu(ehcip, async_head_qh));
/* Set next QH's prev pointer */
Set_QH(next_qh->qh_prev, ehci_qh_cpu_to_iommu(ehcip, qh));
/* Set QH Head's link pointer points to new QH */
Set_QH(async_head_qh->qh_link_ptr,
qh_addr | EHCI_QH_LINK_REF_QH);
}
}
/*
* ehci_insert_intr_qh:
*
* Insert a interrupt endpoint into the Host Controller's (HC) interrupt
* lattice tree.
*/
static void
ehci_insert_intr_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
ehci_qh_t *qh = pp->pp_qh;
ehci_qh_t *next_lattice_qh, *lattice_qh;
uint_t hnode;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_insert_intr_qh:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Make sure this QH is not already in the list */
ASSERT((Get_QH(qh->qh_prev) & EHCI_QH_LINK_PTR) == NULL);
/*
* The appropriate high speed node was found
* during the opening of the pipe.
*/
hnode = pp->pp_pnode;
/* Find the lattice endpoint */
lattice_qh = &ehcip->ehci_qh_pool_addr[hnode];
/* Find the next lattice endpoint */
next_lattice_qh = ehci_qh_iommu_to_cpu(
ehcip, (Get_QH(lattice_qh->qh_link_ptr) & EHCI_QH_LINK_PTR));
/* Update the previous pointer */
Set_QH(qh->qh_prev, ehci_qh_cpu_to_iommu(ehcip, lattice_qh));
/* Check next_lattice_qh value */
if (next_lattice_qh) {
/* Update this qh to point to the next one in the lattice */
Set_QH(qh->qh_link_ptr, Get_QH(lattice_qh->qh_link_ptr));
/* Update the previous pointer of qh->qh_link_ptr */
if (Get_QH(next_lattice_qh->qh_state) != EHCI_QH_STATIC) {
Set_QH(next_lattice_qh->qh_prev,
ehci_qh_cpu_to_iommu(ehcip, qh));
}
} else {
/* Update qh's link pointer to terminate periodic list */
Set_QH(qh->qh_link_ptr,
(Get_QH(lattice_qh->qh_link_ptr) | EHCI_QH_LINK_PTR_VALID));
}
/* Insert this endpoint into the lattice */
Set_QH(lattice_qh->qh_link_ptr,
(ehci_qh_cpu_to_iommu(ehcip, qh) | EHCI_QH_LINK_REF_QH));
}
/*
* 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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
halt_bit_t action)
{
ehci_qh_t *qh = pp->pp_qh;
uint_t smask, eps, split_intr_qh;
uint_t status;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_modify_qh_status_bit: action=0x%x qh=0x%p",
action, (void *)qh);
ehci_print_qh(ehcip, qh);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* If this pipe is in the middle of halting don't allow another
* thread to come in and modify the same pipe.
*/
while (pp->pp_halt_state & EHCI_HALT_STATE_HALTING) {
cv_wait(&pp->pp_halt_cmpl_cv,
&ehcip->ehci_int_mutex);
}
/* Sync the QH QTD pool to get up to date information */
Sync_QH_QTD_Pool(ehcip);
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.
*/
Set_QH(qh->qh_ctrl,
(Get_QH(qh->qh_ctrl) & ~EHCI_QH_CTRL_ED_INACTIVATE));
Set_QH(qh->qh_status,
Get_QH(qh->qh_status) & ~(EHCI_QH_STS_XACT_STATUS));
goto success;
}
/* Halt the the QH, but first check to see if it is already halted */
status = Get_QH(qh->qh_status);
if (!(status & EHCI_QH_STS_HALTED)) {
/* Indicate that this pipe is in the middle of halting. */
pp->pp_halt_state |= EHCI_HALT_STATE_HALTING;
/*
* Find out if this is an full/low speed interrupt endpoint.
* 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 .
*/
smask = Get_QH(qh->qh_split_ctrl) &
EHCI_QH_SPLIT_CTRL_INTR_MASK;
eps = Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_ED_SPEED;
split_intr_qh = ((smask != 0) &&
(eps != EHCI_QH_CTRL_ED_HIGH_SPEED));
if (eps == EHCI_QH_CTRL_ED_HIGH_SPEED) {
ehci_halt_hs_qh(ehcip, pp, qh);
} else {
if (split_intr_qh) {
ehci_halt_fls_intr_qh(ehcip, qh);
} else {
ehci_halt_fls_ctrl_and_bulk_qh(ehcip, pp, qh);
}
}
/* Indicate that this pipe is not in the middle of halting. */
pp->pp_halt_state &= ~EHCI_HALT_STATE_HALTING;
}
/* Sync the QH QTD pool again to get the most up to date information */
Sync_QH_QTD_Pool(ehcip);
ehci_print_qh(ehcip, qh);
status = Get_QH(qh->qh_status);
if (!(status & EHCI_QH_STS_HALTED)) {
USB_DPRINTF_L1(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_modify_qh_status_bit: Failed to halt qh=0x%p",
(void *)qh);
ehci_print_qh(ehcip, qh);
/* Set host controller soft state to error */
ehcip->ehci_hc_soft_state = EHCI_CTLR_ERROR_STATE;
ASSERT(status & EHCI_QH_STS_HALTED);
}
success:
/* Wake up threads waiting for this pipe to be halted. */
cv_signal(&pp->pp_halt_cmpl_cv);
}
/*
* ehci_halt_hs_qh:
*
* Halts all types of HIGH SPEED QHs.
*/
static void
ehci_halt_hs_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_qh_t *qh)
{
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_halt_hs_qh:");
/* Remove this qh from the HCD's view, but do not reclaim it */
ehci_remove_qh(ehcip, pp, B_FALSE);
/*
* 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 */
Sync_QH_QTD_Pool(ehcip);
/* Modify the status bit and halt this QH. */
Set_QH(qh->qh_status,
((Get_QH(qh->qh_status) &
~(EHCI_QH_STS_ACTIVE)) | EHCI_QH_STS_HALTED));
/* Insert this QH back into the HCD's view */
ehci_insert_qh(ehcip, ph);
}
/*
* ehci_halt_fls_ctrl_and_bulk_qh:
*
* Halts FULL/LOW Ctrl and Bulk QHs only.
*/
static void
ehci_halt_fls_ctrl_and_bulk_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_qh_t *qh)
{
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
uint_t status, split_status, bytes_left;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_halt_fls_ctrl_and_bulk_qh:");
/* Remove this qh from the HCD's view, but do not reclaim it */
ehci_remove_qh(ehcip, pp, B_FALSE);
/*
* 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 */
Sync_QH_QTD_Pool(ehcip);
/* Modify the status bit and halt this QH. */
Set_QH(qh->qh_status,
((Get_QH(qh->qh_status) &
~(EHCI_QH_STS_ACTIVE)) | EHCI_QH_STS_HALTED));
/* Check to see if the QH was in the middle of a transaction */
status = Get_QH(qh->qh_status);
split_status = status & EHCI_QH_STS_SPLIT_XSTATE;
bytes_left = status & EHCI_QH_STS_BYTES_TO_XFER;
if ((split_status == EHCI_QH_STS_DO_COMPLETE_SPLIT) &&
(bytes_left != 0)) {
/* send ClearTTBuffer to this device's parent 2.0 hub */
ehci_clear_tt_buffer(ehcip, ph, qh);
}
/* Insert this QH back into the HCD's view */
ehci_insert_qh(ehcip, ph);
}
/*
* ehci_clear_tt_buffer
*
* This function will sent a Clear_TT_Buffer request to the pipe's
* parent 2.0 hub.
*/
static void
ehci_clear_tt_buffer(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
ehci_qh_t *qh)
{
usba_device_t *usba_device;
usba_device_t *hub_usba_device;
usb_pipe_handle_t hub_def_ph;
usb_ep_descr_t *eptd;
uchar_t attributes;
uint16_t wValue;
usb_ctrl_setup_t setup;
usb_cr_t completion_reason;
usb_cb_flags_t cb_flags;
int retry;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_clear_tt_buffer: ");
/* Get some information about the current pipe */
usba_device = ph->p_usba_device;
eptd = &ph->p_ep;
attributes = eptd->bmAttributes & USB_EP_ATTR_MASK;
/*
* 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;
if ((eptd->bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
wValue |= 0x8000;
}
wValue |= attributes << 11;
wValue |= (Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_DEVICE_ADDRESS) << 4;
wValue |= (Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_ED_HIGH_SPEED) >>
EHCI_QH_CTRL_ED_NUMBER_SHIFT;
mutex_exit(&ehcip->ehci_int_mutex);
/* Manually fill in the request. */
setup.bmRequestType = EHCI_CLEAR_TT_BUFFER_REQTYPE;
setup.bRequest = EHCI_CLEAR_TT_BUFFER_BREQ;
setup.wValue = wValue;
setup.wIndex = 1;
setup.wLength = 0;
setup.attrs = USB_ATTRS_NONE;
/* Get the usba_device of the parent 2.0 hub. */
mutex_enter(&usba_device->usb_mutex);
hub_usba_device = usba_device->usb_hs_hub_usba_dev;
mutex_exit(&usba_device->usb_mutex);
/* Get the default ctrl pipe for the parent 2.0 hub */
mutex_enter(&hub_usba_device->usb_mutex);
hub_def_ph = (usb_pipe_handle_t)&hub_usba_device->usb_ph_list[0];
mutex_exit(&hub_usba_device->usb_mutex);
for (retry = 0; retry < 3; retry++) {
/* sync send the request to the default pipe */
if (usb_pipe_ctrl_xfer_wait(
hub_def_ph,
&setup,
NULL,
&completion_reason, &cb_flags, 0) == USB_SUCCESS) {
break;
}
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_clear_tt_buffer: Failed to clear tt buffer,"
"retry = %d, cr = %d, cb_flags = 0x%x\n",
retry, completion_reason, cb_flags);
}
if (retry >= 3) {
char *path = kmem_alloc(MAXPATHLEN, KM_SLEEP);
dev_info_t *dip = hub_usba_device->usb_dip;
/*
* Ask the user to hotplug the 2.0 hub, to make sure that
* all the buffer is in sync since this command has failed.
*/
USB_DPRINTF_L0(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"Error recovery failure: Please hotplug the 2.0 hub at"
"%s", ddi_pathname(dip, path));
kmem_free(path, MAXPATHLEN);
}
mutex_enter(&ehcip->ehci_int_mutex);
}
/*
* ehci_halt_fls_intr_qh:
*
* Halts FULL/LOW speed Intr QHs.
*/
static void
ehci_halt_fls_intr_qh(
ehci_state_t *ehcip,
ehci_qh_t *qh)
{
usb_frame_number_t starting_frame;
usb_frame_number_t frames_past;
uint_t status, i;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_halt_fls_intr_qh:");
/*
* Ask the HC to deactivate the QH in a
* full/low periodic QH.
*/
Set_QH(qh->qh_ctrl,
(Get_QH(qh->qh_ctrl) | EHCI_QH_CTRL_ED_INACTIVATE));
starting_frame = ehci_get_current_frame_number(ehcip);
/*
* Wait at least EHCI_NUM_INTR_QH_LISTS+2 frame or until
* the QH has been halted.
*/
Sync_QH_QTD_Pool(ehcip);
frames_past = 0;
status = Get_QH(qh->qh_status) & EHCI_QTD_CTRL_ACTIVE_XACT;
while ((frames_past <= (EHCI_NUM_INTR_QH_LISTS + 2)) &&
(status != 0)) {
(void) ehci_wait_for_sof(ehcip);
Sync_QH_QTD_Pool(ehcip);
status = Get_QH(qh->qh_status) & EHCI_QTD_CTRL_ACTIVE_XACT;
frames_past = ehci_get_current_frame_number(ehcip) -
starting_frame;
}
/* Modify the status bit and halt this QH. */
Sync_QH_QTD_Pool(ehcip);
status = Get_QH(qh->qh_status);
for (i = 0; i < EHCI_NUM_INTR_QH_LISTS; i++) {
Set_QH(qh->qh_status,
((Get_QH(qh->qh_status) &
~(EHCI_QH_STS_ACTIVE)) | EHCI_QH_STS_HALTED));
Sync_QH_QTD_Pool(ehcip);
(void) ehci_wait_for_sof(ehcip);
Sync_QH_QTD_Pool(ehcip);
if (Get_QH(qh->qh_status) & EHCI_QH_STS_HALTED) {
break;
}
}
Sync_QH_QTD_Pool(ehcip);
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"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) -
starting_frame), status, Get_QH(qh->qh_status));
}
/*
* ehci_remove_qh:
*
* Remove the Endpoint Descriptor (QH) from the Host Controller's appropriate
* endpoint list.
*/
void
ehci_remove_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
boolean_t reclaim)
{
uchar_t attributes;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_remove_qh: qh=0x%p", (void *)pp->pp_qh);
attributes = pp->pp_pipe_handle->p_ep.bmAttributes & USB_EP_ATTR_MASK;
switch (attributes) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_BULK:
ehci_remove_async_qh(ehcip, pp, reclaim);
ehcip->ehci_open_async_count--;
break;
case USB_EP_ATTR_INTR:
ehci_remove_intr_qh(ehcip, pp, reclaim);
ehcip->ehci_open_periodic_count--;
break;
case USB_EP_ATTR_ISOCH:
/* ISOCH does not use QH, don't do anything but update count */
ehcip->ehci_open_periodic_count--;
break;
}
ehci_toggle_scheduler(ehcip);
}
/*
* ehci_remove_async_qh:
*
* Remove a control/bulk endpoint into the Host Controller's (HC)
* Asynchronous schedule endpoint list.
*/
static void
ehci_remove_async_qh(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
boolean_t reclaim)
{
ehci_qh_t *qh = pp->pp_qh; /* qh to be removed */
ehci_qh_t *prev_qh, *next_qh;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_remove_async_qh:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
prev_qh = ehci_qh_iommu_to_cpu(ehcip,
Get_QH(qh->qh_prev) & EHCI_QH_LINK_PTR);
next_qh = ehci_qh_iommu_to_cpu(ehcip,
Get_QH(qh->qh_link_ptr) & EHCI_QH_LINK_PTR);
/* Make sure this QH is in the list */
ASSERT(prev_qh != NULL);
/*
* If next QH and current QH are the same, then this is the last
* QH on the Asynchronous Schedule list.
*/
if (qh == next_qh) {
ASSERT(Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_RECLAIM_HEAD);
/*
* Null our pointer to the async sched list, but do not
* touch the host controller's list_addr.
*/
ehcip->ehci_head_of_async_sched_list = NULL;
ASSERT(ehcip->ehci_open_async_count == 1);
} else {
/* If this QH is the HEAD then find another one to replace it */
if (ehcip->ehci_head_of_async_sched_list == qh) {
ASSERT(Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_RECLAIM_HEAD);
ehcip->ehci_head_of_async_sched_list = next_qh;
Set_QH(next_qh->qh_ctrl,
Get_QH(next_qh->qh_ctrl) |
EHCI_QH_CTRL_RECLAIM_HEAD);
}
Set_QH(prev_qh->qh_link_ptr, Get_QH(qh->qh_link_ptr));
Set_QH(next_qh->qh_prev, Get_QH(qh->qh_prev));
}
/* qh_prev to indicate it is no longer in the circular list */
Set_QH(qh->qh_prev, NULL);
if (reclaim) {
ehci_insert_qh_on_reclaim_list(ehcip, pp);
}
}
/*
* 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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
boolean_t reclaim)
{
ehci_qh_t *qh = pp->pp_qh; /* qh to be removed */
ehci_qh_t *prev_qh, *next_qh;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_remove_intr_qh:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
prev_qh = ehci_qh_iommu_to_cpu(ehcip, Get_QH(qh->qh_prev));
next_qh = ehci_qh_iommu_to_cpu(ehcip,
Get_QH(qh->qh_link_ptr) & EHCI_QH_LINK_PTR);
/* Make sure this QH is in the list */
ASSERT(prev_qh != NULL);
if (next_qh) {
/* Update previous qh's link pointer */
Set_QH(prev_qh->qh_link_ptr, Get_QH(qh->qh_link_ptr));
if (Get_QH(next_qh->qh_state) != EHCI_QH_STATIC) {
/* Set the previous pointer of the next one */
Set_QH(next_qh->qh_prev, Get_QH(qh->qh_prev));
}
} else {
/* Update previous qh's link pointer */
Set_QH(prev_qh->qh_link_ptr,
(Get_QH(qh->qh_link_ptr) | EHCI_QH_LINK_PTR_VALID));
}
/* qh_prev to indicate it is no longer in the circular list */
Set_QH(qh->qh_prev, NULL);
if (reclaim) {
ehci_insert_qh_on_reclaim_list(ehcip, pp);
}
}
/*
* ehci_insert_qh_on_reclaim_list:
*
* Insert Endpoint onto the reclaim list
*/
static void
ehci_insert_qh_on_reclaim_list(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
ehci_qh_t *qh = pp->pp_qh; /* qh to be removed */
ehci_qh_t *next_qh, *prev_qh;
usb_frame_number_t frame_number;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* Read current usb frame number and add appropriate number of
* usb frames needs to wait before reclaiming current endpoint.
*/
frame_number =
ehci_get_current_frame_number(ehcip) + MAX_SOF_WAIT_COUNT;
/* Store 32-bit ID */
Set_QH(qh->qh_reclaim_frame,
((uint32_t)(EHCI_GET_ID((void *)(uintptr_t)frame_number))));
/* Insert the endpoint onto the reclamation list */
if (ehcip->ehci_reclaim_list) {
next_qh = ehcip->ehci_reclaim_list;
while (next_qh) {
prev_qh = next_qh;
next_qh = ehci_qh_iommu_to_cpu(ehcip,
Get_QH(next_qh->qh_reclaim_next));
}
Set_QH(prev_qh->qh_reclaim_next,
ehci_qh_cpu_to_iommu(ehcip, qh));
} else {
ehcip->ehci_reclaim_list = qh;
}
ASSERT(Get_QH(qh->qh_reclaim_next) == NULL);
}
/*
* 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(
ehci_state_t *ehcip,
ehci_qh_t *old_qh)
{
ehci_qtd_t *first_dummy_qtd, *second_dummy_qtd;
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_deallocate_qh:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
first_dummy_qtd = ehci_qtd_iommu_to_cpu(ehcip,
(Get_QH(old_qh->qh_next_qtd) & EHCI_QH_NEXT_QTD_PTR));
if (first_dummy_qtd) {
ASSERT(Get_QTD(first_dummy_qtd->qtd_state) == EHCI_QTD_DUMMY);
second_dummy_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(first_dummy_qtd->qtd_next_qtd));
if (second_dummy_qtd) {
ASSERT(Get_QTD(second_dummy_qtd->qtd_state) ==
EHCI_QTD_DUMMY);
ehci_deallocate_qtd(ehcip, second_dummy_qtd);
}
ehci_deallocate_qtd(ehcip, first_dummy_qtd);
}
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_deallocate_qh: Deallocated 0x%p", (void *)old_qh);
Set_QH(old_qh->qh_state, EHCI_QH_FREE);
}
/*
* 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.
*/
uint32_t
ehci_qh_cpu_to_iommu(
ehci_state_t *ehcip,
ehci_qh_t *addr)
{
uint32_t qh;
qh = (uint32_t)ehcip->ehci_qh_pool_cookie.dmac_address +
(uint32_t)((uintptr_t)addr - (uintptr_t)(ehcip->ehci_qh_pool_addr));
ASSERT(qh >= ehcip->ehci_qh_pool_cookie.dmac_address);
ASSERT(qh <= ehcip->ehci_qh_pool_cookie.dmac_address +
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.
*/
ehci_qh_t *
ehci_qh_iommu_to_cpu(
ehci_state_t *ehcip,
uintptr_t addr)
{
ehci_qh_t *qh;
if (addr == NULL) {
return (NULL);
}
qh = (ehci_qh_t *)((uintptr_t)
(addr - ehcip->ehci_qh_pool_cookie.dmac_address) +
(uintptr_t)ehcip->ehci_qh_pool_addr);
ASSERT(qh >= ehcip->ehci_qh_pool_addr);
ASSERT((uintptr_t)qh <= (uintptr_t)ehcip->ehci_qh_pool_addr +
(uintptr_t)(sizeof (ehci_qh_t) * ehci_qh_pool_size));
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
ehci_initialize_dummy(
ehci_state_t *ehcip,
ehci_qh_t *qh)
{
ehci_qtd_t *first_dummy_qtd, *second_dummy_qtd;
/* Allocate first dummy QTD */
first_dummy_qtd = ehci_allocate_qtd_from_pool(ehcip);
if (first_dummy_qtd == NULL) {
return (USB_NO_RESOURCES);
}
/* Allocate second dummy QTD */
second_dummy_qtd = ehci_allocate_qtd_from_pool(ehcip);
if (second_dummy_qtd == NULL) {
/* Deallocate first dummy QTD */
ehci_deallocate_qtd(ehcip, first_dummy_qtd);
return (USB_NO_RESOURCES);
}
/* Next QTD pointer of an QH point to this new dummy QTD */
Set_QH(qh->qh_next_qtd, ehci_qtd_cpu_to_iommu(ehcip,
first_dummy_qtd) & EHCI_QH_NEXT_QTD_PTR);
/* Set qh's dummy qtd field */
Set_QH(qh->qh_dummy_qtd, ehci_qtd_cpu_to_iommu(ehcip, first_dummy_qtd));
/* Set first_dummy's next qtd pointer */
Set_QTD(first_dummy_qtd->qtd_next_qtd,
ehci_qtd_cpu_to_iommu(ehcip, second_dummy_qtd));
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.
*/
ehci_trans_wrapper_t *
ehci_allocate_ctrl_resources(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
usb_ctrl_req_t *ctrl_reqp,
usb_flags_t usb_flags)
{
size_t qtd_count = 2;
size_t ctrl_buf_size;
ehci_trans_wrapper_t *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) {
ctrl_buf_size = EHCI_MAX_QTD_BUF_SIZE +
ctrl_reqp->ctrl_wLength;
} else {
ctrl_buf_size = SETUP_SIZE;
}
tw = ehci_allocate_tw_resources(ehcip, pp, ctrl_buf_size,
usb_flags, qtd_count);
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(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_ctrl_req_t *ctrl_reqp,
ehci_trans_wrapper_t *tw,
usb_flags_t usb_flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
uchar_t bmRequestType = ctrl_reqp->ctrl_bmRequestType;
uchar_t bRequest = ctrl_reqp->ctrl_bRequest;
uint16_t wValue = ctrl_reqp->ctrl_wValue;
uint16_t wIndex = ctrl_reqp->ctrl_wIndex;
uint16_t wLength = ctrl_reqp->ctrl_wLength;
mblk_t *data = ctrl_reqp->ctrl_data;
uint32_t ctrl = 0;
uint8_t setup_packet[8];
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_insert_ctrl_req:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* Save current control request pointer and timeout values
* in transfer wrapper.
*/
tw->tw_curr_xfer_reqp = (usb_opaque_t)ctrl_reqp;
tw->tw_timeout = ctrl_reqp->ctrl_timeout ?
ctrl_reqp->ctrl_timeout : EHCI_DEFAULT_XFER_TIMEOUT;
/*
* Initialize the callback and any callback data for when
* the qtd completes.
*/
tw->tw_handle_qtd = ehci_handle_ctrl_qtd;
tw->tw_handle_callback_value = NULL;
/*
* swap the setup bytes where necessary since we specified
* NEVERSWAP
*/
setup_packet[0] = bmRequestType;
setup_packet[1] = bRequest;
setup_packet[2] = wValue;
setup_packet[3] = wValue >> 8;
setup_packet[4] = wIndex;
setup_packet[5] = wIndex >> 8;
setup_packet[6] = wLength;
setup_packet[7] = wLength >> 8;
bcopy(setup_packet, tw->tw_buf, SETUP_SIZE);
Sync_IO_Buffer_for_device(tw->tw_dmahandle, SETUP_SIZE);
ctrl = (EHCI_QTD_CTRL_DATA_TOGGLE_0 | EHCI_QTD_CTRL_SETUP_PID);
/*
* 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.
*/
(void) ehci_insert_qtd(ehcip, ctrl, 0, SETUP_SIZE,
EHCI_CTRL_SETUP_PHASE, pp, tw);
USB_DPRINTF_L3(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"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) {
tw->tw_direction = EHCI_QTD_CTRL_IN_PID;
} else {
tw->tw_direction = EHCI_QTD_CTRL_OUT_PID;
/* Copy the data into the message */
bcopy(data->b_rptr, tw->tw_buf + EHCI_MAX_QTD_BUF_SIZE,
wLength);
Sync_IO_Buffer_for_device(tw->tw_dmahandle,
wLength + EHCI_MAX_QTD_BUF_SIZE);
}
ctrl = (EHCI_QTD_CTRL_DATA_TOGGLE_1 | tw->tw_direction);
/*
* 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.
*/
(void) ehci_insert_qtd(ehcip, ctrl, EHCI_MAX_QTD_BUF_SIZE,
tw->tw_length - EHCI_MAX_QTD_BUF_SIZE,
EHCI_CTRL_DATA_PHASE, pp, tw);
/*
* The direction of the STATUS QTD depends on
* the direction of the transfer.
*/
if (tw->tw_direction == EHCI_QTD_CTRL_IN_PID) {
ctrl = (EHCI_QTD_CTRL_DATA_TOGGLE_1|
EHCI_QTD_CTRL_OUT_PID |
EHCI_QTD_CTRL_INTR_ON_COMPLETE);
} else {
ctrl = (EHCI_QTD_CTRL_DATA_TOGGLE_1|
EHCI_QTD_CTRL_IN_PID |
EHCI_QTD_CTRL_INTR_ON_COMPLETE);
}
} else {
/*
* There is no data stage, then initiate
* status phase from the host.
*/
ctrl = (EHCI_QTD_CTRL_DATA_TOGGLE_1 |
EHCI_QTD_CTRL_IN_PID |
EHCI_QTD_CTRL_INTR_ON_COMPLETE);
}
(void) ehci_insert_qtd(ehcip, ctrl, 0, 0,
EHCI_CTRL_STATUS_PHASE, pp, tw);
/* Start the timer for this control transfer */
ehci_start_xfer_timer(ehcip, pp, tw);
}
/*
* 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.
*/
ehci_trans_wrapper_t *
ehci_allocate_bulk_resources(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
usb_bulk_req_t *bulk_reqp,
usb_flags_t usb_flags)
{
size_t qtd_count = 0;
ehci_trans_wrapper_t *tw;
/* Check the size of bulk request */
if (bulk_reqp->bulk_len > EHCI_MAX_BULK_XFER_SIZE) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_bulk_resources: Bulk request size 0x%x is "
"more than 0x%x", bulk_reqp->bulk_len,
EHCI_MAX_BULK_XFER_SIZE);
return (NULL);
}
/* Get the required bulk packet size */
qtd_count = bulk_reqp->bulk_len / EHCI_MAX_QTD_XFER_SIZE;
if (bulk_reqp->bulk_len % EHCI_MAX_QTD_XFER_SIZE ||
bulk_reqp->bulk_len == 0) {
qtd_count += 1;
}
tw = ehci_allocate_tw_resources(ehcip, pp, bulk_reqp->bulk_len,
usb_flags, qtd_count);
return (tw);
}
/*
* ehci_insert_bulk_req:
*
* Create a Transfer Descriptor (QTD) and a data buffer for a bulk
* endpoint.
*/
/* ARGSUSED */
void
ehci_insert_bulk_req(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_bulk_req_t *bulk_reqp,
ehci_trans_wrapper_t *tw,
usb_flags_t flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
uint_t bulk_pkt_size, count;
size_t residue = 0, len = 0;
uint32_t ctrl = 0;
int pipe_dir;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_insert_bulk_req: bulk_reqp = 0x%p flags = 0x%x",
(void *)bulk_reqp, flags);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Get the bulk pipe direction */
pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
/* Get the required bulk packet size */
bulk_pkt_size = min(bulk_reqp->bulk_len, EHCI_MAX_QTD_XFER_SIZE);
if (bulk_pkt_size) {
residue = tw->tw_length % bulk_pkt_size;
}
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_insert_bulk_req: bulk_pkt_size = %d", bulk_pkt_size);
/*
* Save current bulk request pointer and timeout values
* in transfer wrapper.
*/
tw->tw_curr_xfer_reqp = (usb_opaque_t)bulk_reqp;
tw->tw_timeout = bulk_reqp->bulk_timeout;
/*
* Initialize the callback and any callback
* data required when the qtd completes.
*/
tw->tw_handle_qtd = ehci_handle_bulk_qtd;
tw->tw_handle_callback_value = NULL;
tw->tw_direction = (pipe_dir == USB_EP_DIR_OUT) ?
EHCI_QTD_CTRL_OUT_PID : EHCI_QTD_CTRL_IN_PID;
if (tw->tw_direction == EHCI_QTD_CTRL_OUT_PID) {
if (bulk_reqp->bulk_len) {
ASSERT(bulk_reqp->bulk_data != NULL);
bcopy(bulk_reqp->bulk_data->b_rptr, tw->tw_buf,
bulk_reqp->bulk_len);
Sync_IO_Buffer_for_device(tw->tw_dmahandle,
bulk_reqp->bulk_len);
}
}
ctrl = tw->tw_direction;
/* Insert all the bulk QTDs */
for (count = 0; count < tw->tw_num_qtds; count++) {
/* Check for last qtd */
if (count == (tw->tw_num_qtds - 1)) {
ctrl |= EHCI_QTD_CTRL_INTR_ON_COMPLETE;
/* Check for inserting residue data */
if (residue) {
bulk_pkt_size = residue;
}
}
/* Insert the QTD onto the endpoint */
(void) ehci_insert_qtd(ehcip, ctrl, len, bulk_pkt_size,
0, pp, tw);
len = len + bulk_pkt_size;
}
/* Start the timer for this bulk transfer */
ehci_start_xfer_timer(ehcip, pp, tw);
}
/*
* ehci_start_periodic_pipe_polling:
*
* NOTE: This function is also called from POLLED MODE.
*/
int
ehci_start_periodic_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_opaque_t periodic_in_reqp,
usb_flags_t flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
int error = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_start_periodic_pipe_polling: ep%d",
ph->p_ep.bEndpointAddress & USB_EP_NUM_MASK);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* Check and handle start polling on root hub interrupt pipe.
*/
if ((ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) &&
((eptd->bmAttributes & USB_EP_ATTR_MASK) ==
USB_EP_ATTR_INTR)) {
error = ehci_handle_root_hub_pipe_start_intr_polling(ph,
(usb_intr_req_t *)periodic_in_reqp, flags);
return (error);
}
switch (pp->pp_state) {
case EHCI_PIPE_STATE_IDLE:
/* Save the Original client's Periodic IN request */
pp->pp_client_periodic_in_reqp = periodic_in_reqp;
/*
* This pipe is uninitialized or if a valid QTD is
* not found then insert a QTD on the interrupt IN
* endpoint.
*/
error = ehci_start_pipe_polling(ehcip, ph, flags);
if (error != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_INTR,
ehcip->ehci_log_hdl,
"ehci_start_periodic_pipe_polling: "
"Start polling failed");
pp->pp_client_periodic_in_reqp = NULL;
return (error);
}
USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"ehci_start_periodic_pipe_polling: PP = 0x%p", (void *)pp);
#ifdef DEBUG
switch (eptd->bmAttributes & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_INTR:
ASSERT((pp->pp_tw_head != NULL) &&
(pp->pp_tw_tail != NULL));
break;
case USB_EP_ATTR_ISOCH:
ASSERT((pp->pp_itw_head != NULL) &&
(pp->pp_itw_tail != NULL));
break;
}
#endif
break;
case EHCI_PIPE_STATE_ACTIVE:
USB_DPRINTF_L2(PRINT_MASK_INTR,
ehcip->ehci_log_hdl,
"ehci_start_periodic_pipe_polling: "
"Polling is already in progress");
error = USB_FAILURE;
break;
case EHCI_PIPE_STATE_ERROR:
USB_DPRINTF_L2(PRINT_MASK_INTR,
ehcip->ehci_log_hdl,
"ehci_start_periodic_pipe_polling: "
"Pipe is halted and perform reset"
"before restart polling");
error = USB_FAILURE;
break;
default:
USB_DPRINTF_L2(PRINT_MASK_INTR,
ehcip->ehci_log_hdl,
"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
ehci_start_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
int error = USB_FAILURE;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_start_pipe_polling:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* 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) {
ehci_set_periodic_pipe_polling(ehcip, ph);
}
ASSERT(pp->pp_max_periodic_req_cnt != 0);
switch (eptd->bmAttributes & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_INTR:
error = ehci_start_intr_polling(ehcip, ph, flags);
break;
case USB_EP_ATTR_ISOCH:
error = ehci_start_isoc_polling(ehcip, ph, flags);
break;
}
return (error);
}
static int
ehci_start_intr_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
ehci_trans_wrapper_t *tw_list, *tw;
int i, total_tws;
int error = USB_SUCCESS;
/* Allocate all the necessary resources for the IN transfer */
tw_list = NULL;
total_tws = pp->pp_max_periodic_req_cnt - pp->pp_cur_periodic_req_cnt;
for (i = 0; i < total_tws; i += 1) {
tw = ehci_allocate_intr_resources(ehcip, ph, NULL, flags);
if (tw == NULL) {
error = USB_NO_RESOURCES;
/* There are not enough resources, deallocate the TWs */
tw = tw_list;
while (tw != NULL) {
tw_list = tw->tw_next;
ehci_deallocate_intr_in_resource(
ehcip, pp, tw);
ehci_deallocate_tw(ehcip, pp, tw);
tw = tw_list;
}
return (error);
} else {
if (tw_list == NULL) {
tw_list = tw;
}
}
}
while (pp->pp_cur_periodic_req_cnt < pp->pp_max_periodic_req_cnt) {
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_start_pipe_polling: max = %d curr = %d tw = %p:",
pp->pp_max_periodic_req_cnt, pp->pp_cur_periodic_req_cnt,
(void *)tw_list);
tw = tw_list;
tw_list = tw->tw_next;
ehci_insert_intr_req(ehcip, pp, tw, flags);
pp->pp_cur_periodic_req_cnt++;
}
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(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *endpoint = &ph->p_ep;
uchar_t ep_attr = endpoint->bmAttributes;
uint_t interval;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_set_periodic_pipe_polling:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
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.
*/
if (((ep_attr & USB_EP_ATTR_MASK) == USB_EP_ATTR_INTR) &&
(pp->pp_client_periodic_in_reqp)) {
usb_intr_req_t *intr_reqp = (usb_intr_req_t *)
pp->pp_client_periodic_in_reqp;
if (intr_reqp->intr_attributes &
USB_ATTRS_ONE_XFER) {
pp->pp_max_periodic_req_cnt = EHCI_INTR_XMS_REQS;
return;
}
}
mutex_enter(&ph->p_usba_device->usb_mutex);
/*
* 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.
*/
interval = ehci_adjust_polling_interval(ehcip, endpoint,
ph->p_usba_device->usb_port_status);
mutex_exit(&ph->p_usba_device->usb_mutex);
switch (interval) {
case EHCI_INTR_1MS_POLL:
pp->pp_max_periodic_req_cnt = EHCI_INTR_1MS_REQS;
break;
case EHCI_INTR_2MS_POLL:
pp->pp_max_periodic_req_cnt = EHCI_INTR_2MS_REQS;
break;
default:
pp->pp_max_periodic_req_cnt = EHCI_INTR_XMS_REQS;
break;
}
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_set_periodic_pipe_polling: Max periodic requests = %d",
pp->pp_max_periodic_req_cnt);
}
/*
* 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.
*/
ehci_trans_wrapper_t *
ehci_allocate_intr_resources(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_intr_req_t *intr_reqp,
usb_flags_t flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
int pipe_dir;
size_t qtd_count = 1;
size_t tw_length;
ehci_trans_wrapper_t *tw;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_intr_resources:");
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
/* Get the length of interrupt transfer & alloc data */
if (intr_reqp) {
tw_length = intr_reqp->intr_len;
} else {
ASSERT(pipe_dir == USB_EP_DIR_IN);
tw_length = (pp->pp_client_periodic_in_reqp) ?
(((usb_intr_req_t *)pp->
pp_client_periodic_in_reqp)->intr_len) :
ph->p_ep.wMaxPacketSize;
}
/* Check the size of interrupt request */
if (tw_length > EHCI_MAX_QTD_XFER_SIZE) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_intr_resources: Intr request size 0x%lx is "
"more than 0x%x", tw_length, EHCI_MAX_QTD_XFER_SIZE);
return (NULL);
}
if ((tw = ehci_allocate_tw_resources(ehcip, pp, tw_length, flags,
qtd_count)) == NULL) {
return (NULL);
}
if (pipe_dir == USB_EP_DIR_IN) {
if (ehci_allocate_intr_in_resource(ehcip, pp, tw, flags) !=
USB_SUCCESS) {
ehci_deallocate_tw(ehcip, pp, tw);
}
tw->tw_direction = EHCI_QTD_CTRL_IN_PID;
} else {
if (tw_length) {
ASSERT(intr_reqp->intr_data != NULL);
/* Copy the data into the buffer */
bcopy(intr_reqp->intr_data->b_rptr, tw->tw_buf,
intr_reqp->intr_len);
Sync_IO_Buffer_for_device(tw->tw_dmahandle,
intr_reqp->intr_len);
}
tw->tw_curr_xfer_reqp = (usb_opaque_t)intr_reqp;
tw->tw_direction = EHCI_QTD_CTRL_OUT_PID;
}
if (intr_reqp) {
tw->tw_timeout = intr_reqp->intr_timeout;
}
/*
* Initialize the callback and any callback
* data required when the qtd completes.
*/
tw->tw_handle_qtd = ehci_handle_intr_qtd;
tw->tw_handle_callback_value = NULL;
return (tw);
}
/*
* ehci_insert_intr_req:
*
* Insert an Interrupt request into the Host Controller's periodic list.
*/
/* ARGSUSED */
void
ehci_insert_intr_req(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw,
usb_flags_t flags)
{
uint_t ctrl = 0;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
ASSERT(tw->tw_curr_xfer_reqp != NULL);
ctrl = (tw->tw_direction | EHCI_QTD_CTRL_INTR_ON_COMPLETE);
/* Insert another interrupt QTD */
(void) ehci_insert_qtd(ehcip, ctrl, 0, tw->tw_length, 0, pp, tw);
/* Start the timer for this Interrupt transfer */
ehci_start_xfer_timer(ehcip, pp, tw);
}
/*
* ehci_stop_periodic_pipe_polling:
*/
/* ARGSUSED */
int
ehci_stop_periodic_pipe_polling(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_stop_periodic_pipe_polling: Flags = 0x%x", flags);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* Check and handle stop polling on root hub interrupt pipe.
*/
if ((ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) &&
((eptd->bmAttributes & USB_EP_ATTR_MASK) ==
USB_EP_ATTR_INTR)) {
ehci_handle_root_hub_pipe_stop_intr_polling(ph, flags);
return (USB_SUCCESS);
}
if (pp->pp_state != EHCI_PIPE_STATE_ACTIVE) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_stop_periodic_pipe_polling: "
"Polling already stopped");
return (USB_SUCCESS);
}
/* Set pipe state to pipe stop polling */
pp->pp_state = EHCI_PIPE_STATE_STOP_POLLING;
ehci_pipe_cleanup(ehcip, ph);
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
ehci_insert_qtd(
ehci_state_t *ehcip,
uint32_t qtd_ctrl,
size_t qtd_dma_offs,
size_t qtd_length,
uint32_t qtd_ctrl_phase,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw)
{
ehci_qtd_t *curr_dummy_qtd, *next_dummy_qtd;
ehci_qtd_t *new_dummy_qtd;
ehci_qh_t *qh = pp->pp_qh;
int error = USB_SUCCESS;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Allocate new dummy QTD */
new_dummy_qtd = tw->tw_qtd_free_list;
ASSERT(new_dummy_qtd != NULL);
tw->tw_qtd_free_list = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(new_dummy_qtd->qtd_tw_next_qtd));
Set_QTD(new_dummy_qtd->qtd_tw_next_qtd, NULL);
/* Get the current and next dummy QTDs */
curr_dummy_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QH(qh->qh_dummy_qtd));
next_dummy_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(curr_dummy_qtd->qtd_next_qtd));
/* Update QH's dummy qtd field */
Set_QH(qh->qh_dummy_qtd, ehci_qtd_cpu_to_iommu(ehcip, next_dummy_qtd));
/* Update next dummy's next qtd pointer */
Set_QTD(next_dummy_qtd->qtd_next_qtd,
ehci_qtd_cpu_to_iommu(ehcip, new_dummy_qtd));
/*
* Fill in the current dummy qtd and
* add the new dummy to the end.
*/
ehci_fill_in_qtd(ehcip, curr_dummy_qtd, qtd_ctrl,
qtd_dma_offs, qtd_length, qtd_ctrl_phase, pp, tw);
/* Insert this qtd onto the tw */
ehci_insert_qtd_on_tw(ehcip, tw, curr_dummy_qtd);
/*
* Insert this qtd onto active qtd list.
* Don't insert polled mode qtd here.
*/
if (pp->pp_flag != EHCI_POLLED_MODE_FLAG) {
/* Insert this qtd onto active qtd list */
ehci_insert_qtd_into_active_qtd_list(ehcip, curr_dummy_qtd);
}
/* Print qh and qtd */
ehci_print_qh(ehcip, qh);
ehci_print_qtd(ehcip, curr_dummy_qtd);
return (error);
}
/*
* ehci_allocate_qtd_from_pool:
*
* Allocate a Transfer Descriptor (QTD) from the QTD buffer pool.
*/
static ehci_qtd_t *
ehci_allocate_qtd_from_pool(ehci_state_t *ehcip)
{
int i, ctrl;
ehci_qtd_t *qtd;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* Search for a blank Transfer Descriptor (QTD)
* in the QTD buffer pool.
*/
for (i = 0; i < ehci_qtd_pool_size; i ++) {
ctrl = Get_QTD(ehcip->ehci_qtd_pool_addr[i].qtd_state);
if (ctrl == EHCI_QTD_FREE) {
break;
}
}
if (i >= ehci_qtd_pool_size) {
USB_DPRINTF_L2(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_allocate_qtd_from_pool: QTD exhausted");
return (NULL);
}
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_allocate_qtd_from_pool: Allocated %d", i);
/* Create a new dummy for the end of the QTD list */
qtd = &ehcip->ehci_qtd_pool_addr[i];
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_qtd_from_pool: qtd 0x%p", (void *)qtd);
/* Mark the newly allocated QTD as a dummy */
Set_QTD(qtd->qtd_state, EHCI_QTD_DUMMY);
/* Mark the status of this new QTD to halted state */
Set_QTD(qtd->qtd_ctrl, EHCI_QTD_CTRL_HALTED_XACT);
/* Disable dummy QTD's next and alternate next pointers */
Set_QTD(qtd->qtd_next_qtd, EHCI_QTD_NEXT_QTD_PTR_VALID);
Set_QTD(qtd->qtd_alt_next_qtd, EHCI_QTD_ALT_NEXT_QTD_PTR_VALID);
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
ehci_fill_in_qtd(
ehci_state_t *ehcip,
ehci_qtd_t *qtd,
uint32_t qtd_ctrl,
size_t qtd_dma_offs,
size_t qtd_length,
uint32_t qtd_ctrl_phase,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw)
{
uint32_t buf_addr;
size_t buf_len = qtd_length;
uint32_t ctrl = qtd_ctrl;
uint_t i = 0;
int rem_len;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_fill_in_qtd: qtd 0x%p ctrl 0x%x bufoffs 0x%lx "
"len 0x%lx", (void *)qtd, qtd_ctrl, qtd_dma_offs, qtd_length);
/* Assert that the qtd to be filled in is a dummy */
ASSERT(Get_QTD(qtd->qtd_state) == EHCI_QTD_DUMMY);
/* Change QTD's state Active */
Set_QTD(qtd->qtd_state, EHCI_QTD_ACTIVE);
/* Set the total length data transfer */
ctrl |= (((qtd_length << EHCI_QTD_CTRL_BYTES_TO_XFER_SHIFT)
& EHCI_QTD_CTRL_BYTES_TO_XFER) | EHCI_QTD_CTRL_MAX_ERR_COUNTS);
/*
* 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.
*/
ASSERT(buf_len == 0 || qtd_dma_offs >= tw->tw_dma_offs);
/*
* Save the starting dma buffer offset used and
* length of data that will be transfered in
* the current QTD.
*/
Set_QTD(qtd->qtd_xfer_offs, qtd_dma_offs);
Set_QTD(qtd->qtd_xfer_len, buf_len);
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.
*/
while ((tw->tw_dma_offs + tw->tw_cookie.dmac_size) <=
qtd_dma_offs) {
/*
* tw_dma_offs always points to the starting offset
* of a cookie
*/
tw->tw_dma_offs += tw->tw_cookie.dmac_size;
ddi_dma_nextcookie(tw->tw_dmahandle, &tw->tw_cookie);
tw->tw_cookie_idx++;
ASSERT(tw->tw_cookie_idx < tw->tw_ncookies);
}
/*
* Counting the remained buffer length to be filled in
* the QTD for current DMA cookie
*/
rem_len = (tw->tw_dma_offs + tw->tw_cookie.dmac_size) -
qtd_dma_offs;
/* Update the beginning of the buffer */
buf_addr = (qtd_dma_offs - tw->tw_dma_offs) +
tw->tw_cookie.dmac_address;
ASSERT((buf_addr % EHCI_4K_ALIGN) == 0);
Set_QTD(qtd->qtd_buf[i], buf_addr);
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_fill_in_qtd: dmac_addr 0x%x dmac_size "
"0x%lx idx %d", buf_addr, tw->tw_cookie.dmac_size,
tw->tw_cookie_idx);
if (buf_len <= EHCI_MAX_QTD_BUF_SIZE) {
ASSERT(buf_len <= rem_len);
break;
} else {
ASSERT(rem_len >= EHCI_MAX_QTD_BUF_SIZE);
buf_len -= EHCI_MAX_QTD_BUF_SIZE;
qtd_dma_offs += EHCI_MAX_QTD_BUF_SIZE;
}
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.
*/
if (tw->tw_alt_qtd != NULL) {
Set_QTD(qtd->qtd_alt_next_qtd,
(ehci_qtd_cpu_to_iommu(ehcip, tw->tw_alt_qtd) &
EHCI_QTD_ALT_NEXT_QTD_PTR));
}
/*
* For control, bulk and interrupt QTD, now
* enable current QTD by setting active bit.
*/
Set_QTD(qtd->qtd_ctrl, (ctrl | EHCI_QTD_CTRL_ACTIVE_XACT));
/*
* For Control Xfer, qtd_ctrl_phase is a valid filed.
*/
if (qtd_ctrl_phase) {
Set_QTD(qtd->qtd_ctrl_phase, qtd_ctrl_phase);
}
/* Set the transfer wrapper */
ASSERT(tw != NULL);
ASSERT(tw->tw_id != NULL);
Set_QTD(qtd->qtd_trans_wrapper, (uint32_t)tw->tw_id);
}
/*
* 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
ehci_insert_qtd_on_tw(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw,
ehci_qtd_t *qtd)
{
/*
* Set the next pointer to NULL because
* this is the last QTD on list.
*/
Set_QTD(qtd->qtd_tw_next_qtd, NULL);
if (tw->tw_qtd_head == NULL) {
ASSERT(tw->tw_qtd_tail == NULL);
tw->tw_qtd_head = qtd;
tw->tw_qtd_tail = qtd;
} else {
ehci_qtd_t *dummy = (ehci_qtd_t *)tw->tw_qtd_tail;
ASSERT(dummy != NULL);
ASSERT(dummy != qtd);
ASSERT(Get_QTD(qtd->qtd_state) != EHCI_QTD_DUMMY);
/* Add the qtd to the end of the list */
Set_QTD(dummy->qtd_tw_next_qtd,
ehci_qtd_cpu_to_iommu(ehcip, qtd));
tw->tw_qtd_tail = qtd;
ASSERT(Get_QTD(qtd->qtd_tw_next_qtd) == NULL);
}
}
/*
* ehci_insert_qtd_into_active_qtd_list:
*
* Insert current QTD into active QTD list.
*/
static void
ehci_insert_qtd_into_active_qtd_list(
ehci_state_t *ehcip,
ehci_qtd_t *qtd)
{
ehci_qtd_t *curr_qtd, *next_qtd;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
curr_qtd = ehcip->ehci_active_qtd_list;
/* Insert this QTD into QTD Active List */
if (curr_qtd) {
next_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(curr_qtd->qtd_active_qtd_next));
while (next_qtd) {
curr_qtd = next_qtd;
next_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(curr_qtd->qtd_active_qtd_next));
}
Set_QTD(qtd->qtd_active_qtd_prev,
ehci_qtd_cpu_to_iommu(ehcip, curr_qtd));
Set_QTD(curr_qtd->qtd_active_qtd_next,
ehci_qtd_cpu_to_iommu(ehcip, qtd));
} else {
ehcip->ehci_active_qtd_list = qtd;
Set_QTD(qtd->qtd_active_qtd_next, NULL);
Set_QTD(qtd->qtd_active_qtd_prev, NULL);
}
}
/*
* 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
ehci_remove_qtd_from_active_qtd_list(
ehci_state_t *ehcip,
ehci_qtd_t *qtd)
{
ehci_qtd_t *curr_qtd, *prev_qtd, *next_qtd;
ASSERT(qtd != NULL);
curr_qtd = ehcip->ehci_active_qtd_list;
while ((curr_qtd) && (curr_qtd != qtd)) {
curr_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(curr_qtd->qtd_active_qtd_next));
}
if ((curr_qtd) && (curr_qtd == qtd)) {
prev_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(curr_qtd->qtd_active_qtd_prev));
next_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(curr_qtd->qtd_active_qtd_next));
if (prev_qtd) {
Set_QTD(prev_qtd->qtd_active_qtd_next,
Get_QTD(curr_qtd->qtd_active_qtd_next));
} else {
ehcip->ehci_active_qtd_list = next_qtd;
}
if (next_qtd) {
Set_QTD(next_qtd->qtd_active_qtd_prev,
Get_QTD(curr_qtd->qtd_active_qtd_prev));
}
} else {
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"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(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
ehci_trans_wrapper_t *next_tw;
ehci_qtd_t *qtd;
ehci_qtd_t *next_qtd;
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_traverse_qtds:");
/* Process the transfer wrappers for this pipe */
next_tw = pp->pp_tw_head;
while (next_tw) {
/* Stop the the transfer timer */
ehci_stop_xfer_timer(ehcip, next_tw, EHCI_REMOVE_XFER_ALWAYS);
qtd = (ehci_qtd_t *)next_tw->tw_qtd_head;
/* Walk through each QTD for this transfer wrapper */
while (qtd) {
/* Remove this QTD from active QTD list */
ehci_remove_qtd_from_active_qtd_list(ehcip, qtd);
next_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(qtd->qtd_tw_next_qtd));
/* Deallocate this QTD */
ehci_deallocate_qtd(ehcip, qtd);
qtd = next_qtd;
}
next_tw = next_tw->tw_next;
}
/* Clear current qtd pointer */
Set_QH(pp->pp_qh->qh_curr_qtd, (uint32_t)0x00000000);
/* Update the next qtd pointer in the QH */
Set_QH(pp->pp_qh->qh_next_qtd, Get_QH(pp->pp_qh->qh_dummy_qtd));
}
/*
* 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(
ehci_state_t *ehcip,
ehci_qtd_t *old_qtd)
{
ehci_trans_wrapper_t *tw = NULL;
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_deallocate_qtd: old_qtd = 0x%p", (void *)old_qtd);
/*
* Obtain the transaction wrapper and tw will be
* NULL for the dummy QTDs.
*/
if (Get_QTD(old_qtd->qtd_state) != EHCI_QTD_DUMMY) {
tw = (ehci_trans_wrapper_t *)
EHCI_LOOKUP_ID((uint32_t)
Get_QTD(old_qtd->qtd_trans_wrapper));
ASSERT(tw != NULL);
}
/*
* If QTD's transfer wrapper is NULL, don't access its TW.
* Just free the QTD.
*/
if (tw) {
ehci_qtd_t *qtd, *next_qtd;
qtd = tw->tw_qtd_head;
if (old_qtd != qtd) {
next_qtd = ehci_qtd_iommu_to_cpu(
ehcip, Get_QTD(qtd->qtd_tw_next_qtd));
while (next_qtd != old_qtd) {
qtd = next_qtd;
next_qtd = ehci_qtd_iommu_to_cpu(
ehcip, Get_QTD(qtd->qtd_tw_next_qtd));
}
Set_QTD(qtd->qtd_tw_next_qtd, old_qtd->qtd_tw_next_qtd);
if (qtd->qtd_tw_next_qtd == NULL) {
tw->tw_qtd_tail = qtd;
}
} else {
tw->tw_qtd_head = ehci_qtd_iommu_to_cpu(
ehcip, Get_QTD(old_qtd->qtd_tw_next_qtd));
if (tw->tw_qtd_head == NULL) {
tw->tw_qtd_tail = NULL;
}
}
}
bzero((void *)old_qtd, sizeof (ehci_qtd_t));
Set_QTD(old_qtd->qtd_state, EHCI_QTD_FREE);
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"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
ehci_qtd_cpu_to_iommu(
ehci_state_t *ehcip,
ehci_qtd_t *addr)
{
uint32_t td;
td = (uint32_t)ehcip->ehci_qtd_pool_cookie.dmac_address +
(uint32_t)((uintptr_t)addr -
(uintptr_t)(ehcip->ehci_qtd_pool_addr));
ASSERT((ehcip->ehci_qtd_pool_cookie.dmac_address +
(uint32_t) (sizeof (ehci_qtd_t) *
(addr - ehcip->ehci_qtd_pool_addr))) ==
(ehcip->ehci_qtd_pool_cookie.dmac_address +
(uint32_t)((uintptr_t)addr - (uintptr_t)
(ehcip->ehci_qtd_pool_addr))));
ASSERT(td >= ehcip->ehci_qtd_pool_cookie.dmac_address);
ASSERT(td <= ehcip->ehci_qtd_pool_cookie.dmac_address +
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.
*/
ehci_qtd_t *
ehci_qtd_iommu_to_cpu(
ehci_state_t *ehcip,
uintptr_t addr)
{
ehci_qtd_t *qtd;
if (addr == NULL) {
return (NULL);
}
qtd = (ehci_qtd_t *)((uintptr_t)
(addr - ehcip->ehci_qtd_pool_cookie.dmac_address) +
(uintptr_t)ehcip->ehci_qtd_pool_addr);
ASSERT(qtd >= ehcip->ehci_qtd_pool_addr);
ASSERT((uintptr_t)qtd <= (uintptr_t)ehcip->ehci_qtd_pool_addr +
(uintptr_t)(sizeof (ehci_qtd_t) * ehci_qtd_pool_size));
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
ehci_allocate_tds_for_tw(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw,
size_t qtd_count)
{
usb_ep_descr_t *eptd = &pp->pp_pipe_handle->p_ep;
uchar_t attributes;
ehci_qtd_t *qtd;
uint32_t qtd_addr;
int i;
int error = USB_SUCCESS;
attributes = eptd->bmAttributes & USB_EP_ATTR_MASK;
for (i = 0; i < qtd_count; i += 1) {
qtd = ehci_allocate_qtd_from_pool(ehcip);
if (qtd == NULL) {
error = USB_NO_RESOURCES;
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_qtds_for_tw: "
"Unable to allocate %lu QTDs",
qtd_count);
break;
}
if (i > 0) {
qtd_addr = ehci_qtd_cpu_to_iommu(ehcip,
tw->tw_qtd_free_list);
Set_QTD(qtd->qtd_tw_next_qtd, qtd_addr);
}
tw->tw_qtd_free_list = qtd;
/*
* 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.
*/
if ((i == 1) && (attributes == USB_EP_ATTR_BULK)) {
tw->tw_alt_qtd = qtd;
}
}
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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
size_t tw_length,
usb_flags_t usb_flags,
size_t qtd_count)
{
ehci_trans_wrapper_t *tw;
tw = ehci_create_transfer_wrapper(ehcip, pp, tw_length, usb_flags);
if (tw == NULL) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_tw_resources: Unable to allocate TW");
} else {
if (ehci_allocate_tds_for_tw(ehcip, pp, tw, qtd_count) ==
USB_SUCCESS) {
tw->tw_num_qtds = qtd_count;
} else {
ehci_deallocate_tw(ehcip, pp, tw);
tw = NULL;
}
}
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(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw)
{
ehci_qtd_t *qtd = NULL;
ehci_qtd_t *temp_qtd = NULL;
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_free_tw_td_resources: tw = 0x%p", (void *)tw);
qtd = tw->tw_qtd_free_list;
while (qtd != NULL) {
/* Save the pointer to the next qtd before destroying it */
temp_qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(qtd->qtd_tw_next_qtd));
ehci_deallocate_qtd(ehcip, qtd);
qtd = temp_qtd;
}
tw->tw_qtd_free_list = NULL;
}
/*
* 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 *
ehci_create_transfer_wrapper(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
size_t length,
uint_t usb_flags)
{
ddi_device_acc_attr_t dev_attr;
ddi_dma_attr_t dma_attr;
int result;
size_t real_length;
ehci_trans_wrapper_t *tw;
int kmem_flag;
int (*dmamem_wait)(caddr_t);
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_create_transfer_wrapper: length = 0x%lx flags = 0x%x",
length, usb_flags);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* SLEEP flag should not be used in interrupt context */
if (servicing_interrupt()) {
kmem_flag = KM_NOSLEEP;
dmamem_wait = DDI_DMA_DONTWAIT;
} else {
kmem_flag = KM_SLEEP;
dmamem_wait = DDI_DMA_SLEEP;
}
/* Allocate space for the transfer wrapper */
tw = kmem_zalloc(sizeof (ehci_trans_wrapper_t), kmem_flag);
if (tw == NULL) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"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 */
bcopy(&ehcip->ehci_dma_attr, &dma_attr, sizeof (ddi_dma_attr_t));
dma_attr.dma_attr_sgllen = EHCI_DMA_ATTR_TW_SGLLEN;
dma_attr.dma_attr_align = EHCI_DMA_ATTR_ALIGNMENT;
/* Allocate the DMA handle */
result = ddi_dma_alloc_handle(ehcip->ehci_dip,
&dma_attr, dmamem_wait, 0, &tw->tw_dmahandle);
if (result != DDI_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_create_transfer_wrapper: Alloc handle failed");
kmem_free(tw, sizeof (ehci_trans_wrapper_t));
return (NULL);
}
dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
/* no need for swapping the raw data */
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(tw->tw_dmahandle, length,
&dev_attr, DDI_DMA_CONSISTENT, dmamem_wait, NULL,
(caddr_t *)&tw->tw_buf, &real_length, &tw->tw_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(&tw->tw_dmahandle);
kmem_free(tw, sizeof (ehci_trans_wrapper_t));
return (NULL);
}
ASSERT(real_length >= length);
/* Bind the handle */
result = ddi_dma_addr_bind_handle(tw->tw_dmahandle, NULL,
(caddr_t)tw->tw_buf, real_length, DDI_DMA_RDWR|DDI_DMA_CONSISTENT,
dmamem_wait, NULL, &tw->tw_cookie, &tw->tw_ncookies);
if (result != DDI_DMA_MAPPED) {
ehci_decode_ddi_dma_addr_bind_handle_result(ehcip, result);
ddi_dma_mem_free(&tw->tw_accesshandle);
ddi_dma_free_handle(&tw->tw_dmahandle);
kmem_free(tw, sizeof (ehci_trans_wrapper_t));
return (NULL);
}
tw->tw_cookie_idx = 0;
tw->tw_dma_offs = 0;
dmadone:
/*
* Only allow one wrapper to be added at a time. Insert the
* new transaction wrapper into the list for this pipe.
*/
if (pp->pp_tw_head == NULL) {
pp->pp_tw_head = tw;
pp->pp_tw_tail = tw;
} else {
pp->pp_tw_tail->tw_next = tw;
pp->pp_tw_tail = tw;
}
/* Store the transfer length */
tw->tw_length = length;
/* Store a back pointer to the pipe private structure */
tw->tw_pipe_private = pp;
/* Store the transfer type - synchronous or asynchronous */
tw->tw_flags = usb_flags;
/* Get and Store 32bit ID */
tw->tw_id = EHCI_GET_ID((void *)tw);
ASSERT(tw->tw_id != NULL);
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_create_transfer_wrapper: tw = 0x%p, ncookies = %u",
(void *)tw, tw->tw_ncookies);
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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw)
{
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_start_xfer_timer: tw = 0x%p", (void *)tw);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* 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) {
tw->tw_timeout_next = pp->pp_timeout_list;
}
pp->pp_timeout_list = tw;
ehci_start_timer(ehcip, pp);
}
}
/*
* ehci_stop_xfer_timer:
*
* Start the timer for the control, bulk and for one time interrupt
* transfers.
*/
void
ehci_stop_xfer_timer(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw,
uint_t flag)
{
ehci_pipe_private_t *pp;
timeout_id_t timer_id;
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_stop_xfer_timer: tw = 0x%p", (void *)tw);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Obtain the pipe private structure */
pp = tw->tw_pipe_private;
/* check if the timeout tw list is empty */
if (pp->pp_timeout_list == NULL) {
return;
}
switch (flag) {
case EHCI_REMOVE_XFER_IFLAST:
if (tw->tw_qtd_head != tw->tw_qtd_tail) {
break;
}
/* FALLTHRU */
case EHCI_REMOVE_XFER_ALWAYS:
ehci_remove_tw_from_timeout_list(ehcip, tw);
if ((pp->pp_timeout_list == NULL) &&
(pp->pp_timer_id)) {
timer_id = pp->pp_timer_id;
/* Reset the timer id to zero */
pp->pp_timer_id = 0;
mutex_exit(&ehcip->ehci_int_mutex);
(void) untimeout(timer_id);
mutex_enter(&ehcip->ehci_int_mutex);
}
break;
default:
break;
}
}
/*
* ehci_xfer_timeout_handler:
*
* Control or bulk transfer timeout handler.
*/
static void
ehci_xfer_timeout_handler(void *arg)
{
usba_pipe_handle_data_t *ph = (usba_pipe_handle_data_t *)arg;
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
ehci_trans_wrapper_t *tw, *next;
ehci_trans_wrapper_t *expire_xfer_list = NULL;
ehci_qtd_t *qtd;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_xfer_timeout_handler: ehcip = 0x%p, ph = 0x%p",
(void *)ehcip, (void *)ph);
mutex_enter(&ehcip->ehci_int_mutex);
/*
* 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 {
mutex_exit(&ehcip->ehci_int_mutex);
return;
}
/* Get the transfer timeout list head */
tw = pp->pp_timeout_list;
while (tw) {
/* Get the transfer on the timeout list */
next = tw->tw_timeout_next;
tw->tw_timeout--;
if (tw->tw_timeout <= 0) {
/* remove the tw from the timeout list */
ehci_remove_tw_from_timeout_list(ehcip, tw);
/* remove QTDs from active QTD list */
qtd = tw->tw_qtd_head;
while (qtd) {
ehci_remove_qtd_from_active_qtd_list(
ehcip, qtd);
/* Get the next QTD from the wrapper */
qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(qtd->qtd_tw_next_qtd));
}
/*
* Preserve the order to the requests
* started time sequence.
*/
tw->tw_timeout_next = expire_xfer_list;
expire_xfer_list = tw;
}
tw = next;
}
/*
* 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.
*/
ehci_start_timer(ehcip, pp);
/* Get the expired transfer timeout list head */
tw = expire_xfer_list;
while (tw) {
/* Get the next tw on the expired transfer timeout list */
next = tw->tw_timeout_next;
/*
* 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_handle_error(ehcip, tw->tw_qtd_head, USB_CR_TIMEOUT);
tw = next;
}
mutex_exit(&ehcip->ehci_int_mutex);
}
/*
* ehci_remove_tw_from_timeout_list:
*
* Remove Control or bulk transfer from the timeout list.
*/
static void
ehci_remove_tw_from_timeout_list(
ehci_state_t *ehcip,
ehci_trans_wrapper_t *tw)
{
ehci_pipe_private_t *pp;
ehci_trans_wrapper_t *prev, *next;
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_remove_tw_from_timeout_list: tw = 0x%p", (void *)tw);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Obtain the pipe private structure */
pp = tw->tw_pipe_private;
if (pp->pp_timeout_list) {
if (pp->pp_timeout_list == tw) {
pp->pp_timeout_list = tw->tw_timeout_next;
tw->tw_timeout_next = NULL;
} else {
prev = pp->pp_timeout_list;
next = prev->tw_timeout_next;
while (next && (next != tw)) {
prev = next;
next = next->tw_timeout_next;
}
if (next == tw) {
prev->tw_timeout_next =
next->tw_timeout_next;
tw->tw_timeout_next = NULL;
}
}
}
}
/*
* ehci_start_timer:
*
* Start the pipe's timer
*/
static void
ehci_start_timer(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_start_timer: ehcip = 0x%p, pp = 0x%p",
(void *)ehcip, (void *)pp);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* 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.
*/
if ((!pp->pp_timer_id) && (pp->pp_timeout_list)) {
pp->pp_timer_id = timeout(ehci_xfer_timeout_handler,
(void *)(pp->pp_pipe_handle), drv_usectohz(1000000));
}
}
/*
* ehci_deallocate_tw:
*
* Deallocate of a Transaction Wrapper (TW) and this involves the freeing of
* of DMA resources.
*/
void
ehci_deallocate_tw(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw)
{
ehci_trans_wrapper_t *prev, *next;
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"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) {
ASSERT(tw->tw_qtd_tail != NULL);
return;
}
ASSERT(tw->tw_qtd_tail == NULL);
/* Make sure we return all the unused qtd's to the pool as well */
ehci_free_tw_td_resources(ehcip, tw);
/*
* 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_tw_head == tw) {
if (pp->pp_tw_tail == tw) {
pp->pp_tw_head = NULL;
pp->pp_tw_tail = NULL;
} else {
pp->pp_tw_head = tw->tw_next;
}
} else {
prev = pp->pp_tw_head;
next = prev->tw_next;
while (next && (next != tw)) {
prev = next;
next = next->tw_next;
}
if (next == tw) {
prev->tw_next = next->tw_next;
if (pp->pp_tw_tail == tw) {
pp->pp_tw_tail = prev;
}
}
}
/*
* Make sure that, this TW has been removed
* from the timeout list.
*/
ehci_remove_tw_from_timeout_list(ehcip, tw);
/* Deallocate this TW */
ehci_free_tw(ehcip, pp, 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(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
ehci_trans_wrapper_t *head_tw = pp->pp_tw_head;
ehci_trans_wrapper_t *next_tw, *tw;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_free_dma_resources: ph = 0x%p", (void *)ph);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Process the Transfer Wrappers */
next_tw = head_tw;
while (next_tw) {
tw = next_tw;
next_tw = tw->tw_next;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_free_dma_resources: Free TW = 0x%p", (void *)tw);
ehci_free_tw(ehcip, pp, tw);
}
/* Adjust the head and tail pointers */
pp->pp_tw_head = NULL;
pp->pp_tw_tail = NULL;
}
/*
* ehci_free_tw:
*
* Free the Transfer Wrapper (TW).
*/
/*ARGSUSED*/
static void
ehci_free_tw(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw)
{
int rval;
USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl,
"ehci_free_tw: tw = 0x%p", (void *)tw);
ASSERT(tw != NULL);
ASSERT(tw->tw_id != NULL);
/* Free 32bit ID */
EHCI_FREE_ID((uint32_t)tw->tw_id);
if (tw->tw_dmahandle != NULL) {
rval = ddi_dma_unbind_handle(tw->tw_dmahandle);
ASSERT(rval == DDI_SUCCESS);
ddi_dma_mem_free(&tw->tw_accesshandle);
ddi_dma_free_handle(&tw->tw_dmahandle);
}
/* Free transfer wrapper */
kmem_free(tw, sizeof (ehci_trans_wrapper_t));
}
/*
* Miscellaneous functions
*/
/*
* ehci_allocate_intr_in_resource
*
* Allocate interrupt request structure for the interrupt IN transfer.
*/
/*ARGSUSED*/
int
ehci_allocate_intr_in_resource(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw,
usb_flags_t flags)
{
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
usb_intr_req_t *curr_intr_reqp;
usb_opaque_t client_periodic_in_reqp;
size_t length = 0;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_intr_in_resource:"
"pp = 0x%p tw = 0x%p flags = 0x%x", (void *)pp, (void *)tw, flags);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
ASSERT(tw->tw_curr_xfer_reqp == NULL);
/* Get the client periodic in request pointer */
client_periodic_in_reqp = pp->pp_client_periodic_in_reqp;
/*
* 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 *)
client_periodic_in_reqp)->intr_len;
curr_intr_reqp = usba_hcdi_dup_intr_req(ph->p_dip,
(usb_intr_req_t *)client_periodic_in_reqp, length, flags);
} else {
curr_intr_reqp = usb_alloc_intr_req(ph->p_dip, length, flags);
}
if (curr_intr_reqp == NULL) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_allocate_intr_in_resource: Interrupt"
"request structure allocation failed");
return (USB_NO_RESOURCES);
}
/* For polled mode */
if (client_periodic_in_reqp == NULL) {
curr_intr_reqp->intr_attributes = USB_ATTRS_SHORT_XFER_OK;
curr_intr_reqp->intr_len = ph->p_ep.wMaxPacketSize;
} else {
/* Check and save the timeout value */
tw->tw_timeout = (curr_intr_reqp->intr_attributes &
USB_ATTRS_ONE_XFER) ? curr_intr_reqp->intr_timeout: 0;
}
tw->tw_curr_xfer_reqp = (usb_opaque_t)curr_intr_reqp;
tw->tw_length = curr_intr_reqp->intr_len;
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_pipe_cleanup
*
* Cleanup ehci pipe.
*/
void
ehci_pipe_cleanup(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
uint_t pipe_state = pp->pp_state;
usb_cr_t completion_reason;
usb_ep_descr_t *eptd = &ph->p_ep;
USB_DPRINTF_L4(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_pipe_cleanup: ph = 0x%p", (void *)ph);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
if (EHCI_ISOC_ENDPOINT(eptd)) {
ehci_isoc_pipe_cleanup(ehcip, ph);
return;
}
ASSERT(!servicing_interrupt());
/*
* 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.
*/
ehci_modify_qh_status_bit(ehcip, pp, SET_HALT);
/*
* Wait for processing all completed transfers and
* to send results to upstream.
*/
ehci_wait_for_transfers_completion(ehcip, pp);
/* Save the data toggle information */
ehci_save_data_toggle(ehcip, ph);
/*
* 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.
*/
ehci_traverse_qtds(ehcip, ph);
/* Make sure the timer is not running */
ASSERT(pp->pp_timer_id == 0);
/* Do callbacks for all unfinished requests */
ehci_handle_outstanding_requests(ehcip, pp);
/* Free DMA resources */
ehci_free_dma_resources(ehcip, ph);
switch (pipe_state) {
case EHCI_PIPE_STATE_CLOSE:
completion_reason = USB_CR_PIPE_CLOSING;
break;
case EHCI_PIPE_STATE_RESET:
case EHCI_PIPE_STATE_STOP_POLLING:
/* Set completion reason */
completion_reason = (pipe_state ==
EHCI_PIPE_STATE_RESET) ?
USB_CR_PIPE_RESET: USB_CR_STOPPED_POLLING;
/* Restore the data toggle information */
ehci_restore_data_toggle(ehcip, ph);
/*
* Clear the halt bit to restart all the
* transactions on this pipe.
*/
ehci_modify_qh_status_bit(ehcip, pp, CLEAR_HALT);
/* Set pipe state to idle */
pp->pp_state = EHCI_PIPE_STATE_IDLE;
break;
}
/*
* Do the callback for the original client
* periodic IN request.
*/
if ((EHCI_PERIODIC_ENDPOINT(eptd)) &&
((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) ==
USB_EP_DIR_IN)) {
ehci_do_client_periodic_in_req_callback(
ehcip, pp, completion_reason);
}
}
/*
* ehci_wait_for_transfers_completion:
*
* Wait for processing all completed transfers and to send results
* to upstream.
*/
static void
ehci_wait_for_transfers_completion(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
ehci_trans_wrapper_t *next_tw = pp->pp_tw_head;
clock_t xfer_cmpl_time_wait;
ehci_qtd_t *qtd;
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_wait_for_transfers_completion: pp = 0x%p", (void *)pp);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
if ((ehci_state_is_operational(ehcip)) != USB_SUCCESS) {
return;
}
pp->pp_count_done_qtds = 0;
/* Process the transfer wrappers for this pipe */
while (next_tw) {
qtd = (ehci_qtd_t *)next_tw->tw_qtd_head;
/*
* 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) {
if (!(Get_QTD(qtd->qtd_ctrl) &
EHCI_QTD_CTRL_ACTIVE_XACT)) {
pp->pp_count_done_qtds++;
}
qtd = ehci_qtd_iommu_to_cpu(ehcip,
Get_QTD(qtd->qtd_tw_next_qtd));
}
next_tw = next_tw->tw_next;
}
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_wait_for_transfers_completion: count_done_qtds = 0x%x",
pp->pp_count_done_qtds);
if (!pp->pp_count_done_qtds) {
return;
}
/* Get the number of clock ticks to wait */
xfer_cmpl_time_wait = drv_usectohz(EHCI_XFER_CMPL_TIMEWAIT * 1000000);
(void) cv_timedwait(&pp->pp_xfer_cmpl_cv,
&ehcip->ehci_int_mutex,
ddi_get_lbolt() + xfer_cmpl_time_wait);
if (pp->pp_count_done_qtds) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_check_for_transfers_completion: pp = 0x%p", (void *)pp);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
if ((pp->pp_state == EHCI_PIPE_STATE_STOP_POLLING) &&
(pp->pp_error == USB_CR_NO_RESOURCES) &&
(pp->pp_cur_periodic_req_cnt == 0)) {
/* Reset pipe error to zero */
pp->pp_error = 0;
/* Do callback for original request */
ehci_do_client_periodic_in_req_callback(
ehcip, pp, USB_CR_NO_RESOURCES);
}
if (pp->pp_count_done_qtds) {
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_check_for_transfers_completion:"
"count_done_qtds = 0x%x", pp->pp_count_done_qtds);
/* Decrement the done qtd count */
pp->pp_count_done_qtds--;
if (!pp->pp_count_done_qtds) {
USB_DPRINTF_L3(PRINT_MASK_LISTS, ehcip->ehci_log_hdl,
"ehci_check_for_transfers_completion:"
"Sent transfers completion event pp = 0x%p",
(void *)pp);
/* Send the transfer completion signal */
cv_signal(&pp->pp_xfer_cmpl_cv);
}
}
}
/*
* ehci_save_data_toggle:
*
* Save the data toggle information.
*/
static void
ehci_save_data_toggle(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
uint_t data_toggle;
usb_cr_t error = pp->pp_error;
ehci_qh_t *qh = pp->pp_qh;
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_save_data_toggle: ph = 0x%p", (void *)ph);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Reset the pipe error value */
pp->pp_error = USB_CR_OK;
/* Return immediately if it is a control pipe */
if ((eptd->bmAttributes & USB_EP_ATTR_MASK) ==
USB_EP_ATTR_CONTROL) {
return;
}
/* Get the data toggle information from the endpoint (QH) */
data_toggle = (Get_QH(qh->qh_status) &
EHCI_QH_STS_DATA_TOGGLE)? DATA1:DATA0;
/*
* 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.
*/
mutex_enter(&ph->p_mutex);
usba_hcdi_set_data_toggle(ph->p_usba_device, ph->p_ep.bEndpointAddress,
data_toggle);
mutex_exit(&ph->p_mutex);
}
/*
* ehci_restore_data_toggle:
*
* Restore the data toggle information.
*/
void
ehci_restore_data_toggle(
ehci_state_t *ehcip,
usba_pipe_handle_data_t *ph)
{
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
uint_t data_toggle = 0;
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_restore_data_toggle: ph = 0x%p", (void *)ph);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Return immediately if it is a control pipe */
if ((eptd->bmAttributes & USB_EP_ATTR_MASK) ==
USB_EP_ATTR_CONTROL) {
return;
}
mutex_enter(&ph->p_mutex);
data_toggle = usba_hcdi_get_data_toggle(ph->p_usba_device,
ph->p_ep.bEndpointAddress);
usba_hcdi_set_data_toggle(ph->p_usba_device, ph->p_ep.bEndpointAddress,
0);
mutex_exit(&ph->p_mutex);
/*
* Restore the data toggle bit depending on the
* previous data toggle information.
*/
if (data_toggle) {
Set_QH(pp->pp_qh->qh_status,
Get_QH(pp->pp_qh->qh_status) | EHCI_QH_STS_DATA_TOGGLE);
} else {
Set_QH(pp->pp_qh->qh_status,
Get_QH(pp->pp_qh->qh_status) & (~EHCI_QH_STS_DATA_TOGGLE));
}
}
/*
* 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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp)
{
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
usb_ep_descr_t *eptd = &ph->p_ep;
ehci_trans_wrapper_t *curr_tw;
ehci_trans_wrapper_t *next_tw;
usb_opaque_t curr_xfer_reqp;
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_handle_outstanding_requests: pp = 0x%p", (void *)pp);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Deallocate all pre-allocated interrupt requests */
next_tw = pp->pp_tw_head;
while (next_tw) {
curr_tw = next_tw;
next_tw = curr_tw->tw_next;
curr_xfer_reqp = curr_tw->tw_curr_xfer_reqp;
/* Deallocate current interrupt request */
if (curr_xfer_reqp) {
if ((EHCI_PERIODIC_ENDPOINT(eptd)) &&
(curr_tw->tw_direction == EHCI_QTD_CTRL_IN_PID)) {
/* Decrement periodic in request count */
pp->pp_cur_periodic_req_cnt--;
ehci_deallocate_intr_in_resource(
ehcip, pp, curr_tw);
} else {
ehci_hcdi_callback(ph, curr_tw, USB_CR_FLUSHED);
}
}
}
}
/*
* ehci_deallocate_intr_in_resource
*
* Deallocate interrupt request structure for the interrupt IN transfer.
*/
void
ehci_deallocate_intr_in_resource(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
ehci_trans_wrapper_t *tw)
{
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
uchar_t ep_attr = ph->p_ep.bmAttributes;
usb_opaque_t curr_xfer_reqp;
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_deallocate_intr_in_resource: "
"pp = 0x%p tw = 0x%p", (void *)pp, (void *)tw);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
ASSERT((ep_attr & USB_EP_ATTR_MASK) == USB_EP_ATTR_INTR);
curr_xfer_reqp = tw->tw_curr_xfer_reqp;
/* Check the current periodic in request pointer */
if (curr_xfer_reqp) {
tw->tw_curr_xfer_reqp = NULL;
mutex_enter(&ph->p_mutex);
ph->p_req_count--;
mutex_exit(&ph->p_mutex);
/* Free pre-allocated interrupt requests */
usb_free_intr_req((usb_intr_req_t *)curr_xfer_reqp);
/* Set periodic in pipe state to idle */
pp->pp_state = EHCI_PIPE_STATE_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(
ehci_state_t *ehcip,
ehci_pipe_private_t *pp,
usb_cr_t completion_reason)
{
usba_pipe_handle_data_t *ph = pp->pp_pipe_handle;
usb_ep_descr_t *eptd = &ph->p_ep;
USB_DPRINTF_L4(PRINT_MASK_LISTS,
ehcip->ehci_log_hdl,
"ehci_do_client_periodic_in_req_callback: "
"pp = 0x%p cc = 0x%x", (void *)pp, completion_reason);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/*
* 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) {
ASSERT(pp->pp_cur_periodic_req_cnt == 0);
if (EHCI_ISOC_ENDPOINT(eptd)) {
ehci_hcdi_isoc_callback(ph, NULL, completion_reason);
} else {
ehci_hcdi_callback(ph, NULL, completion_reason);
}
}
}
/*
* ehci_hcdi_callback()
*
* Convenience wrapper around usba_hcdi_cb() other than root hub.
*/
void
ehci_hcdi_callback(
usba_pipe_handle_data_t *ph,
ehci_trans_wrapper_t *tw,
usb_cr_t completion_reason)
{
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private;
usb_opaque_t curr_xfer_reqp;
uint_t pipe_state = 0;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_callback: ph = 0x%p, tw = 0x%p, cr = 0x%x",
(void *)ph, (void *)tw, completion_reason);
ASSERT(mutex_owned(&ehcip->ehci_int_mutex));
/* Set the pipe state as per completion reason */
switch (completion_reason) {
case USB_CR_OK:
pipe_state = pp->pp_state;
break;
case USB_CR_NO_RESOURCES:
case USB_CR_NOT_SUPPORTED:
case USB_CR_PIPE_RESET:
case USB_CR_STOPPED_POLLING:
pipe_state = EHCI_PIPE_STATE_IDLE;
break;
case USB_CR_PIPE_CLOSING:
break;
default:
/* Set the pipe state to error */
pipe_state = EHCI_PIPE_STATE_ERROR;
pp->pp_error = completion_reason;
break;
}
pp->pp_state = pipe_state;
if (tw && tw->tw_curr_xfer_reqp) {
curr_xfer_reqp = tw->tw_curr_xfer_reqp;
tw->tw_curr_xfer_reqp = NULL;
} else {
ASSERT(pp->pp_client_periodic_in_reqp != NULL);
curr_xfer_reqp = pp->pp_client_periodic_in_reqp;
pp->pp_client_periodic_in_reqp = NULL;
}
ASSERT(curr_xfer_reqp != NULL);
mutex_exit(&ehcip->ehci_int_mutex);
usba_hcdi_cb(ph, curr_xfer_reqp, completion_reason);
mutex_enter(&ehcip->ehci_int_mutex);
}