ehci.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 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* EHCI Host Controller Driver (EHCI)
*
* The EHCI driver is a software driver which interfaces to the Universal
* Serial Bus layer (USBA) and the Host Controller (HC). The interface to
* the Host Controller is defined by the EHCI Host Controller Interface.
*
* This file contains code for Auto-configuration and HCDI entry points.
*
* NOTE:
*
* Currently EHCI driver does not support the following features
*
* - usb 2.0 or high speed usb isochronous devices.
* - usb 1.1 or full speed usb isochronous devices behind usb 2.0
* or high speed hub.
* - Alternate QTD for short xfer condition is only used in Bulk xfers.
* - Frame Span Traversal Nodes (FSTN).
* - Bandwidth allocation scheme needs to be updated for FSTN and USB2.0
* or High speed hub with multiple TT implementation. Currently bandwidth
* allocation scheme assumes one TT per USB2.0 or High speed hub.
* - 64 bit addressing capability.
* - Programmable periodic frame list size like 256, 512, 1024.
* It supports only 1024 periodic frame list size.
*/
#include <sys/usb/hcd/ehci/ehcid.h>
#include <sys/usb/hcd/ehci/ehci_xfer.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>
/* Pointer to the state structure */
void *ehci_statep;
/* Number of instances */
#define EHCI_INSTS 1
/* Debugging information */
uint_t ehci_errmask = (uint_t)PRINT_MASK_ALL;
uint_t ehci_errlevel = USB_LOG_L2;
uint_t ehci_instance_debug = (uint_t)-1;
/* Enable all workarounds for VIA VT62x2 */
uint_t ehci_vt62x2_workaround = EHCI_VIA_WORKAROUNDS;
/*
* EHCI Auto-configuration entry points.
*
* Device operations (dev_ops) entries function prototypes.
*
* We use the hub cbops since all nexus ioctl operations defined so far will
* be executed by the root hub. The following are the Host Controller Driver
* (HCD) entry points.
*
* the open/close/ioctl functions call the corresponding usba_hubdi_*
* calls after looking up the dip thru the dev_t.
*/
static int ehci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int ehci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static int ehci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd);
static int ehci_info(dev_info_t *dip, ddi_info_cmd_t infocmd,
void *arg, void **result);
static int ehci_open(dev_t *devp, int flags, int otyp, cred_t *credp);
static int ehci_close(dev_t dev, int flag, int otyp, cred_t *credp);
static int ehci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode,
cred_t *credp, int *rvalp);
int usba_hubdi_root_hub_power(dev_info_t *dip, int comp, int level);
static struct cb_ops ehci_cb_ops = {
ehci_open, /* EHCI */
ehci_close, /* Close */
nodev, /* Strategy */
nodev, /* Print */
nodev, /* Dump */
nodev, /* Read */
nodev, /* Write */
ehci_ioctl, /* Ioctl */
nodev, /* Devmap */
nodev, /* Mmap */
nodev, /* Segmap */
nochpoll, /* Poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* Streamtab */
D_NEW | D_MP | D_HOTPLUG /* Driver compatibility flag */
};
static struct dev_ops ehci_ops = {
DEVO_REV, /* Devo_rev */
0, /* Refcnt */
ehci_info, /* Info */
nulldev, /* Identify */
nulldev, /* Probe */
ehci_attach, /* Attach */
ehci_detach, /* Detach */
ehci_reset, /* Reset */
&ehci_cb_ops, /* Driver operations */
&usba_hubdi_busops, /* Bus operations */
usba_hubdi_root_hub_power /* Power */
};
/*
* The USBA library must be loaded for this driver.
*/
static struct modldrv modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"USB EHCI Driver %I%", /* Name of the module. */
&ehci_ops, /* Driver ops */
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
int
_init(void)
{
int error;
/* Initialize the soft state structures */
if ((error = ddi_soft_state_init(&ehci_statep, sizeof (ehci_state_t),
EHCI_INSTS)) != 0) {
return (error);
}
/* Install the loadable module */
if ((error = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&ehci_statep);
}
return (error);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int error;
if ((error = mod_remove(&modlinkage)) == 0) {
/* Release per module resources */
ddi_soft_state_fini(&ehci_statep);
}
return (error);
}
/*
* EHCI Auto configuration entry points.
*/
/*
* ehci_attach:
*
* Description: Attach entry point is called by the Kernel.
* Allocates resources for each EHCI host controller instance.
* Initializes the EHCI Host Controller.
*
* Return : DDI_SUCCESS / DDI_FAILURE.
*/
static int
ehci_attach(dev_info_t *dip,
ddi_attach_cmd_t cmd)
{
int instance;
ehci_state_t *ehcip = NULL;
usba_hcdi_register_args_t hcdi_args;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
ehcip = ehci_obtain_state(dip);
return (ehci_cpr_resume(ehcip));
default:
return (DDI_FAILURE);
}
/* Get the instance and create soft state */
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(ehci_statep, instance) != 0) {
return (DDI_FAILURE);
}
ehcip = ddi_get_soft_state(ehci_statep, instance);
if (ehcip == NULL) {
return (DDI_FAILURE);
}
ehcip->ehci_flags = EHCI_ATTACH;
ehcip->ehci_log_hdl = usb_alloc_log_hdl(dip, "ehci", &ehci_errlevel,
&ehci_errmask, &ehci_instance_debug, 0);
ehcip->ehci_flags |= EHCI_ZALLOC;
/* Set host controller soft state to initialization */
ehcip->ehci_hc_soft_state = EHCI_CTLR_INIT_STATE;
USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl,
"ehcip = 0x%p", (void *)ehcip);
/* Initialize the DMA attributes */
ehci_set_dma_attributes(ehcip);
/* Save the dip and instance */
ehcip->ehci_dip = dip;
ehcip->ehci_instance = instance;
/* Initialize the DMA attributes */
ehci_create_stats(ehcip);
/* Create the qtd and qh pools */
if (ehci_allocate_pools(ehcip) != DDI_SUCCESS) {
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Initialize the isochronous resources */
if (ehci_isoc_init(ehcip) != DDI_SUCCESS) {
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Map the registers */
if (ehci_map_regs(ehcip) != DDI_SUCCESS) {
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Register interrupts */
if (ehci_register_intrs_and_init_mutex(ehcip) != DDI_SUCCESS) {
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
ehcip->ehci_flags |= EHCI_INTR;
mutex_enter(&ehcip->ehci_int_mutex);
/* Initialize the controller */
if (ehci_init_ctlr(ehcip) != DDI_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/*
* At this point, the hardware will be okay.
* Initialize the usba_hcdi structure
*/
ehcip->ehci_hcdi_ops = ehci_alloc_hcdi_ops(ehcip);
mutex_exit(&ehcip->ehci_int_mutex);
/*
* Make this HCD instance known to USBA
* (dma_attr must be passed for USBA busctl's)
*/
hcdi_args.usba_hcdi_register_version = HCDI_REGISTER_VERSION;
hcdi_args.usba_hcdi_register_dip = dip;
hcdi_args.usba_hcdi_register_ops = ehcip->ehci_hcdi_ops;
hcdi_args.usba_hcdi_register_dma_attr = &ehcip->ehci_dma_attr;
/*
* Priority and iblock_cookie are one and the same
* (However, retaining hcdi_soft_iblock_cookie for now
* assigning it w/ priority. In future all iblock_cookie
* could just go)
*/
hcdi_args.usba_hcdi_register_iblock_cookie =
(ddi_iblock_cookie_t)(uint64_t)ehcip->ehci_intr_pri;
if (usba_hcdi_register(&hcdi_args, 0) != DDI_SUCCESS) {
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
ehcip->ehci_flags |= EHCI_USBAREG;
mutex_enter(&ehcip->ehci_int_mutex);
if ((ehci_init_root_hub(ehcip)) != USB_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
mutex_exit(&ehcip->ehci_int_mutex);
/* Finally load the root hub driver */
if (ehci_load_root_hub_driver(ehcip) != USB_SUCCESS) {
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
ehcip->ehci_flags |= EHCI_RHREG;
/* Display information in the banner */
ddi_report_dev(dip);
mutex_enter(&ehcip->ehci_int_mutex);
/* Reset the ehci initialization flag */
ehcip->ehci_flags &= ~EHCI_ATTACH;
/* Print the Host Control's Operational registers */
ehci_print_caps(ehcip);
ehci_print_regs(ehcip);
(void) pci_report_pmcap(dip, PCI_PM_IDLESPEED, (void *)4000);
mutex_exit(&ehcip->ehci_int_mutex);
USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl,
"ehci_attach: dip = 0x%p done", (void *)dip);
return (DDI_SUCCESS);
}
/*
* ehci_detach:
*
* Description: Detach entry point is called by the Kernel.
* Deallocates all resource allocated.
* Unregisters the interrupt handler.
*
* Return : DDI_SUCCESS / DDI_FAILURE
*/
int
ehci_detach(dev_info_t *dip,
ddi_detach_cmd_t cmd)
{
ehci_state_t *ehcip = ehci_obtain_state(dip);
USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl, "ehci_detach:");
switch (cmd) {
case DDI_DETACH:
return (ehci_cleanup(ehcip));
case DDI_SUSPEND:
return (ehci_cpr_suspend(ehcip));
default:
return (DDI_FAILURE);
}
}
/*
* ehci_reset:
*
* Description: Reset entry point - called by the Kernel
* on the way down.
* Toshiba Tecra laptop has been observed to hang
* on soft reboot. The resetting ehci on the way
* down solves the problem.
*
* Return : DDI_SUCCESS / DDI_FAILURE
*/
/* ARGSUSED */
static int
ehci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
ehci_state_t *ehcip = ehci_obtain_state(dip);
mutex_enter(&ehcip->ehci_int_mutex);
/*
* To reset the host controller, the HCRESET bit should be set to one.
* Software should not set this bit to a one when the HCHalted bit in
* the USBSTS register is a zero. Attempting to reset an actively
* running host controller will result in undefined behavior.
* see EHCI SPEC. for more information.
*/
if (!(Get_OpReg(ehci_status) & EHCI_STS_HOST_CTRL_HALTED)) {
/* Stop the EHCI host controller */
Set_OpReg(ehci_command,
Get_OpReg(ehci_command) & ~EHCI_CMD_HOST_CTRL_RUN);
/*
* When this bit is set to 0, the Host Controller completes the
* current and any actively pipelined transactions on the USB
* and then halts. The Host Controller must halt within 16
* micro-frames after software clears the Run bit.
* The HC Halted bit in the status register indicates when the
* Host Controller has finished its pending pipelined
* transactions and has entered the stopped state.
*/
drv_usecwait(EHCI_RESET_TIMEWAIT);
}
/* Reset the EHCI host controller */
Set_OpReg(ehci_command,
Get_OpReg(ehci_command) | EHCI_CMD_HOST_CTRL_RESET);
mutex_exit(&ehcip->ehci_int_mutex);
return (DDI_SUCCESS);
}
/*
* ehci_info:
*/
/* ARGSUSED */
static int
ehci_info(dev_info_t *dip,
ddi_info_cmd_t infocmd,
void *arg,
void **result)
{
dev_t dev;
ehci_state_t *ehcip;
int instance;
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
dev = (dev_t)arg;
instance = EHCI_UNIT(dev);
ehcip = ddi_get_soft_state(ehci_statep, instance);
if (ehcip != NULL) {
*result = (void *)ehcip->ehci_dip;
if (*result != NULL) {
error = DDI_SUCCESS;
}
} else {
*result = NULL;
}
break;
case DDI_INFO_DEVT2INSTANCE:
dev = (dev_t)arg;
instance = EHCI_UNIT(dev);
*result = (void *)(uintptr_t)instance;
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* EHCI CB_OPS entry points.
*/
static dev_info_t *
ehci_get_dip(dev_t dev)
{
int instance = EHCI_UNIT(dev);
ehci_state_t *ehcip = ddi_get_soft_state(ehci_statep, instance);
if (ehcip) {
return (ehcip->ehci_dip);
} else {
return (NULL);
}
}
static int
ehci_open(dev_t *devp,
int flags,
int otyp,
cred_t *credp)
{
dev_info_t *dip = ehci_get_dip(*devp);
return (usba_hubdi_open(dip, devp, flags, otyp, credp));
}
static int
ehci_close(dev_t dev,
int flag,
int otyp,
cred_t *credp)
{
dev_info_t *dip = ehci_get_dip(dev);
return (usba_hubdi_close(dip, dev, flag, otyp, credp));
}
static int
ehci_ioctl(dev_t dev,
int cmd,
intptr_t arg,
int mode,
cred_t *credp,
int *rvalp)
{
dev_info_t *dip = ehci_get_dip(dev);
return (usba_hubdi_ioctl(dip,
dev, cmd, arg, mode, credp, rvalp));
}
/*
* EHCI Interrupt Handler entry point.
*/
/*
* ehci_intr:
*
* EHCI (EHCI) interrupt handling routine.
*/
uint_t
ehci_intr(caddr_t arg)
{
uint_t intr;
ehci_state_t *ehcip = (ehci_state_t *)arg;
USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"ehci_intr: Interrupt occurred");
/* Get the ehci global mutex */
mutex_enter(&ehcip->ehci_int_mutex);
/*
* Now process the actual ehci interrupt events that caused
* invocation of this ehci interrupt handler.
*/
intr = (Get_OpReg(ehci_status) & Get_OpReg(ehci_interrupt));
/* Update kstat values */
ehci_do_intrs_stats(ehcip, intr);
/*
* We could have gotten a spurious interrupts. If so, do not
* claim it. This is quite possible on some architectures
* where more than one PCI slots share the IRQs. If so, the
* associated driver's interrupt routine may get called even
* if the interrupt is not meant for them.
*
* By unclaiming the interrupt, the other driver gets chance
* to service its interrupt.
*/
if (!intr) {
mutex_exit(&ehcip->ehci_int_mutex);
return (DDI_INTR_UNCLAIMED);
}
/* Acknowledge the interrupt */
Set_OpReg(ehci_status, intr);
if (ehcip->ehci_hc_soft_state == EHCI_CTLR_ERROR_STATE) {
mutex_exit(&ehcip->ehci_int_mutex);
return (DDI_INTR_CLAIMED);
}
USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"Interrupt status 0x%x", intr);
/*
* If necessary broadcast that an interrupt has occured. This
* is only necessary during controller init.
*/
if (ehcip->ehci_flags & EHCI_CV_INTR) {
ehcip->ehci_flags &= ~EHCI_CV_INTR;
cv_broadcast(&ehcip->ehci_async_schedule_advance_cv);
}
/* Check for Frame List Rollover */
if (intr & EHCI_INTR_FRAME_LIST_ROLLOVER) {
USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"ehci_intr: Frame List Rollover");
ehci_handle_frame_list_rollover(ehcip);
/* VIA VT6202 looses EHCI_INTR_USB interrupts, workaround. */
if ((ehcip->ehci_vendor_id == PCI_VENDOR_VIA) &&
(ehci_vt62x2_workaround & EHCI_VIA_LOST_INTERRUPTS)) {
ehcip->ehci_missed_intr_sts |= EHCI_INTR_USB;
}
}
/* Check for Advance on Asynchronous Schedule */
if (intr & EHCI_INTR_ASYNC_ADVANCE) {
USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"ehci_intr: Asynchronous Schedule Advance Notification");
/* Disable async list advance interrupt */
Set_OpReg(ehci_interrupt,
(Get_OpReg(ehci_interrupt) & ~EHCI_INTR_ASYNC_ADVANCE));
/*
* Call cv_broadcast on every this interrupt to wakeup
* all the threads that are waiting the async list advance
* event.
*/
cv_broadcast(&ehcip->ehci_async_schedule_advance_cv);
}
/* Always process completed itds */
ehci_traverse_active_isoc_list(ehcip);
/*
* Check for any USB transaction completion notification. Also
* process any missed USB transaction completion interrupts.
*/
if ((intr & EHCI_INTR_USB) || (intr & EHCI_INTR_USB_ERROR) ||
(ehcip->ehci_missed_intr_sts & EHCI_INTR_USB) ||
(ehcip->ehci_missed_intr_sts & EHCI_INTR_USB_ERROR)) {
USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"ehci_intr: USB Transaction Completion Notification");
/* Clear missed interrupts */
if (ehcip->ehci_missed_intr_sts) {
ehcip->ehci_missed_intr_sts = 0;
}
/* Process completed qtds */
ehci_traverse_active_qtd_list(ehcip);
}
/* Process endpoint reclamation list */
if (ehcip->ehci_reclaim_list) {
ehci_handle_endpoint_reclaimation(ehcip);
}
/* Check for Host System Error */
if (intr & EHCI_INTR_HOST_SYSTEM_ERROR) {
USB_DPRINTF_L2(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"ehci_intr: Unrecoverable error");
ehci_handle_ue(ehcip);
}
/*
* Read interrupt status register to make sure that any PIO
* store to clear the ISR has made it on the PCI bus before
* returning from its interrupt handler.
*/
(void) Get_OpReg(ehci_status);
/* Release the ehci global mutex */
mutex_exit(&ehcip->ehci_int_mutex);
USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl,
"Interrupt handling completed");
return (DDI_INTR_CLAIMED);
}
/*
* EHCI HCDI entry points
*
* The Host Controller Driver Interfaces (HCDI) are the software interfaces
* between the Universal Serial Bus Layer (USBA) and the Host Controller
* Driver (HCD). The HCDI interfaces or entry points are subject to change.
*/
/*
* ehci_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
ehci_hcdi_pipe_open(
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
usb_ep_descr_t *epdt = &ph->p_ep;
int rval, error = USB_SUCCESS;
int kmflag = (flags & USB_FLAGS_SLEEP) ?
KM_SLEEP : KM_NOSLEEP;
uchar_t smask = 0;
uchar_t cmask = 0;
uint_t pnode = 0;
ehci_pipe_private_t *pp;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_open: addr = 0x%x, ep%d",
ph->p_usba_device->usb_addr,
epdt->bEndpointAddress & USB_EP_NUM_MASK);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
mutex_exit(&ehcip->ehci_int_mutex);
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check and handle root hub pipe open.
*/
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
mutex_enter(&ehcip->ehci_int_mutex);
error = ehci_handle_root_hub_pipe_open(ph, flags);
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* Opening of other pipes excluding root hub pipe are
* handled below. Check whether pipe is already opened.
*/
if (ph->p_hcd_private) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_open: Pipe is already opened");
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
* millisecond frame period & usually it will be 20% 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 80% of the total frame time is reserved for periodic
* transfers.
*/
if (EHCI_PERIODIC_ENDPOINT(epdt)) {
mutex_enter(&ehcip->ehci_int_mutex);
mutex_enter(&ph->p_mutex);
error = ehci_allocate_bandwidth(ehcip,
ph, &pnode, &smask, &cmask);
if (error != USB_SUCCESS) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_open: Bandwidth allocation failed");
mutex_exit(&ph->p_mutex);
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
mutex_exit(&ph->p_mutex);
mutex_exit(&ehcip->ehci_int_mutex);
}
/* Create the HCD pipe private structure */
pp = kmem_zalloc(sizeof (ehci_pipe_private_t), kmflag);
/*
* Return failure if ehci pipe private
* structure allocation fails.
*/
if (pp == NULL) {
mutex_enter(&ehcip->ehci_int_mutex);
/* Deallocate bandwidth */
if (EHCI_PERIODIC_ENDPOINT(epdt)) {
mutex_enter(&ph->p_mutex);
ehci_deallocate_bandwidth(ehcip,
ph, pnode, smask, cmask);
mutex_exit(&ph->p_mutex);
}
mutex_exit(&ehcip->ehci_int_mutex);
return (USB_NO_RESOURCES);
}
mutex_enter(&ehcip->ehci_int_mutex);
/* Save periodic nodes */
pp->pp_pnode = pnode;
/* Save start and complete split mask values */
pp->pp_smask = smask;
pp->pp_cmask = cmask;
/* Create prototype for xfer completion condition variable */
cv_init(&pp->pp_xfer_cmpl_cv, NULL, CV_DRIVER, NULL);
/* Set the state of pipe as idle */
pp->pp_state = EHCI_PIPE_STATE_IDLE;
/* Store a pointer to the pipe handle */
pp->pp_pipe_handle = ph;
mutex_enter(&ph->p_mutex);
/* Store the pointer in the pipe handle */
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);
/* Allocate the host controller endpoint descriptor */
pp->pp_qh = ehci_alloc_qh(ehcip, ph, NULL);
/* Initialize the halting flag */
pp->pp_halt_state = EHCI_HALT_STATE_FREE;
/* Create prototype for halt completion condition variable */
cv_init(&pp->pp_halt_cmpl_cv, NULL, CV_DRIVER, NULL);
/* Isoch does not use QH, so ignore this */
if ((pp->pp_qh == NULL) && !(EHCI_ISOC_ENDPOINT(epdt))) {
ASSERT(pp->pp_qh == NULL);
USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_open: QH allocation failed");
mutex_enter(&ph->p_mutex);
/* Deallocate bandwidth */
if (EHCI_PERIODIC_ENDPOINT(epdt)) {
ehci_deallocate_bandwidth(ehcip,
ph, pnode, smask, cmask);
}
/* Destroy the xfer completion condition variable */
cv_destroy(&pp->pp_xfer_cmpl_cv);
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
kmem_free(ph->p_hcd_private, sizeof (ehci_pipe_private_t));
/*
* Set the private structure in the
* pipe handle equal to NULL.
*/
ph->p_hcd_private = NULL;
mutex_exit(&ph->p_mutex);
mutex_exit(&ehcip->ehci_int_mutex);
return (USB_NO_RESOURCES);
}
/*
* Isoch does not use QH so no need to
* restore data toggle or insert QH
*/
if (!(EHCI_ISOC_ENDPOINT(epdt))) {
/* Restore the data toggle information */
ehci_restore_data_toggle(ehcip, ph);
}
/*
* Insert the endpoint onto the host controller's
* appropriate endpoint list. The host controller
* will not schedule this endpoint and will not have
* any QTD's to process. It will also update the pipe count.
*/
ehci_insert_qh(ehcip, ph);
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_open: ph = 0x%p", (void *)ph);
ehcip->ehci_open_pipe_count++;
mutex_exit(&ehcip->ehci_int_mutex);
return (USB_SUCCESS);
}
/*
* ehci_hcdi_pipe_close:
*
* Member of HCD Ops structure and called during the client specific pipe
* close. Remove the pipe and the data structure representing the device.
* Deallocate bandwidth for the pipe if it is a interrupt or isochronous
* endpoint.
*/
/* ARGSUSED */
int
ehci_hcdi_pipe_close(
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
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_ep_descr_t *eptd = &ph->p_ep;
int error = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_close: addr = 0x%x, ep%d",
ph->p_usba_device->usb_addr,
eptd->bEndpointAddress & USB_EP_NUM_MASK);
/* Check and handle root hub pipe close */
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
mutex_enter(&ehcip->ehci_int_mutex);
error = ehci_handle_root_hub_pipe_close(ph);
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
ASSERT(ph->p_hcd_private != NULL);
mutex_enter(&ehcip->ehci_int_mutex);
/* Set pipe state to pipe close */
pp->pp_state = EHCI_PIPE_STATE_CLOSE;
ehci_pipe_cleanup(ehcip, ph);
/*
* Remove the endpoint descriptor from Host
* Controller's appropriate endpoint list.
*/
ehci_remove_qh(ehcip, pp, B_TRUE);
/* Deallocate bandwidth */
if (EHCI_PERIODIC_ENDPOINT(eptd)) {
mutex_enter(&ph->p_mutex);
ehci_deallocate_bandwidth(ehcip, ph, pp->pp_pnode,
pp->pp_smask, pp->pp_cmask);
mutex_exit(&ph->p_mutex);
}
mutex_enter(&ph->p_mutex);
/* Destroy the xfer completion condition variable */
cv_destroy(&pp->pp_xfer_cmpl_cv);
/* Destory halt completion condition variable */
cv_destroy(&pp->pp_halt_cmpl_cv);
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
kmem_free(ph->p_hcd_private, sizeof (ehci_pipe_private_t));
ph->p_hcd_private = NULL;
mutex_exit(&ph->p_mutex);
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_close: ph = 0x%p", (void *)ph);
ehcip->ehci_open_pipe_count--;
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* ehci_hcdi_pipe_reset:
*/
/* ARGSUSED */
int
ehci_hcdi_pipe_reset(
usba_pipe_handle_data_t *ph,
usb_flags_t usb_flags)
{
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;
int error = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_reset:");
/*
* Check and handle root hub pipe reset.
*/
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
error = ehci_handle_root_hub_pipe_reset(ph, usb_flags);
return (error);
}
mutex_enter(&ehcip->ehci_int_mutex);
/* Set pipe state to pipe reset */
pp->pp_state = EHCI_PIPE_STATE_RESET;
ehci_pipe_cleanup(ehcip, ph);
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* ehci_hcdi_pipe_ctrl_xfer:
*/
int
ehci_hcdi_pipe_ctrl_xfer(
usba_pipe_handle_data_t *ph,
usb_ctrl_req_t *ctrl_reqp,
usb_flags_t usb_flags)
{
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;
int rval;
int error = USB_SUCCESS;
ehci_trans_wrapper_t *tw;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_ctrl_xfer: ph = 0x%p reqp = 0x%p flags = %x",
(void *)ph, ctrl_reqp, usb_flags);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
mutex_exit(&ehcip->ehci_int_mutex);
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check and handle root hub control request.
*/
if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) {
error = ehci_handle_root_hub_request(ehcip, ph, ctrl_reqp);
return (error);
}
mutex_enter(&ehcip->ehci_int_mutex);
/*
* Check whether pipe is in halted state.
*/
if (pp->pp_state == EHCI_PIPE_STATE_ERROR) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_ctrl_xfer: "
"Pipe is in error state, need pipe reset to continue");
mutex_exit(&ehcip->ehci_int_mutex);
return (USB_FAILURE);
}
/* Allocate a transfer wrapper */
if ((tw = ehci_allocate_ctrl_resources(ehcip, pp, ctrl_reqp,
usb_flags)) == NULL) {
error = USB_NO_RESOURCES;
} else {
/* Insert the qtd's on the endpoint */
ehci_insert_ctrl_req(ehcip, ph, ctrl_reqp, tw, usb_flags);
}
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* ehci_hcdi_bulk_transfer_size:
*
* Return maximum bulk transfer size
*/
/* ARGSUSED */
int
ehci_hcdi_bulk_transfer_size(
usba_device_t *usba_device,
size_t *size)
{
ehci_state_t *ehcip = ehci_obtain_state(
usba_device->usb_root_hub_dip);
int rval;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_bulk_transfer_size:");
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
mutex_exit(&ehcip->ehci_int_mutex);
if (rval != USB_SUCCESS) {
return (rval);
}
/* VIA VT6202 may not handle bigger xfers well, workaround. */
if ((ehcip->ehci_vendor_id == PCI_VENDOR_VIA) &&
(ehci_vt62x2_workaround & EHCI_VIA_REDUCED_MAX_BULK_XFER_SIZE)) {
*size = EHCI_VIA_MAX_BULK_XFER_SIZE;
} else {
*size = EHCI_MAX_BULK_XFER_SIZE;
}
return (USB_SUCCESS);
}
/*
* ehci_hcdi_pipe_bulk_xfer:
*/
int
ehci_hcdi_pipe_bulk_xfer(
usba_pipe_handle_data_t *ph,
usb_bulk_req_t *bulk_reqp,
usb_flags_t usb_flags)
{
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;
int rval, error = USB_SUCCESS;
ehci_trans_wrapper_t *tw;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_bulk_xfer: ph = 0x%p reqp = 0x%p flags = %x",
(void *)ph, bulk_reqp, usb_flags);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
if (rval != USB_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}
/*
* Check whether pipe is in halted state.
*/
if (pp->pp_state == EHCI_PIPE_STATE_ERROR) {
USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_bulk_xfer:"
"Pipe is in error state, need pipe reset to continue");
mutex_exit(&ehcip->ehci_int_mutex);
return (USB_FAILURE);
}
/* Allocate a transfer wrapper */
if ((tw = ehci_allocate_bulk_resources(ehcip, pp, bulk_reqp,
usb_flags)) == NULL) {
error = USB_NO_RESOURCES;
} else {
/* Add the QTD into the Host Controller's bulk list */
ehci_insert_bulk_req(ehcip, ph, bulk_reqp, tw, usb_flags);
}
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* ehci_hcdi_pipe_intr_xfer:
*/
int
ehci_hcdi_pipe_intr_xfer(
usba_pipe_handle_data_t *ph,
usb_intr_req_t *intr_reqp,
usb_flags_t usb_flags)
{
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
int pipe_dir, rval, error = USB_SUCCESS;
ehci_trans_wrapper_t *tw;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_intr_xfer: ph = 0x%p reqp = 0x%p flags = %x",
(void *)ph, intr_reqp, usb_flags);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
if (rval != USB_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}
/* Get the pipe direction */
pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
if (pipe_dir == USB_EP_DIR_IN) {
error = ehci_start_periodic_pipe_polling(ehcip, ph,
(usb_opaque_t)intr_reqp, usb_flags);
} else {
/* Allocate transaction resources */
if ((tw = ehci_allocate_intr_resources(ehcip, ph,
intr_reqp, usb_flags)) == NULL) {
error = USB_NO_RESOURCES;
} else {
ehci_insert_intr_req(ehcip,
(ehci_pipe_private_t *)ph->p_hcd_private,
tw, usb_flags);
}
}
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* ehci_hcdi_pipe_stop_intr_polling()
*/
int
ehci_hcdi_pipe_stop_intr_polling(
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
int error = USB_SUCCESS;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_stop_intr_polling: ph = 0x%p fl = 0x%x",
(void *)ph, flags);
mutex_enter(&ehcip->ehci_int_mutex);
error = ehci_stop_periodic_pipe_polling(ehcip, ph, flags);
mutex_exit(&ehcip->ehci_int_mutex);
return (error);
}
/*
* ehci_hcdi_get_current_frame_number:
*
* Return the current usb frame number
*/
usb_frame_number_t
ehci_hcdi_get_current_frame_number(usba_device_t *usba_device)
{
ehci_state_t *ehcip = ehci_obtain_state(
usba_device->usb_root_hub_dip);
usb_frame_number_t frame_number;
int rval;
ehcip = ehci_obtain_state(usba_device->usb_root_hub_dip);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
if (rval != USB_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}
frame_number = ehci_get_current_frame_number(ehcip);
mutex_exit(&ehcip->ehci_int_mutex);
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_get_current_frame_number: "
"Current frame number 0x%llx", frame_number);
return (frame_number);
}
/*
* ehci_hcdi_get_max_isoc_pkts:
*
* Return maximum isochronous packets per usb isochronous request
*/
uint_t
ehci_hcdi_get_max_isoc_pkts(usba_device_t *usba_device)
{
ehci_state_t *ehcip = ehci_obtain_state(
usba_device->usb_root_hub_dip);
uint_t max_isoc_pkts_per_request;
int rval;
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
mutex_exit(&ehcip->ehci_int_mutex);
if (rval != USB_SUCCESS) {
return (rval);
}
max_isoc_pkts_per_request = EHCI_MAX_ISOC_PKTS_PER_XFER;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_get_max_isoc_pkts: maximum isochronous"
"packets per usb isochronous request = 0x%x",
max_isoc_pkts_per_request);
return (max_isoc_pkts_per_request);
}
/*
* ehci_hcdi_pipe_isoc_xfer:
*/
int
ehci_hcdi_pipe_isoc_xfer(
usba_pipe_handle_data_t *ph,
usb_isoc_req_t *isoc_reqp,
usb_flags_t usb_flags)
{
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
int pipe_dir, rval;
ehci_isoc_xwrapper_t *itw;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_isoc_xfer: ph = 0x%p reqp = 0x%p flags = 0x%x",
(void *)ph, isoc_reqp, usb_flags);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
if (rval != USB_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}
/* Get the isochronous pipe direction */
pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK;
if (pipe_dir == USB_EP_DIR_IN) {
rval = ehci_start_periodic_pipe_polling(ehcip, ph,
(usb_opaque_t)isoc_reqp, usb_flags);
} else {
/* Allocate transaction resources */
if ((itw = ehci_allocate_isoc_resources(ehcip, ph,
isoc_reqp, usb_flags)) == NULL) {
rval = USB_NO_RESOURCES;
} else {
rval = ehci_insert_isoc_req(ehcip,
(ehci_pipe_private_t *)ph->p_hcd_private,
itw, usb_flags);
}
}
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}
/*
* ehci_hcdi_pipe_stop_isoc_polling()
*/
/*ARGSUSED*/
int
ehci_hcdi_pipe_stop_isoc_polling(
usba_pipe_handle_data_t *ph,
usb_flags_t flags)
{
ehci_state_t *ehcip = ehci_obtain_state(
ph->p_usba_device->usb_root_hub_dip);
int rval;
USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl,
"ehci_hcdi_pipe_stop_isoc_polling: ph = 0x%p fl = 0x%x",
(void *)ph, flags);
mutex_enter(&ehcip->ehci_int_mutex);
rval = ehci_state_is_operational(ehcip);
if (rval != USB_SUCCESS) {
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}
rval = ehci_stop_periodic_pipe_polling(ehcip, ph, flags);
mutex_exit(&ehcip->ehci_int_mutex);
return (rval);
}