uhcitgt.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (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 2004 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Universal Host Controller Driver (UHCI)
*
* The UHCI driver is a driver which interfaces to the Universal
* Serial Bus Driver (USBA) and the Host Controller (HC). The interface to
* the Host Controller is defined by the Universal Host Controller Interface.
* This file contains the code for HCDI entry points.
*/
#include <sys/usb/hcd/uhci/uhcid.h>
#include <sys/usb/hcd/uhci/uhcitgt.h>
#include <sys/usb/hcd/uhci/uhciutil.h>
/* function prototypes */
static int uhci_pipe_send_isoc_data(uhci_state_t *uhcip,
usba_pipe_handle_data_t *ph, usb_isoc_req_t *isoc_req,
usb_flags_t usb_flags);
static int uhci_send_intr_data(uhci_state_t *uhcip,
usba_pipe_handle_data_t *pipe_handle,
usb_intr_req_t *req,
usb_flags_t flags);
static int uhci_start_periodic_pipe_polling(uhci_state_t *uhcip,
usba_pipe_handle_data_t *ph,
usb_opaque_t reqp,
usb_flags_t flags);
static int uhci_stop_periodic_pipe_polling(uhci_state_t *uhcip,
usba_pipe_handle_data_t *ph,
usb_flags_t flags);
static void uhci_update_intr_td_data_toggle(uhci_state_t *uhcip,
uhci_pipe_private_t *pp);
/* Maximum bulk transfer size */
static int uhci_bulk_transfer_size = UHCI_BULK_MAX_XFER_SIZE;
/*
* uhci_hcdi_pipe_open:
* Member of HCD Ops structure and called during client specific pipe open
* Add the pipe to the data structure representing the device and allocate
* bandwidth for the pipe if it is a interrupt or isochronous endpoint.
*/
int
uhci_hcdi_pipe_open(usba_pipe_handle_data_t *ph, usb_flags_t flags)
{
uint_t node = 0;
usb_addr_t usb_addr;
uhci_state_t *uhcip;
uhci_pipe_private_t *pp;
int error = USB_SUCCESS;
ASSERT(ph);
usb_addr = ph->p_usba_device->usb_addr;
uhcip = uhci_obtain_state(ph->p_usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: addr = 0x%x, ep%d", usb_addr,
ph->p_ep.bEndpointAddress & USB_EP_NUM_MASK);
sema_p(&uhcip->uhci_ocsem);
/*
* Return failure immediately for any other pipe open on the root hub
* except control or interrupt pipe.
*/
if (usb_addr == ROOT_HUB_ADDR) {
switch (UHCI_XFER_TYPE(&ph->p_ep)) {
case USB_EP_ATTR_CONTROL:
USB_DPRINTF_L3(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: Root hub control pipe");
break;
case USB_EP_ATTR_INTR:
ASSERT(UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_IN);
mutex_enter(&uhcip->uhci_int_mutex);
uhcip->uhci_root_hub.rh_intr_pipe_handle = ph;
/*
* Set the state of the root hub interrupt
* pipe as IDLE.
*/
uhcip->uhci_root_hub.rh_pipe_state =
UHCI_PIPE_STATE_IDLE;
ASSERT(uhcip->uhci_root_hub.rh_client_intr_req == NULL);
uhcip->uhci_root_hub.rh_client_intr_req = NULL;
ASSERT(uhcip->uhci_root_hub.rh_curr_intr_reqp == NULL);
uhcip->uhci_root_hub.rh_curr_intr_reqp = NULL;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: Root hub interrupt "
"pipe open succeeded");
mutex_exit(&uhcip->uhci_int_mutex);
sema_v(&uhcip->uhci_ocsem);
return (USB_SUCCESS);
default:
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: Root hub pipe open failed");
sema_v(&uhcip->uhci_ocsem);
return (USB_FAILURE);
}
}
/*
* A portion of the bandwidth is reserved for the non-periodic
* transfers i.e control and bulk transfers in each of one
* mill second frame period & usually it will be 10% of frame
* period. Hence there is no need to check for the available
* bandwidth before adding the control or bulk endpoints.
*
* There is a need to check for the available bandwidth before
* adding the periodic transfers i.e interrupt & isochronous, since
* all these periodic transfers are guaranteed transfers. Usually,
* 90% of the total frame time is reserved for periodic transfers.
*/
if (UHCI_PERIODIC_ENDPOINT(&ph->p_ep)) {
/* Zero Max Packet size endpoints are not supported */
if (ph->p_ep.wMaxPacketSize == 0) {
USB_DPRINTF_L3(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: Zero length packet");
sema_v(&uhcip->uhci_ocsem);
return (USB_FAILURE);
}
mutex_enter(&uhcip->uhci_int_mutex);
mutex_enter(&ph->p_mutex);
error = uhci_allocate_bandwidth(uhcip, ph, &node);
if (error != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: Bandwidth allocation failed");
mutex_exit(&ph->p_mutex);
mutex_exit(&uhcip->uhci_int_mutex);
sema_v(&uhcip->uhci_ocsem);
return (error);
}
mutex_exit(&ph->p_mutex);
mutex_exit(&uhcip->uhci_int_mutex);
}
/* Create the HCD pipe private structure */
pp = kmem_zalloc(sizeof (uhci_pipe_private_t),
(flags & USB_FLAGS_SLEEP) ? KM_SLEEP : KM_NOSLEEP);
if (pp == NULL) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: pp allocation failure");
if (UHCI_PERIODIC_ENDPOINT(&ph->p_ep)) {
mutex_enter(&uhcip->uhci_int_mutex);
uhci_deallocate_bandwidth(uhcip, ph);
mutex_exit(&uhcip->uhci_int_mutex);
}
sema_v(&uhcip->uhci_ocsem);
return (USB_NO_RESOURCES);
}
mutex_enter(&uhcip->uhci_int_mutex);
pp->pp_node = node; /* Store the node in the interrupt lattice */
/* Initialize frame number */
pp->pp_frame_num = INVALID_FRNUM;
/* Set the state of pipe as IDLE */
pp->pp_state = UHCI_PIPE_STATE_IDLE;
/* Store a pointer to the pipe handle */
pp->pp_pipe_handle = ph;
/* Store the pointer in the pipe handle */
mutex_enter(&ph->p_mutex);
ph->p_hcd_private = (usb_opaque_t)pp;
/* Store a copy of the pipe policy */
bcopy(&ph->p_policy, &pp->pp_policy, sizeof (usb_pipe_policy_t));
mutex_exit(&ph->p_mutex);
/* don't check for ROOT_HUB here anymore */
if (UHCI_XFER_TYPE(&ph->p_ep) != USB_EP_ATTR_ISOCH) {
/* Allocate the host controller endpoint descriptor */
pp->pp_qh = uhci_alloc_queue_head(uhcip);
if (pp->pp_qh == NULL) {
USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: QH allocation failed");
mutex_enter(&ph->p_mutex);
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
kmem_free(ph->p_hcd_private,
sizeof (uhci_pipe_private_t));
/*
* Set the private structure in the
* pipe handle equal to NULL.
*/
ph->p_hcd_private = NULL;
mutex_exit(&ph->p_mutex);
if (UHCI_PERIODIC_ENDPOINT(&ph->p_ep)) {
uhci_deallocate_bandwidth(uhcip, ph);
}
mutex_exit(&uhcip->uhci_int_mutex);
sema_v(&uhcip->uhci_ocsem);
return (USB_NO_RESOURCES);
}
/*
* Insert the endpoint onto the host controller's
* appropriate endpoint list. The host controller
* will not schedule this endpoint until there are
* any TD's to process.
*/
uhci_insert_qh(uhcip, ph);
}
/*
* Restore the data toggle from usb device structure.
*/
if (UHCI_PERIODIC_ENDPOINT(&ph->p_ep)) {
mutex_enter(&ph->p_mutex);
pp->pp_data_toggle = usba_hcdi_get_data_toggle(
ph->p_usba_device, ph->p_ep.bEndpointAddress);
mutex_exit(&ph->p_mutex);
}
mutex_exit(&uhcip->uhci_int_mutex);
sema_v(&uhcip->uhci_ocsem);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_open: ph = 0x%p", ph);
return (USB_SUCCESS);
}
/*
* uhci_hcdi_pipe_close:
* Member of HCD Ops structure and called during the client specific pipe
* close. Remove the pipe to the data structure representing the device
* deallocate bandwidth for the pipe if it is an intr or isoch endpoint.
*/
int
uhci_hcdi_pipe_close(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags)
{
usb_addr_t usb_addr;
uhci_state_t *uhcip;
usb_ep_descr_t *eptd = &ph->p_ep;
uhci_pipe_private_t *pp;
uhcip = uhci_obtain_state(ph->p_usba_device->usb_root_hub_dip);
pp = (uhci_pipe_private_t *)ph->p_hcd_private;
usb_addr = ph->p_usba_device->usb_addr;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_close: addr = 0x%x, ep%d, flags = 0x%x", usb_addr,
eptd->bEndpointAddress, usb_flags);
sema_p(&uhcip->uhci_ocsem);
mutex_enter(&uhcip->uhci_int_mutex);
/*
* Check whether the pipe is a root hub
*/
if (usb_addr == ROOT_HUB_ADDR) {
switch (UHCI_XFER_TYPE(eptd)) {
case USB_EP_ATTR_CONTROL:
USB_DPRINTF_L3(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_close: Root hub control pipe "
"close succeeded");
break;
case USB_EP_ATTR_INTR:
ASSERT((eptd->bEndpointAddress &
USB_EP_NUM_MASK) == 1);
ASSERT(uhcip->uhci_root_hub.rh_pipe_state ==
UHCI_PIPE_STATE_ACTIVE);
/* Do interrupt pipe cleanup */
uhci_root_hub_intr_pipe_cleanup(uhcip,
USB_CR_PIPE_CLOSING);
ASSERT(uhcip->uhci_root_hub.rh_pipe_state ==
UHCI_PIPE_STATE_IDLE);
uhcip->uhci_root_hub.rh_intr_pipe_handle = NULL;
USB_DPRINTF_L3(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_close: Root hub interrupt "
"pipe close succeeded");
uhcip->uhci_root_hub.rh_pipe_state =
UHCI_PIPE_STATE_IDLE;
mutex_exit(&uhcip->uhci_int_mutex);
sema_v(&uhcip->uhci_ocsem);
return (USB_SUCCESS);
}
} else {
/*
* Stop all the transactions if it is not the root hub.
*/
if (UHCI_XFER_TYPE(eptd) == USB_EP_ATTR_INTR) {
/*
* Stop polling on the pipe to prevent any subsequently
* queued tds (while we're waiting for SOF, below)
* from being executed
*/
pp->pp_state = UHCI_PIPE_STATE_IDLE;
}
/* Disable all outstanding tds */
uhci_modify_td_active_bits(uhcip, pp);
/* Prevent this queue from being executed */
if (UHCI_XFER_TYPE(eptd) != USB_EP_ATTR_ISOCH) {
UHCI_SET_TERMINATE_BIT(pp->pp_qh->element_ptr);
}
/* Wait for the next start of frame */
(void) uhci_wait_for_sof(uhcip);
ASSERT(eptd != NULL);
switch (UHCI_XFER_TYPE(eptd)) {
case USB_EP_ATTR_INTR:
uhci_update_intr_td_data_toggle(uhcip, pp);
/* FALLTHROUGH */
case USB_EP_ATTR_CONTROL:
uhci_remove_tds_tws(uhcip, ph);
break;
case USB_EP_ATTR_BULK:
SetQH32(uhcip, pp->pp_qh->element_ptr,
TD_PADDR(pp->pp_qh->td_tailp));
uhci_remove_bulk_tds_tws(uhcip, pp, UHCI_IN_CLOSE);
uhci_save_data_toggle(pp);
break;
case USB_EP_ATTR_ISOCH:
uhci_remove_isoc_tds_tws(uhcip, pp);
break;
default:
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_close: Unknown xfer type");
break;
}
/*
* Remove the endoint descriptor from Host Controller's
* appropriate endpoint list. Isochronous pipes dont have
* any queue heads attached to it.
*/
if (UHCI_XFER_TYPE(eptd) != USB_EP_ATTR_ISOCH) {
uhci_remove_qh(uhcip, pp);
}
/*
* Do the callback for the original client
* periodic IN request.
*/
if (pp->pp_client_periodic_in_reqp) {
uhci_hcdi_callback(uhcip, pp, ph, NULL,
USB_CR_PIPE_CLOSING);
}
/* Deallocate bandwidth */
if (UHCI_PERIODIC_ENDPOINT(eptd)) {
mutex_enter(&ph->p_mutex);
uhci_deallocate_bandwidth(uhcip, ph);
mutex_exit(&ph->p_mutex);
}
}
/* Deallocate the hcd private portion of the pipe handle. */
mutex_enter(&ph->p_mutex);
kmem_free(ph->p_hcd_private, sizeof (uhci_pipe_private_t));
ph->p_hcd_private = NULL;
mutex_exit(&ph->p_mutex);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_close: ph = 0x%p", ph);
mutex_exit(&uhcip->uhci_int_mutex);
sema_v(&uhcip->uhci_ocsem);
return (USB_SUCCESS);
}
/*
* uhci_hcdi_pipe_reset:
*/
int
uhci_hcdi_pipe_reset(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags)
{
uhci_state_t *uhcip = uhci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_reset: usb_flags = 0x%x", usb_flags);
/*
* Return failure immediately for any other pipe reset on the root
* hub except control or interrupt pipe.
*/
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
switch (UHCI_XFER_TYPE(&ph->p_ep)) {
case USB_EP_ATTR_CONTROL:
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_reset: Pipe reset for root"
"hub control pipe successful");
break;
case USB_EP_ATTR_INTR:
mutex_enter(&uhcip->uhci_int_mutex);
uhcip->uhci_root_hub.rh_pipe_state =
UHCI_PIPE_STATE_IDLE;
/* Do interrupt pipe cleanup */
uhci_root_hub_intr_pipe_cleanup(uhcip,
USB_CR_PIPE_RESET);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_reset: Pipe reset for "
"root hub interrupt pipe successful");
mutex_exit(&uhcip->uhci_int_mutex);
break;
default:
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_close: Root hub pipe reset failed");
return (USB_FAILURE);
}
return (USB_SUCCESS);
}
mutex_enter(&uhcip->uhci_int_mutex);
/*
* Set the active bit in to INACTIVE for all the remaining TD's of
* this end point. Set the active bit for the dummy td. This will
* generate an interrupt at the end of the frame. After receiving
* the interrupt, it is safe to to manipulate the lattice.
*/
uhci_modify_td_active_bits(uhcip, pp);
/* Initialize the element pointer */
if (UHCI_XFER_TYPE(eptd) != USB_EP_ATTR_ISOCH) {
UHCI_SET_TERMINATE_BIT(pp->pp_qh->element_ptr);
SetQH32(uhcip, pp->pp_qh->element_ptr,
TD_PADDR(pp->pp_qh->td_tailp));
}
(void) uhci_wait_for_sof(uhcip);
/*
* Save the data toggle and clear the pipe.
*/
switch (UHCI_XFER_TYPE(eptd)) {
case USB_EP_ATTR_CONTROL:
case USB_EP_ATTR_INTR:
uhci_remove_tds_tws(uhcip, ph);
break;
case USB_EP_ATTR_BULK:
SetQH32(uhcip, pp->pp_qh->element_ptr,
TD_PADDR(pp->pp_qh->td_tailp));
uhci_remove_bulk_tds_tws(uhcip, pp, UHCI_IN_RESET);
break;
case USB_EP_ATTR_ISOCH:
uhci_remove_isoc_tds_tws(uhcip, pp);
break;
default:
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_reset: Unknown xfer type");
break;
}
/*
* Do the callback for the original client
* periodic IN request.
*/
if (pp->pp_client_periodic_in_reqp) {
uhci_hcdi_callback(uhcip, pp, ph, NULL, USB_CR_PIPE_RESET);
}
/*
* Since the endpoint is stripped of Transfer Descriptors (TD),
* reset the state of the periodic pipe to IDLE.
*/
pp->pp_state = UHCI_PIPE_STATE_IDLE;
mutex_exit(&uhcip->uhci_int_mutex);
return (USB_SUCCESS);
}
/*
* uhci_hcdi_pipe_ctrl_xfer:
*/
int
uhci_hcdi_pipe_ctrl_xfer(
usba_pipe_handle_data_t *ph,
usb_ctrl_req_t *ctrl_reqp,
usb_flags_t flags)
{
uhci_state_t *uhcip = uhci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private;
int error = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_ctrl_xfer: req=0x%p, ph=0x%p, flags=0x%x",
ctrl_reqp, ph, flags);
mutex_enter(&uhcip->uhci_int_mutex);
ASSERT(pp->pp_state == UHCI_PIPE_STATE_IDLE);
/*
* Check and handle root hub control request.
*/
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
error = uhci_handle_root_hub_request(uhcip, ph, ctrl_reqp);
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
/* Insert the td's on the endpoint */
if ((error = uhci_insert_ctrl_td(uhcip, ph, ctrl_reqp, flags)) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_ctrl_xfer: No resources");
}
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
/*
* uhci_hcdi_pipe_bulk_xfer:
*/
int
uhci_hcdi_pipe_bulk_xfer(usba_pipe_handle_data_t *pipe_handle,
usb_bulk_req_t *bulk_reqp, usb_flags_t usb_flags)
{
int error;
uhci_state_t *uhcip;
uhcip = uhci_obtain_state(pipe_handle->p_usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_bulk_xfer: Flags = 0x%x", usb_flags);
/* Check the size of bulk request */
if (bulk_reqp->bulk_len > UHCI_BULK_MAX_XFER_SIZE) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_bulk_xfer: req size 0x%x is more than 0x%x",
bulk_reqp->bulk_len, UHCI_BULK_MAX_XFER_SIZE);
return (USB_FAILURE);
}
mutex_enter(&uhcip->uhci_int_mutex);
/* Add the TD into the Host Controller's bulk list */
if ((error = uhci_insert_bulk_td(uhcip, pipe_handle, bulk_reqp,
usb_flags)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_bulk_xfer: uhci_insert_bulk_td failed");
}
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
/*
* uhci_hcdi_bulk_transfer_size:
* Return maximum bulk transfer size
*/
int
uhci_hcdi_bulk_transfer_size(
usba_device_t *usba_device,
size_t *size)
{
uhci_state_t *uhcip = uhci_obtain_state(usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_bulk_transfer_size:");
*size = uhci_bulk_transfer_size;
return (USB_SUCCESS);
}
/*
* uhci_hcdi_pipe_intr_xfer:
*/
int
uhci_hcdi_pipe_intr_xfer(
usba_pipe_handle_data_t *ph,
usb_intr_req_t *req,
usb_flags_t flags)
{
uhci_state_t *uhcip = uhci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_intr_xfer: req=0x%p, uf=0x%x", req, flags);
if (UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_IN) {
return (uhci_start_periodic_pipe_polling(uhcip, ph,
(usb_opaque_t)req, flags));
} else {
return (uhci_send_intr_data(uhcip, ph, req, flags));
}
}
/*
* uhci_send_intr_data():
* send data to interrupt out pipe
*/
static int
uhci_send_intr_data(
uhci_state_t *uhcip,
usba_pipe_handle_data_t *pipe_handle,
usb_intr_req_t *req,
usb_flags_t flags)
{
int rval = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_send_intr_data:");
mutex_enter(&uhcip->uhci_int_mutex);
/* Add the TD into the Host Controller's interrupt list */
if ((rval = uhci_insert_intr_td(uhcip, pipe_handle, req, flags)) !=
USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_send_intr_data: No resources");
}
mutex_exit(&uhcip->uhci_int_mutex);
return (rval);
}
/*
* uhci_hcdi_pipe_stop_intr_polling()
*/
int
uhci_hcdi_pipe_stop_intr_polling(
usba_pipe_handle_data_t *pipe_handle,
usb_flags_t flags)
{
uhci_state_t *uhcip =
uhci_obtain_state(pipe_handle->p_usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_stop_intr_polling: ph = 0x%p fl = 0x%x",
(void *)pipe_handle, flags);
return (uhci_stop_periodic_pipe_polling(uhcip, pipe_handle, flags));
}
/*
* uhci_hcdi_get_current_frame_number
* Returns the current frame number
*/
usb_frame_number_t
uhci_hcdi_get_current_frame_number(usba_device_t *usba_device)
{
uhci_state_t *uhcip = uhci_obtain_state(usba_device->usb_root_hub_dip);
usb_frame_number_t frame_number;
mutex_enter(&uhcip->uhci_int_mutex);
frame_number = uhci_get_sw_frame_number(uhcip);
mutex_exit(&uhcip->uhci_int_mutex);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_get_current_frame_number: %llx", frame_number);
return (frame_number);
}
/*
* uhci_hcdi_get_max_isoc_pkts
* Returns the maximum number of isoc packets per USB Isoch request
*/
uint_t
uhci_hcdi_get_max_isoc_pkts(usba_device_t *usba_device)
{
uhci_state_t *uhcip = uhci_obtain_state(usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_get_max_isoc_pkts: 0x%x", UHCI_MAX_ISOC_PKTS);
return (UHCI_MAX_ISOC_PKTS);
}
/*
* uhci_hcdi_pipe_isoc_xfer:
*/
int
uhci_hcdi_pipe_isoc_xfer(
usba_pipe_handle_data_t *ph,
usb_isoc_req_t *isoc_reqp,
usb_flags_t flags)
{
uhci_state_t *uhcip;
uhcip = uhci_obtain_state(ph->p_usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_isoc_xfer: req=0x%p, uf=0x%x", isoc_reqp, flags);
if (UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_IN) {
return (uhci_start_periodic_pipe_polling(uhcip, ph,
(usb_opaque_t)isoc_reqp, flags));
} else {
return (uhci_pipe_send_isoc_data(uhcip, ph, isoc_reqp, flags));
}
}
/*
* uhci_hcdi_pipe_stop_isoc_polling()
*/
int
uhci_hcdi_pipe_stop_isoc_polling(
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
uhci_state_t *uhcip =
uhci_obtain_state(ph->p_usba_device->usb_root_hub_dip);
USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl,
"uhci_hcdi_pipe_stop_isoc_polling: ph = 0x%p fl = 0x%x",
(void *)ph, flags);
return (uhci_stop_periodic_pipe_polling(uhcip, ph, flags));
}
/*
* uhci_start_periodic_pipe_polling:
*/
static int
uhci_start_periodic_pipe_polling(
uhci_state_t *uhcip,
usba_pipe_handle_data_t *ph,
usb_opaque_t in_reqp,
usb_flags_t flags)
{
int n, num_tds;
int error = USB_SUCCESS;
usb_intr_req_t *intr_reqp = (usb_intr_req_t *)in_reqp;
usb_ep_descr_t *eptd = &ph->p_ep;
uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_start_periodic_pipe_polling: flags: 0x%x, ep%d",
flags, eptd->bEndpointAddress);
mutex_enter(&uhcip->uhci_int_mutex);
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
uint_t pipe_state = uhcip->uhci_root_hub.rh_pipe_state;
ASSERT(pipe_state == UHCI_PIPE_STATE_IDLE);
ASSERT(UHCI_XFER_DIR(eptd) == USB_EP_DIR_IN);
/* ONE_XFER not supported */
ASSERT((intr_reqp->intr_attributes &
USB_ATTRS_ONE_XFER) == 0);
ASSERT(uhcip->uhci_root_hub.rh_client_intr_req == NULL);
uhcip->uhci_root_hub.rh_client_intr_req = intr_reqp;
if ((error = uhci_root_hub_allocate_intr_pipe_resource(
uhcip, flags)) != USB_SUCCESS) {
/* reset the client interrupt request pointer */
uhcip->uhci_root_hub.rh_client_intr_req = NULL;
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
uhcip->uhci_root_hub.rh_pipe_state = USB_PIPE_STATE_ACTIVE;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_start_periodic_pipe_polling: "
"Start intr polling for root hub successful");
/* check if we need to send the reset data up? */
if (uhcip->uhci_root_hub.rh_status) {
uhci_root_hub_reset_occurred(uhcip,
uhcip->uhci_root_hub.rh_status - 1);
uhcip->uhci_root_hub.rh_status = 0;
}
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
/* save the original client's periodic IN request */
pp->pp_client_periodic_in_reqp = in_reqp;
ASSERT(pp->pp_state != UHCI_PIPE_STATE_ACTIVE);
/*
*
* This pipe is uninitialized. If it is an isoc
* receive request, insert four times the same
* request so that we do not lose any frames.
*/
if (UHCI_XFER_TYPE(eptd) == USB_EP_ATTR_ISOCH) {
for (n = 0; n < 5; n++) {
if ((error = uhci_start_isoc_receive_polling(
uhcip, ph, NULL, flags)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_INTR,
uhcip->uhci_log_hdl,
"uhci_start_periodic_pipe_polling: "
"Start isoc polling failed %d", n);
pp->pp_client_periodic_in_reqp = NULL;
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
}
}
if (UHCI_XFER_TYPE(eptd) == USB_EP_ATTR_INTR) {
if ((pp->pp_node < POLLING_FREQ_7MS) &&
(!(intr_reqp->intr_attributes & USB_ATTRS_ONE_XFER))) {
num_tds = 5;
} else {
num_tds = 1;
}
/*
* This pipe is uninitialized.
* Insert a TD on the interrupt ED.
*/
for (n = 0; n < num_tds; n++) {
if ((error = uhci_insert_intr_td(uhcip, ph, NULL,
flags)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_INTR,
uhcip->uhci_log_hdl,
"uhci_start_periodic_pipe_polling: "
"Start polling failed");
pp->pp_client_periodic_in_reqp = NULL;
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
}
}
pp->pp_state = UHCI_PIPE_STATE_ACTIVE;
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
/*
* uhci_hcdi_periodic_pipe_stop_polling:
*/
static int
uhci_stop_periodic_pipe_polling(uhci_state_t *uhcip,
usba_pipe_handle_data_t *ph, usb_flags_t flags)
{
uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private;
usb_ep_descr_t *eptd = &ph->p_ep;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_stop_periodic_pipe_polling: flags = 0x%x", flags);
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
ASSERT(UHCI_XFER_DIR(eptd) == USB_EP_DIR_IN);
mutex_enter(&uhcip->uhci_int_mutex);
if (uhcip->uhci_root_hub.rh_pipe_state ==
UHCI_PIPE_STATE_ACTIVE) {
uhcip->uhci_root_hub.rh_pipe_state =
UHCI_PIPE_STATE_IDLE;
/* Do interrupt pipe cleanup */
uhci_root_hub_intr_pipe_cleanup(uhcip,
USB_CR_STOPPED_POLLING);
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_stop_periodic_pipe_polling: Stop intr "
"polling for root hub successful");
} else {
USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_stop_periodic_pipe_polling: "
"Intr polling for root hub is already stopped");
}
mutex_exit(&uhcip->uhci_int_mutex);
return (USB_SUCCESS);
}
mutex_enter(&uhcip->uhci_int_mutex);
if (pp->pp_state != UHCI_PIPE_STATE_ACTIVE) {
USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl,
"uhci_stop_periodic_pipe_polling: Polling already stopped");
mutex_exit(&uhcip->uhci_int_mutex);
return (USB_SUCCESS);
}
/*
* Set the terminate bits in all the tds in the queue and
* in the element_ptr.
* Do not deallocate the bandwidth or tear down the DMA
*/
uhci_modify_td_active_bits(uhcip, pp);
(void) uhci_wait_for_sof(uhcip);
if (UHCI_XFER_TYPE(eptd) == USB_EP_ATTR_ISOCH) {
uhci_remove_isoc_tds_tws(uhcip, pp);
pp->pp_state = UHCI_PIPE_STATE_IDLE;
} else {
UHCI_SET_TERMINATE_BIT(pp->pp_qh->element_ptr);
uhci_update_intr_td_data_toggle(uhcip, pp);
SetQH32(uhcip, pp->pp_qh->element_ptr,
TD_PADDR(pp->pp_qh->td_tailp));
uhci_remove_tds_tws(uhcip, ph);
}
pp->pp_state = UHCI_PIPE_STATE_IDLE;
if (pp->pp_client_periodic_in_reqp) {
uhci_hcdi_callback(uhcip, pp, ph, NULL, USB_CR_STOPPED_POLLING);
}
mutex_exit(&uhcip->uhci_int_mutex);
return (USB_SUCCESS);
}
/*
* uhci_hcdi_pipe_send_isoc_data:
* Handles the isoc write request.
*/
static int
uhci_pipe_send_isoc_data(
uhci_state_t *uhcip,
usba_pipe_handle_data_t *ph,
usb_isoc_req_t *isoc_req,
usb_flags_t usb_flags)
{
int error;
size_t max_isoc_xfer_sz, length;
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_pipe_send_isoc_data: isoc_req = %p flags = %x",
isoc_req, usb_flags);
ASSERT(isoc_req->isoc_pkts_count < UHCI_MAX_ISOC_PKTS);
/* Calculate the maximum isochronous transfer size */
max_isoc_xfer_sz = UHCI_MAX_ISOC_PKTS * ph->p_ep.wMaxPacketSize;
/* Check the size of isochronous request */
ASSERT(isoc_req->isoc_data != NULL);
length = isoc_req->isoc_data->b_wptr - isoc_req->isoc_data->b_rptr;
if (length > max_isoc_xfer_sz) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_pipe_send_isoc_data: Maximum isoc request size %lx "
"Given isoc request size %lx", max_isoc_xfer_sz, length);
return (USB_INVALID_REQUEST);
}
/*
* Check whether we can insert these tds?
* At any point of time, we can insert maximum of 1024 isoc td's,
* size of frame list table.
*/
if (isoc_req->isoc_pkts_count > UHCI_MAX_ISOC_PKTS) {
USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl,
"uhci_pipe_send_isoc_data: request too big");
return (USB_INVALID_REQUEST);
}
/* Add the TD into the Host Controller's isoc list */
mutex_enter(&uhcip->uhci_int_mutex);
if ((error = uhci_insert_isoc_td(uhcip, ph, isoc_req,
length, usb_flags)) != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl,
"uhci_pipe_send_isoc_data: Unable to insert the isoc_req,"
"Error = %d", error);
}
mutex_exit(&uhcip->uhci_int_mutex);
return (error);
}
/*
* uhci_update_intr_td_data_toggle
* Update the data toggle and save in the usba_device structure
*/
static void
uhci_update_intr_td_data_toggle(uhci_state_t *uhcip, uhci_pipe_private_t *pp)
{
uint32_t paddr_tail, element_ptr;
uhci_td_t *next_td;
/* Find the next td that would have been executed */
element_ptr = GetQH32(uhcip, pp->pp_qh->element_ptr) &
QH_ELEMENT_PTR_MASK;
next_td = TD_VADDR(element_ptr);
paddr_tail = TD_PADDR(pp->pp_qh->td_tailp);
/*
* If element_ptr points to the dummy td, then the data toggle in
* pp_data_toggle is correct. Otherwise update the data toggle in
* the pipe private
*/
if (element_ptr != paddr_tail) {
pp->pp_data_toggle = GetTD_dtogg(uhcip, next_td);
}
USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl,
"uhci_update_intr_td_data_toggle: "
"pp %p toggle %x element ptr %x ptail %x",
pp, pp->pp_data_toggle, element_ptr, paddr_tail);
uhci_save_data_toggle(pp);
}