/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2009 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 file contains code for Auto-configuration and HCDI entry points.
*
* NOTE:
*
* Currently EHCI driver does not support the following features
*
* - 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.
*/
/* Pointer to the state structure */
void *ehci_statep;
/* Number of instances */
/* Debugging information */
/*
* Tunable to ensure host controller goes off even if a keyboard is attached.
*/
/* Enable all workarounds for VIA VT62x2 */
/*
* 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.
*
* calls after looking up the dip thru the dev_t.
*/
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 */
};
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 */
ehci_quiesce /* Quiesce */
};
/*
* The USBA library must be loaded for this driver.
*/
&mod_driverops, /* Type of module. This one is a driver */
"USB EHCI Driver", /* Name of the module. */
&ehci_ops, /* Driver ops */
};
};
int
_init(void)
{
int error;
/* Initialize the soft state structures */
EHCI_INSTS)) != 0) {
return (error);
}
/* Install the loadable module */
}
return (error);
}
int
{
}
int
_fini(void)
{
int error;
/* Release per module resources */
}
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
{
int instance;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (ehci_cpr_resume(ehcip));
default:
return (DDI_FAILURE);
}
/* Get the instance and create soft state */
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
&ehci_errmask, &ehci_instance_debug, 0);
/* Set host controller soft state to initialization */
"ehcip = 0x%p", (void *)ehcip);
/* Save the dip and instance */
/* Map the registers */
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Get the ehci chip vendor and device id */
/* Initialize the DMA attributes */
/* Initialize kstat structures */
/* Create the qtd and qh pools */
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Initialize the isochronous resources */
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Register interrupts */
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Initialize the controller */
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/*
* At this point, the hardware will be okay.
* Initialize the usba_hcdi structure
*/
/*
* Make this HCD instance known to USBA
* (dma_attr must be passed for USBA busctl's)
*/
/*
* 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)
*/
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Finally load the root hub driver */
(void) ehci_cleanup(ehcip);
return (DDI_FAILURE);
}
/* Display information in the banner */
/* Reset the ehci initialization flag */
/* Print the Host Control's Operational registers */
"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
{
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
{
#if defined(__sparc)
/*
* Don't reset the host controller on SPARC, for OBP needs Solaris
* to continue to provide keyboard support after shutdown of SPARC,
* or the keyboard connected to a USB 2.0 port will not work after
* that. The incomplete reset problem on Toshiba Tecra laptop is
* specific to Tecra laptop or BIOS, not present on SPARC. The SPARC
* OBP guarantees good reset behavior during startup.
*/
return (DDI_SUCCESS);
#else
/*
* 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.
*/
/* Stop the EHCI host controller */
/*
* 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.
*/
}
/* Reset the EHCI host controller */
return (DDI_SUCCESS);
#endif
}
/*
* quiesce(9E) entry point.
*
* This function is called when the system is single-threaded at high
* PIL with preemption disabled. Therefore, this function must not be
* blocked.
*
* This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure.
* DDI_FAILURE indicates an error condition and should almost never happen.
*/
static int
{
return (DDI_FAILURE);
#ifndef lint
#endif
/*
* 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.
*/
/* Stop the EHCI host controller */
/*
* 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.
*/
}
/* Reset the EHCI host controller */
#ifndef lint
#endif
return (DDI_SUCCESS);
}
/*
* ehci_info:
*/
/* ARGSUSED */
static int
void *arg,
void **result)
{
int instance;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* EHCI CB_OPS entry points.
*/
static dev_info_t *
{
if (ehcip) {
} else {
return (NULL);
}
}
static int
int flags,
int otyp,
{
}
static int
int flag,
int otyp,
{
}
static int
int cmd,
int mode,
int *rvalp)
{
return (usba_hubdi_ioctl(dip,
}
/*
* EHCI Interrupt Handler entry point.
*/
/*
* ehci_intr:
*
* EHCI (EHCI) interrupt handling routine.
*/
{
"ehci_intr: Interrupt occurred, arg1 0x%p arg2 0x%p",
/* Get the ehci global mutex */
/* Any interrupt is not handled for the suspended device. */
return (DDI_INTR_UNCLAIMED);
}
/*
* Now process the actual ehci interrupt events that caused
* invocation of this ehci interrupt handler.
*/
/* Update kstat values */
/*
* 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) {
return (DDI_INTR_UNCLAIMED);
}
/* Acknowledge the interrupt */
return (DDI_INTR_CLAIMED);
}
"Interrupt status 0x%x", intr);
/*
* If necessary broadcast that an interrupt has occured. This
* is only necessary during controller init.
*/
}
/* Check for Frame List Rollover */
if (intr & EHCI_INTR_FRAME_LIST_ROLLOVER) {
"ehci_intr: Frame List Rollover");
/* VIA VT6202 looses EHCI_INTR_USB interrupts, workaround. */
}
}
/* Check for Advance on Asynchronous Schedule */
if (intr & EHCI_INTR_ASYNC_ADVANCE) {
"ehci_intr: Asynchronous Schedule Advance Notification");
/* Disable async list advance interrupt */
/*
* Call cv_broadcast on every this interrupt to wakeup
* all the threads that are waiting the async list advance
* event.
*/
}
/* Always process completed itds */
/*
* Check for any USB transaction completion notification. Also
* process any missed USB transaction completion interrupts.
*/
"ehci_intr: USB Transaction Completion Notification");
/* Clear missed interrupts */
if (ehcip->ehci_missed_intr_sts) {
ehcip->ehci_missed_intr_sts = 0;
}
/* Process completed qtds */
}
/* Process endpoint reclamation list */
if (ehcip->ehci_reclaim_list) {
}
/* Check for Host System Error */
if (intr & EHCI_INTR_HOST_SYSTEM_ERROR) {
"ehci_intr: Unrecoverable error");
}
/*
* 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 */
"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: addr = 0x%x, ep%d",
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check and handle root hub pipe open.
*/
return (error);
}
/*
* Opening of other pipes excluding root hub pipe are
* handled below. Check whether pipe is already opened.
*/
if (ph->p_hcd_private) {
"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)) {
if (error != USB_SUCCESS) {
"ehci_hcdi_pipe_open: Bandwidth allocation failed");
return (error);
}
}
/* Create the HCD pipe private structure */
/*
* Return failure if ehci pipe private
* structure allocation fails.
*/
/* Deallocate bandwidth */
if (EHCI_PERIODIC_ENDPOINT(epdt)) {
}
return (USB_NO_RESOURCES);
}
/* Save periodic nodes */
/* Save start and complete split mask values */
/* Create prototype for xfer completion condition variable */
/* Set the state of pipe as idle */
/* Store a pointer to the pipe handle */
/* Store the pointer in the pipe handle */
/* Store a copy of the pipe policy */
/* Allocate the host controller endpoint descriptor */
/* Initialize the halting flag */
/* Create prototype for halt completion condition variable */
/* Isoch does not use QH, so ignore this */
"ehci_hcdi_pipe_open: QH allocation failed");
/* Deallocate bandwidth */
if (EHCI_PERIODIC_ENDPOINT(epdt)) {
}
/* Destroy the xfer completion condition variable */
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
/*
* Set the private structure in the
* pipe handle equal to NULL.
*/
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 */
}
/*
* 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_hcdi_pipe_open: ph = 0x%p", (void *)ph);
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: addr = 0x%x, ep%d",
/* Check and handle root hub pipe close */
return (error);
}
/* Set pipe state to pipe close */
/*
* Remove the endpoint descriptor from Host
* Controller's appropriate endpoint list.
*/
/* Deallocate bandwidth */
if (EHCI_PERIODIC_ENDPOINT(eptd)) {
}
/* Destroy the xfer completion condition variable */
/* Destory halt completion condition variable */
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
"ehci_hcdi_pipe_close: ph = 0x%p", (void *)ph);
return (error);
}
/*
* ehci_hcdi_pipe_reset:
*/
/* ARGSUSED */
int
{
"ehci_hcdi_pipe_reset:");
/*
* Check and handle root hub pipe reset.
*/
return (error);
}
/* Set pipe state to pipe reset */
return (error);
}
/*
* ehci_hcdi_pipe_reset_data_toggle:
*/
void
{
"ehci_hcdi_pipe_reset_data_toggle:");
DATA0);
}
/*
* ehci_hcdi_pipe_ctrl_xfer:
*/
int
{
int rval;
"ehci_hcdi_pipe_ctrl_xfer: ph = 0x%p reqp = 0x%p flags = %x",
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check and handle root hub control request.
*/
return (error);
}
/*
* Check whether pipe is in halted state.
*/
"ehci_hcdi_pipe_ctrl_xfer: "
"Pipe is in error state, need pipe reset to continue");
return (USB_FAILURE);
}
/* Allocate a transfer wrapper */
} else {
/* Insert the qtd's on the endpoint */
}
return (error);
}
/*
* ehci_hcdi_bulk_transfer_size:
*
* Return maximum bulk transfer size
*/
/* ARGSUSED */
int
{
int rval;
"ehci_hcdi_bulk_transfer_size:");
if (rval != USB_SUCCESS) {
return (rval);
}
/* VIA VT6202 may not handle bigger xfers well, workaround. */
} else {
}
return (USB_SUCCESS);
}
/*
* ehci_hcdi_pipe_bulk_xfer:
*/
int
{
"ehci_hcdi_pipe_bulk_xfer: ph = 0x%p reqp = 0x%p flags = %x",
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check whether pipe is in halted state.
*/
"ehci_hcdi_pipe_bulk_xfer:"
"Pipe is in error state, need pipe reset to continue");
return (USB_FAILURE);
}
/* Allocate a transfer wrapper */
} else {
/* Add the QTD into the Host Controller's bulk list */
}
return (error);
}
/*
* ehci_hcdi_pipe_intr_xfer:
*/
int
{
"ehci_hcdi_pipe_intr_xfer: ph = 0x%p reqp = 0x%p flags = %x",
if (rval != USB_SUCCESS) {
return (rval);
}
/* Get the pipe direction */
if (pipe_dir == USB_EP_DIR_IN) {
} else {
/* Allocate transaction resources */
} else {
}
}
return (error);
}
/*
* ehci_hcdi_pipe_stop_intr_polling()
*/
int
{
"ehci_hcdi_pipe_stop_intr_polling: ph = 0x%p fl = 0x%x",
return (error);
}
/*
* ehci_hcdi_get_current_frame_number:
*
* Get the current usb frame number.
* Return whether the request is handled successfully.
*/
int
{
int rval;
if (rval != USB_SUCCESS) {
return (rval);
}
"ehci_hcdi_get_current_frame_number: "
"Current frame number 0x%llx", (unsigned long long)(*frame_number));
return (rval);
}
/*
* ehci_hcdi_get_max_isoc_pkts:
*
* Get maximum isochronous packets per usb isochronous request.
* Return whether the request is handled successfully.
*/
int
{
int rval;
if (rval != USB_SUCCESS) {
return (rval);
}
"ehci_hcdi_get_max_isoc_pkts: maximum isochronous"
"packets per usb isochronous request = 0x%x",
return (rval);
}
/*
* ehci_hcdi_pipe_isoc_xfer:
*/
int
{
"ehci_hcdi_pipe_isoc_xfer: ph = 0x%p reqp = 0x%p flags = 0x%x",
if (rval != USB_SUCCESS) {
return (rval);
}
/* Get the isochronous pipe direction */
if (pipe_dir == USB_EP_DIR_IN) {
} else {
/* Allocate transaction resources */
} else {
}
}
return (rval);
}
/*
* ehci_hcdi_pipe_stop_isoc_polling()
*/
/*ARGSUSED*/
int
{
int rval;
"ehci_hcdi_pipe_stop_isoc_polling: ph = 0x%p fl = 0x%x",
if (rval != USB_SUCCESS) {
return (rval);
}
return (rval);
}