ohci.c revision b3001def2a41995242feff3e584ad9ead06d7b1b
/*
* 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 2006 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* Open Host Controller Driver (OHCI)
*
* The USB Open Host Controller driver is a software driver which interfaces
* to the Universal Serial Bus layer (USBA) and the USB Open Host Controller.
* The interface to USB Open Host Controller is defined by the OpenHCI Host
* Controller Interface.
*
* NOTE:
*
* Currently OHCI driver does not support the following features
*
* - Handle request with multiple TDs under short xfer conditions except for
* bulk transfers.
*/
/* Pointer to the state structure */
static void *ohci_statep;
/* Number of instances */
#define OHCI_INSTS 1
/* Adjustable variables for the size of the pools */
/*
* Initialize the values which are used for setting up head pointers for
* the 32ms scheduling lists which starts from the HCCA.
*/
0x2, 0xa, 0x6, 0xe,
0x1, 0x9, 0x5, 0xd,
0x3, 0xb, 0x7, 0xf};
/* Debugging information */
/*
* OHCI MSI tunable:
*
* By default MSI is enabled on all supported platforms.
*/
/*
* HCDI entry points
*
* The Host Controller Driver Interfaces (HCDI) are the software interfaces
* between the Universal Serial Bus Driver (USBA) and the Host Controller
* Driver (HCD). The HCDI interfaces or entry points are subject to change.
*/
static int ohci_hcdi_pipe_open(
static int ohci_hcdi_pipe_close(
static int ohci_hcdi_pipe_reset(
static int ohci_hcdi_pipe_ctrl_xfer(
static int ohci_hcdi_bulk_transfer_size(
static int ohci_hcdi_pipe_bulk_xfer(
static int ohci_hcdi_pipe_intr_xfer(
static int ohci_hcdi_pipe_stop_intr_polling(
static uint_t ohci_hcdi_get_max_isoc_pkts(
static int ohci_hcdi_pipe_isoc_xfer(
static int ohci_hcdi_pipe_stop_isoc_polling(
/*
* Internal Function Prototypes
*/
/* Host Controller Driver (HCD) initialization functions */
static void ohci_decode_ddi_dma_addr_bind_handle_result(
int result);
static int ohci_register_intrs_and_init_mutex(
int intr_type);
static void ohci_build_interrupt_lattice(
static usba_hcdi_ops_t *ohci_alloc_hcdi_ops(
/* Host Controller Driver (HCD) deinitialization functions */
/* Bandwidth Allocation functions */
static int ohci_compute_total_bandwidth(
static int ohci_adjust_polling_interval(
static uint_t ohci_hcca_intr_index(
static uint_t ohci_hcca_leaf_index(
/* Endpoint Descriptor (ED) related functions */
static void ohci_insert_ctrl_ed(
static void ohci_insert_bulk_ed(
static void ohci_insert_intr_ed(
static void ohci_insert_isoc_ed(
static void ohci_remove_ctrl_ed(
static void ohci_remove_bulk_ed(
static void ohci_remove_periodic_ed(
static void ohci_insert_ed_on_reclaim_list(
static void ohci_detach_ed_from_list(
static ohci_ed_t *ohci_ed_iommu_to_cpu(
/* Transfer Descriptor (TD) related functions */
static void ohci_insert_ctrl_req(
static void ohci_set_periodic_pipe_polling(
static int ohci_stop_periodic_pipe_polling(
static ohci_td_t *ohci_allocate_td_from_pool(
static void ohci_init_itd(
static int ohci_insert_td_with_frame_number(
/* Transfer Wrapper (TW) functions */
static int ohci_allocate_tds_for_tw(
static void ohci_free_tw_tds_resources(
static void ohci_start_xfer_timer(
static void ohci_stop_xfer_timer(
static void ohci_xfer_timeout_handler(void *arg);
static void ohci_remove_tw_from_timeout_list(
static int ohci_tw_rebind_cookie(
/* Interrupt Handling functions */
static void ohci_handle_missed_intr(
static void ohci_handle_endpoint_reclaimation(
static void ohci_traverse_done_list(
static ohci_td_t *ohci_reverse_done_list(
static void ohci_parse_isoc_error(
static usb_cr_t ohci_check_for_error(
static void ohci_handle_error(
static int ohci_cleanup_data_underrun(
static void ohci_handle_normal_td(
void *);
void *);
void *);
static void ohci_handle_one_xfer_completion(
void *);
static void ohci_sendup_td_message(
static int ohci_check_done_head(
/* Miscillaneous functions */
static void ohci_cpr_cleanup(
static int ohci_allocate_periodic_in_resource(
static int ohci_wait_for_sof(
static void ohci_pipe_cleanup(
static void ohci_wait_for_transfers_completion(
static void ohci_check_for_transfers_completion(
static void ohci_deallocate_periodic_in_resource(
static void ohci_do_client_periodic_in_req_callback(
static void ohci_hcdi_callback(
/* Kstat Support */
static void ohci_do_byte_stats(
static void ohci_do_intrs_stats(
int val);
/* extern */
/*
* 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.
*/
static struct cb_ops ohci_cb_ops = {
ohci_open, /* Open */
ohci_close, /* Close */
nodev, /* Strategy */
nodev, /* Print */
nodev, /* Dump */
nodev, /* Read */
nodev, /* Write */
ohci_ioctl, /* Ioctl */
nodev, /* Devmap */
nodev, /* Mmap */
nodev, /* Segmap */
nochpoll, /* Poll */
ddi_prop_op, /* cb_prop_op */
NULL, /* Streamtab */
D_MP /* Driver compatibility flag */
};
DEVO_REV, /* Devo_rev */
0, /* Refcnt */
ohci_info, /* Info */
nulldev, /* Identify */
nulldev, /* Probe */
ohci_attach, /* Attach */
ohci_detach, /* Detach */
nodev, /* Reset */
&ohci_cb_ops, /* Driver operations */
&usba_hubdi_busops, /* Bus operations */
usba_hubdi_root_hub_power /* Power */
};
/*
* The USBA library must be loaded for this driver.
*/
&mod_driverops, /* Type of module. This one is a driver */
"USB OpenHCI Driver %I%", /* Name of the module. */
&ohci_ops, /* Driver ops */
};
static struct modlinkage modlinkage = {
};
int
_init(void)
{
int error;
/* Initialize the soft state structures */
OHCI_INSTS)) != 0) {
return (error);
}
/* Install the loadable module */
}
return (error);
}
int
{
}
int
_fini(void)
{
int error;
/* Release per module resources */
}
return (error);
}
/*
* Host Controller Driver (HCD) entry points
*/
/*
* ohci_attach:
*/
static int
{
int instance;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
return (ohci_cpr_resume(ohcip));
default:
return (DDI_FAILURE);
}
/* Get the instance and create soft state */
return (DDI_FAILURE);
}
return (DDI_FAILURE);
}
&ohci_errmask, &ohci_instance_debug, 0);
/* Set host controller soft state to initilization */
"ohcip = 0x%p", (void *)ohcip);
/* Initialize the DMA attributes */
/* Save the dip and instance */
/* Initialize the kstat structures */
/* Create the td and ed pools */
(void) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
/* Map the registers */
(void) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
/* Register interrupts */
(void) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
/* Initialize the controller */
(void) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
/*
* At this point, the hardware wiil 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) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
(void) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
/* Finally load the root hub driver */
(void) ohci_cleanup(ohcip);
return (DDI_FAILURE);
}
/* Display information in the banner */
/* Reset the ohci initilization flag */
/* Print the Host Control's Operational registers */
/* For RIO we need to call pci_report_pmcap */
if (OHCI_IS_RIO(ohcip)) {
}
"ohci_attach: dip = 0x%p done", (void *)dip);
return (DDI_SUCCESS);
}
/*
* ohci_detach:
*/
int
{
switch (cmd) {
case DDI_DETACH:
return (ohci_cleanup(ohcip));
case DDI_SUSPEND:
return (ohci_cpr_suspend(ohcip));
default:
return (DDI_FAILURE);
}
}
/*
* ohci_info:
*/
/* ARGSUSED */
static int
void *arg,
void **result)
{
int instance;
int error = DDI_FAILURE;
switch (infocmd) {
case DDI_INFO_DEVT2DEVINFO:
error = DDI_SUCCESS;
}
} else {
}
break;
case DDI_INFO_DEVT2INSTANCE:
error = DDI_SUCCESS;
break;
default:
break;
}
return (error);
}
/*
* cb_ops entry points
*/
static dev_info_t *
{
if (ohcip) {
} 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,
}
/*
* Host Controller Driver (HCD) initialization functions
*/
/*
* ohci_set_dma_attributes:
*
* Set the limits in the DMA attributes structure. Most of the values used
* in the DMA limit structres are the default values as specified by the
* Writing PCI device drivers document.
*/
static void
{
"ohci_set_dma_attributes:");
/* Initialize the DMA attributes */
/* 32 bit addressing */
/* Byte alignment */
/*
* Since PCI specification is byte alignment, the
* burstsize field should be set to 1 for PCI devices.
*/
}
/*
* ohci_allocate_pools:
*
* Allocate the system memory for the Endpoint Descriptor (ED) and for the
* Transfer Descriptor (TD) pools. Both ED and TD structures must be aligned
* to a 16 byte boundary.
*/
static int
{
int result;
int i;
"ohci_allocate_pools:");
/* The host controller will be little endian */
/* Byte alignment to TD alignment */
/* Allocate the TD pool DMA handle */
DDI_DMA_SLEEP, 0,
return (DDI_FAILURE);
}
/* Allocate the memory for the TD pool */
ohci_td_pool_size * sizeof (ohci_td_t),
&dev_attr,
0,
&ohcip->ohci_td_pool_mem_handle)) {
return (DDI_FAILURE);
}
/* Map the TD pool into the I/O address space */
NULL,
NULL,
&ccount);
ohci_td_pool_size * sizeof (ohci_td_t));
/* Process the result */
if (result == DDI_DMA_MAPPED) {
/* The cookie count should be 1 */
if (ccount != 1) {
"ohci_allocate_pools: More than 1 cookie");
return (DDI_FAILURE);
}
} else {
"ohci_allocate_pools: Result = %d", result);
return (DDI_FAILURE);
}
/*
* DMA addresses for TD pools are bound
*/
/* Initialize the TD pool */
for (i = 0; i < ohci_td_pool_size; i ++) {
}
/* Byte alignment to ED alignment */
/* Allocate the ED pool DMA handle */
0,
return (DDI_FAILURE);
}
/* Allocate the memory for the ED pool */
ohci_ed_pool_size * sizeof (ohci_ed_t),
&dev_attr,
0,
return (DDI_FAILURE);
}
NULL,
NULL,
&ccount);
ohci_ed_pool_size * sizeof (ohci_ed_t));
/* Process the result */
if (result == DDI_DMA_MAPPED) {
/* The cookie count should be 1 */
if (ccount != 1) {
"ohci_allocate_pools: More than 1 cookie");
return (DDI_FAILURE);
}
} else {
return (DDI_FAILURE);
}
/*
* DMA addresses for ED pools are bound
*/
/* Initialize the ED pool */
for (i = 0; i < ohci_ed_pool_size; i ++) {
}
return (DDI_SUCCESS);
}
/*
* ohci_decode_ddi_dma_addr_bind_handle_result:
*
* Process the return values of ddi_dma_addr_bind_handle()
*/
static void
int result)
{
"ohci_decode_ddi_dma_addr_bind_handle_result:");
switch (result) {
case DDI_DMA_PARTIAL_MAP:
"Partial transfers not allowed");
break;
case DDI_DMA_INUSE:
"Handle is in use");
break;
case DDI_DMA_NORESOURCES:
"No resources");
break;
case DDI_DMA_NOMAPPING:
"No mapping");
break;
case DDI_DMA_TOOBIG:
"Object is too big");
break;
default:
"Unknown dma error");
}
}
/*
* ohci_map_regs:
*
* The Host Controller (HC) contains a set of on-chip operational registers
* and which should be mapped into a non-cacheable portion of the system
* addressable space.
*/
static int
{
/* The host controller will be little endian */
/* Map in operational registers */
sizeof (ohci_regs_t), &attr,
"ohci_map_regs: Map setup error");
return (DDI_FAILURE);
}
"ohci_map_regs: Config error");
return (DDI_FAILURE);
}
/* Make sure Memory Access Enable and Master Enable are set */
if (!(cmd_reg & PCI_COMM_MAE)) {
"ohci_map_regs: Memory base address access disabled");
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* The following simulated polling is for debugging purposes only.
* It is activated on x86 by setting usb-polling=true in GRUB or ohci.conf.
*/
static int
{
int ret;
char *propval;
return (0);
return (ret);
}
static void
ohci_poll_intr(void *arg)
{
/* poll every millisecond */
for (;;) {
}
}
/*
* ohci_register_intrs_and_init_mutex:
*
* Register interrupts and initialize each mutex and condition variables
*/
static int
{
int intr_types;
"ohci_register_intrs_and_init_mutex:");
extern pri_t maxclsyspri;
"ohci_register_intrs_and_init_mutex: "
"running in simulated polled mode");
goto skip_intr;
}
/* Get supported interrupt types */
&intr_types) != DDI_SUCCESS) {
"ohci_register_intrs_and_init_mutex: "
"ddi_intr_get_supported_types failed");
return (DDI_FAILURE);
}
"ohci_register_intrs_and_init_mutex: "
"supported interrupt types 0x%x", intr_types);
!= DDI_SUCCESS) {
"ohci_register_intrs_and_init_mutex: MSI "
"registration failed, trying FIXED interrupt \n");
} else {
"ohci_register_intrs_and_init_mutex: "
"Using MSI interrupt type\n");
}
}
(intr_types & DDI_INTR_TYPE_FIXED)) {
!= DDI_SUCCESS) {
"ohci_register_intrs_and_init_mutex: "
"FIXED interrupt registration failed\n");
return (DDI_FAILURE);
}
"ohci_register_intrs_and_init_mutex: "
"Using FIXED interrupt type\n");
}
/* Create prototype for SOF condition variable */
/* Semaphore to serialize opens and closes */
return (DDI_SUCCESS);
}
/*
* ohci_add_intrs:
*
* Register FIXED or MSI interrupts.
*/
static int
int intr_type)
{
"ohci_add_intrs: interrupt type 0x%x", intr_type);
/* Get number of interrupts */
"ohci_add_intrs: ddi_intr_get_nintrs() failure, "
return (DDI_FAILURE);
}
/* Get number of available interrupts */
"ohci_add_intrs: ddi_intr_get_navail() failure, "
return (DDI_FAILURE);
}
"ohci_add_intrs: ohci_add_intrs: nintrs () "
}
/* Allocate an array of interrupt handles */
/* call ddi_intr_alloc() */
"ohci_add_intrs: ddi_intr_alloc() failed %d", ret);
return (DDI_FAILURE);
}
"ohci_add_intrs: Requested: %d, Received: %d\n",
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
"ohci_add_intrs: ddi_intr_get_pri() failed %d", ret);
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
"ohci_add_intrs: Supported Interrupt priority 0x%x",
/* Test for high level mutex */
"ohci_add_intrs: Hi level interrupt not supported");
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
/* Initialize the mutex */
/* Call ddi_intr_add_handler() */
for (i = 0; i < actual; i++) {
"ohci_add_intrs: ddi_intr_add_handler() "
"failed %d", ret);
for (i = 0; i < actual; i++)
return (DDI_FAILURE);
}
}
"ohci_add_intrs: ddi_intr_get_cap() failed %d", ret);
for (i = 0; i < actual; i++) {
}
return (DDI_FAILURE);
}
/* Enable all interrupts */
/* Call ddi_intr_block_enable() for MSI interrupts */
} else {
/* Call ddi_intr_enable for MSI or FIXED interrupts */
for (i = 0; i < ohcip->ohci_intr_cnt; i++)
}
return (DDI_SUCCESS);
}
/*
* ohci_init_ctlr:
*
* Initialize the Host Controller (HC).
*/
static int
{
int retry = 0;
int ohci_frame_interval;
"ohci_init_ctlr: ohci_take_control failed\n");
return (DDI_FAILURE);
}
/*
* Soft reset the host controller.
*
* On soft reset, the ohci host controller moves to the
* USB Suspend state in which most of the ohci operational
* registers are reset except stated ones. The soft reset
* doesn't cause a reset to the ohci root hub and even no
* subsequent reset signaling should be asserterd to its
* down stream.
*/
/* Wait 10ms for reset to complete */
/*
* Do hard reset the host controller.
*
* Now perform USB reset in order to reset the ohci root
* hub.
*/
/*
* According to Section 5.1.2.3 of the specification, the
* host controller will go into suspend state immediately
* after the reset.
*/
/* Verify the version number */
return (DDI_FAILURE);
}
"ohci_init_ctlr: Revision verified");
/* hcca area need not be initialized on resume */
/* Get the ohci chip vendor and device id */
/* Initialize the hcca area */
return (DDI_FAILURE);
}
}
/*
* Workaround for ULI1575 chipset. Following OHCI Operational Memory
* Registers are not cleared to their default value on reset.
* Explicitly set the registers to default value.
*/
}
/* Set the HcHCCA to the physical address of the HCCA block */
/*
* Set HcInterruptEnable to enable all interrupts except Root
* Hub Status change and SOF interrupts.
*/
/*
* For non-periodic transfers, reserve atleast for one low-speed
* device transaction. According to USB Bandwidth Analysis white
* paper and also as per OHCI Specification 1.0a, section 7.3.5,
* page 123, one low-speed transaction takes 0x628h full speed
* bits (197 bytes), which comes to around 13% of USB frame time.
*
* The periodic transfers will get around 87% of USB frame time.
*/
/* Save the contents of the Frame Interval Registers */
/*
* Initialize the FSLargestDataPacket value in the frame interval
* register. The controller compares the value of MaxPacketSize to
* this value to see if the entire packet may be sent out before
* the EOF.
*/
/*
* Sometimes the HcFmInterval register in OHCI controller does not
* maintain its value after the first write. This problem is found
* on ULI M1575 South Bridge. To workaround the hardware problem,
* check the value after write and retry if the last write failed.
*/
while ((ohci_frame_interval != (max_packet |
ohcip->ohci_frame_interval))) {
if (retry >= 10) {
" Failed to program Frame"
" Interval Register, giving up.");
return (DDI_FAILURE);
}
retry++;
"ohci_init_ctlr: Failed to program Frame"
" Interval Register, retry=%d", retry);
}
}
/* Begin sending SOFs */
"ohci_init_ctlr: curr_control=0x%x", curr_control);
/* Set the state to operational */
curr_control = (curr_control &
/* Set host controller soft state to operational */
/* Get the number of clock ticks to wait */
/* Clear ohci_sof_flag indicating waiting for SOF interrupt */
/* Enable the SOF interrupt */
/* Wait for the SOF or timeout event */
/* Set host controller soft state to error */
"No SOF interrupts have been received, this USB OHCI host"
"controller is unusable");
return (DDI_FAILURE);
}
"ohci_init_ctlr: SOF's have started");
return (DDI_SUCCESS);
}
/*
* ohci_init_hcca:
*
* Allocate the system memory and initialize Host Controller Communication
* Area (HCCA). The HCCA structure must be aligned to a 256-byte boundary.
*/
static int
{
int result;
/* The host controller will be little endian */
/* Byte alignment to HCCA alignment */
/* Create space for the HCCA block */
0,
!= DDI_SUCCESS) {
return (DDI_FAILURE);
}
2 * sizeof (ohci_hcca_t),
&dev_attr,
0,
&ohcip->ohci_hcca_mem_handle)) {
return (DDI_FAILURE);
}
/* Figure out the alignment requirements */
/*
* Read the hcr_HCCA register until
* contenets are non-zero.
*/
while (mask == 0) {
}
addr++;
}
"ohci_init_hcca: Real length %lu", real_length);
/* Map the whole HCCA into the I/O address space */
NULL,
&ccount);
if (result == DDI_DMA_MAPPED) {
/* The cookie count should be 1 */
if (ccount != 1) {
"ohci_init_hcca: More than 1 cookie");
return (DDI_FAILURE);
}
} else {
return (DDI_FAILURE);
}
/*
* DMA addresses for HCCA are bound
*/
"ohci_init_hcca: physical 0x%p",
/* Initialize the interrupt lists */
"ohci_init_hcca: End");
return (DDI_SUCCESS);
}
/*
* ohci_build_interrupt_lattice:
*
* Construct the interrupt lattice tree using static Endpoint Descriptors
* (ED). This interrupt lattice tree will have total of 32 interrupt ED
* lists and the Host Controller (HC) processes one interrupt ED list in
* every frame. The lower five bits of the current frame number indexes
* into an array of 32 interrupt Endpoint Descriptor lists found in the
* HCCA.
*/
static void
{
int i;
"ohci_build_interrupt_lattice:");
/*
* Reserve the first 31 Endpoint Descriptor (ED) structures
* in the pool as static endpoints & these are required for
* constructing interrupt lattice tree.
*/
for (i = 0; i < NUM_STATIC_NODES; i++) {
}
/* Build the interrupt lattice tree */
for (i = 0; i < half_list - 1; i++) {
/*
* The next pointer in the host controller endpoint
* descriptor must contain an iommu address. Calculate
* the offset into the cpu address and add this to the
* starting iommu address.
*/
}
/*
* Initialize the interrupt list in the HCCA so that it points
* to the bottom of the tree.
*/
for (i = 0; i < half_list; i++) {
ohci_index[i]].hced_ctrl));
}
}
/*
* ohci_take_control:
*
* Take control of the host controller. OpenHCI allows for optional support
* of legacy devices through the use of System Management Mode software and
* system Management interrupt hardware. See section 5.1.1.3 of the OpenHCI
* spec for more details.
*/
static int
{
#if defined(__x86)
int wait;
#endif /* __x86 */
"ohci_take_control:");
#if defined(__x86)
/*
* On x86, we must tell the BIOS we want the controller,
* and wait for it to respond that we can have it.
*/
if ((hcr_control_val & HCR_CONTROL_IR) == 0) {
"ohci_take_control: InterruptRouting off\n");
return (DDI_SUCCESS);
}
/* attempt the OwnershipChange request */
"ohci_take_control: hcr_cmd_status: 0x%x\n",
/* now wait for 5 seconds for InterruptRouting to go away */
break;
drv_usecwait(1000);
}
if (wait >= 5000) {
"ohci_take_control: couldn't take control from BIOS\n");
return (DDI_FAILURE);
}
#else /* __x86 */
/*
* On Sparc, there won't be special System Management Mode
* hardware for legacy devices, while the x86 platforms may
* have to deal with this. This function may be platform
* specific.
*
* The interrupt routing bit should not be set.
*/
"ohci_take_control: Routing bit set");
return (DDI_FAILURE);
}
#endif /* __x86 */
"ohci_take_control: End");
return (DDI_SUCCESS);
}
/*
* ohci_pm_support:
* always return success since PM has been quite reliable on ohci
*/
/*ARGSUSED*/
int
{
return (USB_SUCCESS);
}
/*
* ohci_alloc_hcdi_ops:
*
* The HCDI interfaces or entry points are the software interfaces used by
* the Universal Serial Bus Driver (USBA) to access the services of the
* Host Controller Driver (HCD). During HCD initialization, inform USBA
* about all available HCDI interfaces or entry points.
*/
static usba_hcdi_ops_t *
{
"ohci_alloc_hcdi_ops:");
return (usba_hcdi_ops);
}
/*
* Host Controller Driver (HCD) deinitialization functions
*/
/*
* ohci_cleanup:
*
* Cleanup on attach failure or detach
*/
static int
{
if (flags & OHCI_RHREG) {
/* Unload the root hub driver */
return (DDI_FAILURE);
}
}
if (flags & OHCI_USBAREG) {
/* Unregister this HCD instance with USBA */
}
/* Disable all HC ED list processing */
/* Disable all HC interrupts */
/* Wait for the next SOF */
(void) ohci_wait_for_sof(ohcip);
/* Disable Master and SOF interrupts */
/* Set the Host Controller Functional State to Reset */
(~HCR_CONTROL_HCFS)) | HCR_CONTROL_RESET));
/* Wait for sometime */
/*
* Workaround for ULI1575 chipset. Following OHCI Operational
* Memory Registers are not cleared to their default value
* on reset. Explicitly set the registers to default value.
*/
}
}
/* Unmap the OHCI registers */
if (ohcip->ohci_regs_handle) {
/* Reset the host controller */
}
if (ohcip->ohci_config_handle) {
}
/* Free all the buffers */
for (i = 0; i < ohci_td_pool_size; i ++) {
(td->hctd_trans_wrapper)) {
tw = (ohci_trans_wrapper_t *)
/* Obtain the pipe private structure */
/* Stop the the transfer timer */
}
}
/*
* If OHCI_TD_POOL_BOUND flag is set, then unbind
* the handle for TD pools.
*/
if ((ohcip->ohci_dma_addr_bind_flag &
}
}
/* Free the TD pool */
if (ohcip->ohci_td_pool_dma_handle) {
}
/*
* If OHCI_ED_POOL_BOUND flag is set, then unbind
* the handle for ED pools.
*/
if ((ohcip->ohci_dma_addr_bind_flag &
}
}
/* Free the ED pool */
if (ohcip->ohci_ed_pool_dma_handle) {
}
/* Free the HCCA area */
/*
* If OHCI_HCCA_DMA_BOUND flag is set, then unbind
* the handle for HCCA.
*/
if ((ohcip->ohci_dma_addr_bind_flag &
}
}
if (ohcip->ohci_hcca_dma_handle) {
}
/* Destroy the mutex */
/* Destroy the SOF condition varibale */
/* Destroy the serialize opens and closes semaphore */
}
/* clean up kstat structs */
/* Free ohci hcdi ops */
if (ohcip->ohci_hcdi_ops) {
}
if (flags & OHCI_ZALLOC) {
/* Remove all properties that might have been created */
/* Free the soft state */
}
return (DDI_SUCCESS);
}
/*
* ohci_rem_intrs:
*
* Unregister FIXED or MSI interrupts
*/
static void
{
int i;
/* Disable all interrupts */
} else {
for (i = 0; i < ohcip->ohci_intr_cnt; i++) {
}
}
/* Call ddi_intr_remove_handler() */
for (i = 0; i < ohcip->ohci_intr_cnt; i++) {
}
}
/*
* ohci_cpr_suspend
*/
static int
{
"ohci_cpr_suspend:");
/* Call into the root hub and suspend it */
return (DDI_FAILURE);
}
/* Only root hub's intr pipe should be open at this time */
"ohci_cpr_suspend: fails as open pipe count = %d",
return (DDI_FAILURE);
}
"ohci_cpr_suspend: Disable HC ED list processing");
/* Disable all HC ED list processing */
"ohci_cpr_suspend: Disable HC interrupts");
/* Disable all HC interrupts */
"ohci_cpr_suspend: Wait for the next SOF");
/* Wait for the next SOF */
"ohci_cpr_suspend: ohci host controller suspend failed");
return (DDI_FAILURE);
}
"ohci_cpr_suspend: Disable Master interrupt");
/*
* Disable Master interrupt so that ohci driver don't
* get any ohci interrupts.
*/
/*
* Suspend the ohci host controller
* if usb keyboard is not connected.
*/
if (ohcip->ohci_polled_kbd_count == 0) {
}
/* Set host controller soft state to suspend */
return (DDI_SUCCESS);
}
/*
* ohci_cpr_resume
*/
static int
{
"ohci_cpr_resume: Restart the controller");
/* Cleanup ohci specific information across cpr */
/* Restart the controller */
"ohci_cpr_resume: ohci host controller resume failed ");
return (DDI_FAILURE);
}
/* Now resume the root hub */
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
/*
* 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.
*/
/*
* ohci_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.
*/
static int
{
"ohci_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) {
"ohci_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 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 (OHCI_PERIODIC_ENDPOINT(epdt)) {
if (error != USB_SUCCESS) {
"ohci_hcdi_pipe_open: Bandwidth allocation failed");
return (error);
}
}
/* Create the HCD pipe private structure */
/*
* Return failure if ohci pipe private
* structure allocation fails.
*/
/* Deallocate bandwidth */
if (OHCI_PERIODIC_ENDPOINT(epdt)) {
}
return (USB_NO_RESOURCES);
}
/* Store the node in the interrupt lattice */
/* 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 */
"ohci_hcdi_pipe_open: ED allocation failed");
/* Deallocate bandwidth */
if (OHCI_PERIODIC_ENDPOINT(epdt)) {
}
/* Destroy the xfer completion condition varibale */
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
/*
* Set the private structure in the
* pipe handle equal to NULL.
*/
return (USB_NO_RESOURCES);
}
/* 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 TD's to process.
*/
"ohci_hcdi_pipe_open: ph = 0x%p", (void *)ph);
return (USB_SUCCESS);
}
/*
* ohci_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 */
static int
{
int error = USB_SUCCESS;
"ohci_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 endoint descriptor from Host
* Controller's appropriate endpoint list.
*/
/* Deallocate bandwidth */
if (OHCI_PERIODIC_ENDPOINT(eptd)) {
}
/* Destroy the xfer completion condition varibale */
/*
* Deallocate the hcd private portion
* of the pipe handle.
*/
"ohci_hcdi_pipe_close: ph = 0x%p", (void *)ph);
return (error);
}
/*
* ohci_hcdi_pipe_reset:
*/
/* ARGSUSED */
static int
{
int error = USB_SUCCESS;
"ohci_hcdi_pipe_reset: ph = 0x%p ", (void *)ph);
/*
* Check and handle root hub pipe reset.
*/
return (error);
}
/* Set pipe state to pipe reset */
return (error);
}
/*
* ohci_hcdi_pipe_ctrl_xfer:
*/
static int
{
int rval;
int error = USB_SUCCESS;
"ohci_hcdi_pipe_ctrl_xfer: ph = 0x%p reqp = 0x%p flags = 0x%x",
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check and handle root hub control request.
*/
return (error);
}
/*
* Check whether pipe is in halted state.
*/
"ohci_hcdi_pipe_ctrl_xfer:"
"Pipe is in error state, need pipe reset to continue");
return (USB_FAILURE);
}
/* Allocate a transfer wrapper */
} else {
/* Insert the td's on the endpoint */
}
return (error);
}
/*
* ohci_hcdi_bulk_transfer_size:
*
* Return maximum bulk transfer size
*/
/* ARGSUSED */
static int
{
int rval;
"ohci_hcdi_bulk_transfer_size:");
if (rval != USB_SUCCESS) {
return (rval);
}
return (USB_SUCCESS);
}
/*
* ohci_hcdi_pipe_bulk_xfer:
*/
static int
{
"ohci_hcdi_pipe_bulk_xfer: ph = 0x%p reqp = 0x%p flags = 0x%x",
if (rval != USB_SUCCESS) {
return (rval);
}
/*
* Check whether pipe is in halted state.
*/
"ohci_hcdi_pipe_bulk_xfer:"
"Pipe is in error state, need pipe reset to continue");
return (USB_FAILURE);
}
/* Allocate a transfer wrapper */
} else {
/* Add the TD into the Host Controller's bulk list */
}
return (error);
}
/*
* ohci_hcdi_pipe_intr_xfer:
*/
static int
{
"ohci_hcdi_pipe_intr_xfer: ph = 0x%p reqp = 0x%p flags = 0x%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);
}
/*
* ohci_hcdi_pipe_stop_intr_polling()
*/
static int
{
int error = USB_SUCCESS;
"ohci_hcdi_pipe_stop_intr_polling: ph = 0x%p fl = 0x%x",
return (error);
}
/*
* ohci_hcdi_get_current_frame_number:
*
* Return the current usb frame number
*/
static usb_frame_number_t
{
int rval;
if (rval != USB_SUCCESS) {
return (rval);
}
"ohci_hcdi_get_current_frame_number:"
"Current frame number 0x%llx", frame_number);
return (frame_number);
}
/*
* ohci_hcdi_get_max_isoc_pkts:
*
* Return maximum isochronous packets per usb isochronous request
*/
static uint_t
{
int rval;
if (rval != USB_SUCCESS) {
return (rval);
}
"ohci_hcdi_get_max_isoc_pkts: maximum isochronous"
"packets per usb isochronous request = 0x%x",
return (max_isoc_pkts_per_request);
}
/*
* ohci_hcdi_pipe_isoc_xfer:
*/
static int
{
int error = USB_SUCCESS;
"ohci_hcdi_pipe_isoc_xfer: ph = 0x%p reqp = 0x%p flags = 0x%x",
if (rval != USB_SUCCESS) {
return (rval);
}
/* Get the isochronous pipe direction */
"ohci_hcdi_pipe_isoc_xfer: isoc_reqp = 0x%p, uf = 0x%x",
if (pipe_dir == USB_EP_DIR_IN) {
} else {
/* Allocate transaction resources */
} else {
}
}
return (error);
}
/*
* ohci_hcdi_pipe_stop_isoc_polling()
*/
static int
{
"ohci_hcdi_pipe_stop_isoc_polling: ph = 0x%p fl = 0x%x",
if (rval != USB_SUCCESS) {
return (rval);
}
return (error);
}
/*
* Bandwidth Allocation functions
*/
/*
* ohci_allocate_bandwidth:
*
* Figure out whether or not this interval may be supported. Return the index
* into the lattice if it can be supported. Return allocation failure if it
* can not be supported.
*
* The lattice structure looks like this with the bottom leaf actually
* being an array. There is a total of 63 nodes in this tree. The lattice tree
* itself is 0 based, while the bottom leaf array is 0 based. The 0 bucket in
* the bottom leaf array is used to store the smalled allocated bandwidth of all
* the leaves.
*
* 0
* 1 2
* 3 4 5 6
* ...
* (32 33 ... 62 63) <-- last row does not exist in lattice, but an array
* 0 1 2 3 ... 30 31
*
* We keep track of the bandwidth that each leaf uses. First we search for the
* first leaf with the smallest used bandwidth. Based on that leaf we find the
* parent node of that leaf based on the interval time.
*
* From the parent node, we find all the leafs of that subtree and update the
* additional bandwidth needed. In order to balance the load the leaves are not
* executed directly from left to right, but scattered. For a better picture
* refer to Section 3.3.2 in the OpenHCI 1.0 spec, there should be a figure
* showing the Interrupt ED Structure.
*/
static int
{
/* This routine is protected by the ohci_int_mutex */
/*
* Calculate the length in bytes of a transaction on this
* periodic endpoint.
*/
/*
* If length is zero, then, it means endpoint maximum packet
* supported is zero. In that case, return failure without
* allocating any bandwidth.
*/
if (error != USB_SUCCESS) {
"ohci_allocate_bandwidth: Periodic endpoint with "
"zero endpoint maximum packet size is not supported");
return (USB_NOT_SUPPORTED);
}
/*
* If the length in bytes plus the allocated bandwidth exceeds
* the maximum, return bandwidth allocation failure.
*/
"ohci_allocate_bandwidth: Reached maximum "
"bandwidth value and cannot allocate bandwidth "
"for a given periodic endpoint");
return (USB_NO_BANDWIDTH);
}
/* Adjust polling interval to be a power of 2 */
/*
* If this interval can't be supported,
* return allocation failure.
*/
if (interval == USB_FAILURE) {
return (USB_FAILURE);
}
"The new interval is %d", interval);
/* Find the leaf with the smallest allocated bandwidth */
min_index = 0;
for (i = 1; i < NUM_INTR_ED_LISTS; i++) {
min_index = i;
}
}
/* Adjust min for the lattice */
/*
* Find the index into the lattice given the
* leaf with the smallest allocated bandwidth.
*/
"The height is %d", height);
for (i = 0; i < height; i++) {
}
"Real node is %d", *node);
/*
* Find the leftmost leaf in the subtree
* specified by the node.
*/
"Leftmost %d", leftmost);
for (i = 0; i < (NUM_INTR_ED_LISTS/interval); i++) {
"ohci_allocate_bandwidth: Reached maximum "
"bandwidth value and cannot allocate bandwidth "
"for periodic endpoint");
return (USB_NO_BANDWIDTH);
}
}
/*
* All the leaves for this node must be updated with the bandwidth.
*/
for (i = 0; i < (NUM_INTR_ED_LISTS/interval); i++) {
}
/* Find the leaf with the smallest allocated bandwidth */
min_index = 0;
for (i = 1; i < NUM_INTR_ED_LISTS; i++) {
min_index = i;
}
}
/* Save the minimum for later use */
return (USB_SUCCESS);
}
/*
* ohci_deallocate_bandwidth:
*
* Deallocate bandwidth for the given node in the lattice and the length
* of transfer.
*/
static void
{
int i, interval;
/* This routine is protected by the ohci_int_mutex */
/* Obtain the length */
(void) ohci_compute_total_bandwidth(
/* Obtain the node */
/* Adjust polling interval to be a power of 2 */
/* Find the height in the tree */
/*
* Find the leftmost leaf in the subtree specified by the node
*/
/* Delete the bandwith from the appropriate lists */
for (i = 0; i < (NUM_INTR_ED_LISTS/interval); i++) {
}
/* Recompute the minimum */
for (i = 1; i < NUM_INTR_ED_LISTS; i++) {
}
}
/* Save the minimum for later use */
}
/*
* ohci_compute_total_bandwidth:
*
* Given a periodic endpoint (interrupt or isochronous) determine the total
* bandwidth for one transaction. The OpenHCI host controller traverses the
* endpoint descriptor lists on a first-come-first-serve basis. When the HC
* services an endpoint, only a single transaction attempt is made. The HC
* moves to the next Endpoint Descriptor after the first transaction attempt
* rather than finishing the entire Transfer Descriptor. Therefore, when a
* Transfer Descriptor is inserted into the lattice, we will only count the
* number of bytes for one transaction.
*
* The following are the formulas used for calculating bandwidth in terms
* bytes and it is for the single USB full speed and low speed transaction
* respectively. The protocol overheads will be different for each of type
* of USB transfer and all these formulas & protocol overheads are derived
* from the 5.9.3 section of USB Specification & with the help of Bandwidth
* Analysis white paper which is posted on the USB developer forum.
*
* Full-Speed:
* Protocol overhead + ((MaxPacketSize * 7)/6 ) + Host_Delay
*
* Low-Speed:
* Protocol overhead + Hub LS overhead +
* (Low-Speed clock * ((MaxPacketSize * 7)/6 )) + Host_Delay
*/
static int
{
/*
* If endpoint maximum packet is zero, then return immediately.
*/
if (maxpacketsize == 0) {
return (USB_NOT_SUPPORTED);
}
/* Add Host Controller specific delay to required bandwidth */
/* Add bit-stuffing overhead */
/* Low Speed interrupt transaction */
if (port_status == USBA_LOW_SPEED_DEV) {
/* Low Speed interrupt transaction */
(LOW_SPEED_CLOCK * maxpacketsize));
} else {
/* Full Speed transaction */
*bandwidth += maxpacketsize;
if ((endpoint->bmAttributes &
USB_EP_ATTR_MASK) == USB_EP_ATTR_INTR) {
/* Full Speed interrupt transaction */
} else {
/* Isochronous and input transaction */
if ((endpoint->bEndpointAddress &
USB_EP_DIR_MASK) == USB_EP_DIR_IN) {
} else {
/* Isochronous and output transaction */
}
}
}
return (USB_SUCCESS);
}
/*
* ohci_adjust_polling_interval:
*/
static int
{
int i = 0;
/*
* Get the polling interval from the endpoint descriptor
*/
/*
* The bInterval value in the endpoint descriptor can range
* from 1 to 255ms. The interrupt lattice has 32 leaf nodes,
* and the host controller cycles through these nodes every
* 32ms. The longest polling interval that the controller
* supports is 32ms.
*/
/*
* Return an error if the polling interval is less than 1ms
* and greater than 255ms
*/
if ((interval < MIN_POLL_INTERVAL) ||
(interval > MAX_POLL_INTERVAL)) {
"ohci_adjust_polling_interval: "
"Endpoint's poll interval must be between %d and %d ms",
return (USB_FAILURE);
}
/*
* According USB Specifications, a full-speed endpoint can
* specify a desired polling interval 1ms to 255ms and a low
* speed endpoints are limited to specifying only 10ms to
* 255ms. But some old keyboards & mice uses polling interval
* of 8ms. For compatibility purpose, we are using polling
* interval between 8ms & 255ms for low speed endpoints. But
* ohci driver will reject the any low speed endpoints which
* request polling interval less than 8ms.
*/
if ((port_status == USBA_LOW_SPEED_DEV) &&
"ohci_adjust_polling_interval: "
"Low speed endpoint's poll interval of %d ms "
"is below threshold. Rounding up to %d ms",
}
/*
* If polling interval is greater than 32ms,
* adjust polling interval equal to 32ms.
*/
if (interval > NUM_INTR_ED_LISTS) {
}
/*
* Find the nearest power of 2 that'sless
* than interval.
*/
while ((ohci_pow_2(i)) <= interval) {
i++;
}
return (ohci_pow_2((i - 1)));
}
/*
* ohci_lattice_height:
*
* Given the requested bandwidth, find the height in the tree at which the
* nodes for this bandwidth fall. The height is measured as the number of
* nodes from the leaf to the level specified by bandwidth The root of the
* tree is at height TREE_HEIGHT.
*/
static uint_t
{
}
/*
* ohci_lattice_parent:
*/
static uint_t
{
if ((node % 2) == 0) {
} else {
}
}
/*
* ohci_leftmost_leaf:
*
* Find the leftmost leaf in the subtree specified by the node. Height refers
* to number of nodes from the bottom of the tree to the node, including the
* node.
*
* The formula for a zero based tree is:
* 2^H * Node + 2^H - 1
* The leaf of the tree is an array, convert the number for the array.
* Subtract the size of nodes not in the array
* 2^H * Node + 2^H - 1 - (NUM_INTR_ED_LIST - 1) =
* 2^H * Node + 2^H - NUM_INTR_ED_LIST =
* 2^H * (Node + 1) - NUM_INTR_ED_LIST
* 0
* 1 2
* 0 1 2 3
*/
static uint_t
{
}
/*
* ohci_hcca_intr_index:
*
* Given a node in the lattice, find the index for the hcca interrupt table
*/
static uint_t
{
/*
* Adjust the node to the array representing
* the bottom of the tree.
*/
if ((node % 2) == 0) {
} else {
}
}
/*
* ohci_hcca_leaf_index:
*
* Given a node in the bottom leaf array of the lattice, find the index
* for the hcca interrupt table
*/
static uint_t
{
if ((leaf % 2) == 0) {
} else {
}
}
/*
* ohci_pow_2:
*
* Compute 2 to the power
*/
static uint_t
ohci_pow_2(uint_t x)
{
if (x == 0) {
return (1);
} else {
return (2 << (x - 1));
}
}
/*
* ohci_log_2:
*
* Compute log base 2 of x
*/
static uint_t
ohci_log_2(uint_t x)
{
int i = 0;
while (x != 1) {
x = x >> 1;
i++;
}
return (i);
}
/*
* Endpoint Descriptor (ED) manipulations functions
*/
/*
* ohci_alloc_hc_ed:
* NOTE: This function is also called from POLLED MODE.
*
* Allocate an endpoint descriptor (ED)
*/
{
int i, state;
"ohci_alloc_hc_ed: ph = 0x%p", (void *)ph);
/*
* The first 31 endpoints in the Endpoint Descriptor (ED)
* buffer pool are reserved for building interrupt lattice
* tree. Search for a blank endpoint descriptor in the ED
* buffer pool.
*/
for (i = NUM_STATIC_NODES; i < ohci_ed_pool_size; i ++) {
if (state == HC_EPT_FREE) {
break;
}
}
"ohci_alloc_hc_ed: Allocated %d", i);
if (i == ohci_ed_pool_size) {
"ohci_alloc_hc_ed: ED exhausted");
return (NULL);
} else {
"ohci_alloc_hc_ed: Allocated address 0x%p", (void *)hc_ed);
/* Unpack the endpoint descriptor into a control field */
if (ph) {
if ((ohci_initialize_dummy(ohcip,
hc_ed)) == USB_NO_RESOURCES) {
return (NULL);
}
/* Change ED's state Active */
} else {
/* Change ED's state Static */
}
return (hc_ed);
}
}
/*
* ohci_unpack_endpoint:
*
* Unpack the information in the pipe handle and create the first byte
* of the Host Controller's (HC) Endpoint Descriptor (ED).
*/
static uint_t
{
"ohci_unpack_endpoint:");
/* Assign the endpoint's address */
/*
* Assign the direction. If the endpoint is a control endpoint,
* the direction is assigned by the Transfer Descriptor (TD).
*/
if ((endpoint->bmAttributes &
if (addr & USB_EP_DIR_MASK) {
/* The direction is IN */
} else {
/* The direction is OUT */
}
}
/* Assign the speed */
}
/* Assign the format */
if ((endpoint->bmAttributes &
}
return (ctrl);
}
/*
* ohci_insert_ed:
*
* Add the Endpoint Descriptor (ED) into the Host Controller's
* (HC) appropriate endpoint list.
*/
static void
{
"ohci_insert_ed:");
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
}
/*
* ohci_insert_ctrl_ed:
*
* Insert a control endpoint into the Host Controller's (HC)
* control endpoint list.
*/
static void
{
"ohci_insert_ctrl_ed:");
/* Obtain a ptr to the head of the list */
if (Get_OpReg(hcr_ctrl_head)) {
/* Set up the backwards pointer */
}
/* The new endpoint points to the head of the list */
/* Set the head ptr to the new endpoint */
/*
* Enable Control list processing if control open
* pipe count is zero.
*/
if (!ohcip->ohci_open_ctrl_pipe_count) {
/* Start Control list processing */
}
}
/*
* ohci_insert_bulk_ed:
*
* Insert a bulk endpoint into the Host Controller's (HC) bulk endpoint list.
*/
static void
{
"ohci_insert_bulk_ed:");
/* Obtain a ptr to the head of the Bulk list */
if (Get_OpReg(hcr_bulk_head)) {
/* Set up the backwards pointer */
}
/* The new endpoint points to the head of the Bulk list */
/* Set the Bulk head ptr to the new endpoint */
/*
* Enable Bulk list processing if bulk open pipe
* count is zero.
*/
if (!ohcip->ohci_open_bulk_pipe_count) {
/* Start Bulk list processing */
}
}
/*
* ohci_insert_intr_ed:
*
* Insert a interrupt endpoint into the Host Controller's (HC) interrupt
* lattice tree.
*/
static void
{
"ohci_insert_intr_ed:");
/*
* The appropriate node was found
* during the opening of the pipe.
*/
if (node >= NUM_STATIC_NODES) {
/* Get the hcca interrupt table index */
/* Get the first endpoint on the list */
/* Update this endpoint to point to it */
/* Put this endpoint at the head of the list */
/* The previous pointer is NULL */
/* Update the previous pointer of ept->hced_next */
}
} else {
/* Find the lattice endpoint */
/* Find the next lattice endpoint */
/*
* Update this endpoint to point to the next one in the
* lattice.
*/
/* Insert this endpoint into the lattice */
/* Update the previous pointer */
/* Update the previous pointer of ept->hced_next */
if ((next_lattice_ept) &&
}
}
/*
* Enable periodic list processing if periodic (interrupt
* and isochronous) open pipe count is zero.
*/
if (!ohcip->ohci_open_periodic_pipe_count) {
}
}
/*
* ohci_insert_isoc_ed:
*
* Insert a isochronous endpoint into the Host Controller's (HC) interrupt
* lattice tree. A isochronous endpoint will be inserted at the end of the
* 1ms interrupt endpoint list.
*/
static void
{
"ohci_insert_isoc_ed:");
/*
* The appropriate node was found during the opening of the pipe.
* This node must be root of the interrupt lattice tree.
*/
/* Find the 1ms interrupt lattice endpoint */
/* Find the next lattice endpoint */
while (next_lattice_ept) {
/* Find the next lattice endpoint */
}
/* The next pointer is NULL */
/* Update the previous pointer */
/* Insert this endpoint into the lattice */
/*
* Enable periodic and isoch lists processing if isoch
* open pipe count is zero.
*/
if (!ohcip->ohci_open_isoch_pipe_count) {
}
}
/*
* ohci_modify_sKip_bit:
*
* Modify the sKip bit on the Host Controller (HC) Endpoint Descriptor (ED).
*/
static void
{
"ohci_modify_sKip_bit: action = 0x%x flag = 0x%x",
if (action == CLEAR_sKip) {
/*
* If the skip 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.
*/
} else {
/* Sync ED and TD pool */
if (flag & OHCI_FLAGS_DMA_SYNC) {
}
/* Check Halt or Skip bit is already set */
"ohci_modify_sKip_bit: "
"Halt or Skip bit is already set");
} else {
/*
* The action is to set the skip bit. In order to
* be sure that the HCD has seen the sKip bit, wait
* for the next start of frame.
*/
if (flag & OHCI_FLAGS_SLEEP) {
/* Wait for the next SOF */
(void) ohci_wait_for_sof(ohcip);
/* Sync ED and TD pool */
if (flag & OHCI_FLAGS_DMA_SYNC) {
}
}
}
}
}
/*
* ohci_remove_ed:
*
* Remove the Endpoint Descriptor (ED) from the Host Controller's appropriate
* endpoint list.
*/
static void
{
"ohci_remove_ed:");
switch (attributes) {
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
case USB_EP_ATTR_ISOCH:
break;
}
}
/*
* ohci_remove_ctrl_ed:
*
* Remove a control Endpoint Descriptor (ED) from the Host Controller's (HC)
* control endpoint list.
*/
static void
{
"ohci_remove_ctrl_ed:");
/* The control list should already be stopped */
/* Detach the endpoint from the list that it's on */
/*
* If next endpoint pointed by endpoint to be removed is not NULL
* then set current control pointer to the next endpoint pointed by
* endpoint to be removed. Otherwise set current control pointer to
* the beginning of the control list.
*/
} else {
}
if (ohcip->ohci_open_ctrl_pipe_count) {
/* Reenable the control list */
}
}
/*
* ohci_remove_bulk_ed:
*
* Remove free the bulk Endpoint Descriptor (ED) from the Host Controller's
* (HC) bulk endpoint list.
*/
static void
{
"ohci_remove_bulk_ed:");
/* The bulk list should already be stopped */
/* Detach the endpoint from the bulk list */
/*
* If next endpoint pointed by endpoint to be removed is not NULL
* then set current bulk pointer to the next endpoint pointed by
* endpoint to be removed. Otherwise set current bulk pointer to
* the beginning of the bulk list.
*/
} else {
}
if (ohcip->ohci_open_bulk_pipe_count) {
/* Re-enable the bulk list */
}
}
/*
* ohci_remove_periodic_ed:
*
* Set up an periodic endpoint to be removed from the Host Controller's (HC)
* interrupt lattice tree. The Endpoint Descriptor (ED) will be freed in the
* interrupt handler.
*/
static void
{
"ohci_remove_periodic_ed:");
if (ept_type == USB_EP_ATTR_ISOCH) {
}
/* Store the node number */
/* Remove the endpoint from interrupt lattice tree */
/*
* Disable isoch list processing if isoch open pipe count
* is zero.
*/
if (!ohcip->ohci_open_isoch_pipe_count) {
}
/*
* Disable periodic list processing if periodic (interrupt
* and isochrous) open pipe count is zero.
*/
if (!ohcip->ohci_open_periodic_pipe_count) {
}
}
/*
* ohci_detach_ed_from_list:
*
* Remove the Endpoint Descriptor (ED) from the appropriate Host Controller's
* (HC) endpoint list.
*/
static void
{
"ohci_detach_ed_from_list:");
/*
* If there is no previous endpoint, then this
* endpoint is at the head of the endpoint list.
*/
if (next_ept) {
/*
* If this endpoint is the first element of the
* list and there is more than one endpoint on
* the list then perform specific actions based
* on the type of endpoint list.
*/
switch (ept_type) {
case USB_EP_ATTR_CONTROL:
/* Set the head of list to next ept */
/* Clear prev ptr of next endpoint */
break;
case USB_EP_ATTR_BULK:
/* Set the head of list to next ept */
/* Clear prev ptr of next endpoint */
break;
case USB_EP_ATTR_INTR:
/*
* HCCA area should point
* directly to this ept.
*/
/* Get the hcca interrupt table index */
/*
* Delete the ept from the
* bottom of the tree.
*/
/*
* Update the previous pointer
* of ept->hced_next
*/
}
break;
case USB_EP_ATTR_ISOCH:
default:
break;
}
} else {
/*
* If there was only one element on the list
* perform specific actions based on the type
* of the list.
*/
switch (ept_type) {
case USB_EP_ATTR_CONTROL:
/* Set the head to NULL */
break;
case USB_EP_ATTR_BULK:
/* Set the head to NULL */
break;
case USB_EP_ATTR_INTR:
case USB_EP_ATTR_ISOCH:
default:
break;
}
}
} else {
/* The previous ept points to the next one */
/*
* Set the previous ptr of the next_ept to prev_ept
* if this isn't the last endpoint on the list
*/
if ((next_ept) &&
/* Set the previous ptr of the next one */
}
}
}
/*
* ohci_insert_ed_on_reclaim_list:
*
* Insert Endpoint onto the reclaim list
*/
static void
{
/*
* Read current usb frame number and add appropriate number of
* usb frames needs to wait before reclaiming current endpoint.
*/
/* Store 32bit ID */
/* Insert the endpoint onto the reclaimation list */
if (ohcip->ohci_reclaim_list) {
while (next_ept) {
}
} else {
}
/* Enable the SOF interrupt */
}
/*
* ohci_deallocate_ed:
* NOTE: This function is also called from POLLED MODE.
*
* Deallocate a Host Controller's (HC) Endpoint Descriptor (ED).
*/
void
{
"ohci_deallocate_ed:");
if (dummy_td) {
}
"ohci_deallocate_ed: Deallocated 0x%p", (void *)old_ed);
}
/*
* ohci_ed_cpu_to_iommu:
* NOTE: This function is also called from POLLED MODE.
*
* This function converts for the given Endpoint Descriptor (ED) CPU address
* to IO address.
*/
{
sizeof (ohci_ed_t) * ohci_ed_pool_size);
return (ed);
}
/*
* ohci_ed_iommu_to_cpu:
*
* This function converts for the given Endpoint Descriptor (ED) IO address
* to CPU address.
*/
static ohci_ed_t *
{
return (NULL);
}
return (ed);
}
/*
* Transfer Descriptor manipulations functions
*/
/*
* ohci_initialize_dummy:
*
* An Endpoint Descriptor (ED) has a dummy Transfer Descriptor (TD) on the
* end of its TD list. Initially, both the head and tail pointers of the ED
* point to the dummy TD.
*/
static int
{
/* Obtain a dummy TD */
return (USB_NO_RESOURCES);
}
/*
* Both the head and tail pointers of an ED point
* to this new dummy TD.
*/
return (USB_SUCCESS);
}
/*
* ohci_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.
*/
static ohci_trans_wrapper_t *
{
/* Add one more td for data phase */
if (ctrl_reqp->ctrl_wLength) {
td_count++;
}
/*
* 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, OHCI_MAX_TD_BUF_SIZE) is just for padding
* and not to be transferred.
*/
if (ctrl_reqp->ctrl_wLength) {
} else {
}
return (tw);
}
/*
* ohci_insert_ctrl_req:
*
* Create a Transfer Descriptor (TD) and a data buffer for a control endpoint.
*/
/* ARGSUSED */
static void
{
int sdata;
"ohci_insert_ctrl_req:");
/*
* Save current control request pointer and timeout values
* in transfer wrapper.
*/
/*
* Initialize the callback and any callback data for when
* the td completes.
*/
/* Create the first four bytes of the setup packet */
"ohci_create_setup_pkt: sdata = 0x%x", sdata);
/* Create the second four bytes */
"ohci_create_setup_pkt: sdata = 0x%x", sdata);
/*
* The TD's are placed on the ED one at a time.
* Once this TD is placed on the done list, the
* data or status phase TD will be enqueued.
*/
"Create_setup: 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) {
} else {
/* Copy the data into the message */
}
HC_TD_R : 0;
/*
* There is a data stage.
* Find the direction.
*/
} else {
}
/*
* Create the TD. If this is an OUT transaction,
* the data is already in the buffer of the TW.
*/
/*
* The direction of the STATUS TD depends on
* the direction of the transfer.
*/
} else {
}
} else {
}
/* Status stage */
/* Indicate that the control list is filled */
/* Start the timer for this control transfer */
}
/*
* ohci_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.
*/
static ohci_trans_wrapper_t *
{
/* Check the size of bulk request */
"ohci_allocate_bulk_resources: Bulk request size 0x%x is "
return (NULL);
}
/* Get the required bulk packet size */
td_count++;
}
return (tw);
}
/*
* ohci_insert_bulk_req:
*
* Create a Transfer Descriptor (TD) and a data buffer for a bulk
* endpoint.
*/
/* ARGSUSED */
static void
{
int pipe_dir;
"ohci_insert_bulk_req: bulk_reqp = 0x%p flags = 0x%x",
/* Get the bulk pipe direction */
/* Get the required bulk packet size */
if (bulk_pkt_size)
"ohci_insert_bulk_req: bulk_pkt_size = %d", bulk_pkt_size);
/*
* Save current bulk request pointer and timeout values
* in transfer wrapper.
*/
/*
* Initialize the callback and any callback
* data required when the td completes.
*/
tw->tw_direction =
/* Copy the data into the message */
}
/* Insert all the bulk TDs */
/* Check for last td */
/* Check for inserting residue data */
if (residue) {
}
/*
* Only set the round bit on the last TD, to ensure
* the controller will always HALT the ED in case of
* a short transfer.
*/
if (bulk_reqp->bulk_attributes &
}
}
/* Insert the TD onto the endpoint */
}
/* Indicate that the bulk list is filled */
/* Start the timer for this bulk transfer */
}
/*
* ohci_start_periodic_pipe_polling:
* NOTE: This function is also called from POLLED MODE.
*/
int
{
int error = USB_SUCCESS;
"ohci_start_periodic_pipe_polling: ep%d",
/*
* Check and handle start polling on root hub interrupt pipe.
*/
USB_EP_ATTR_INTR)) {
return (error);
}
case OHCI_PIPE_STATE_IDLE:
/* Save the Original client's Periodic IN request */
/*
* This pipe is uninitialized or if a valid TD is
* not found then insert a TD on the interrupt or
* isochronous IN endpoint.
*/
if (error != USB_SUCCESS) {
"ohci_start_periodic_pipe_polling: "
"Start polling failed");
return (error);
}
"ohci_start_periodic_pipe_polling: PP = 0x%p", pp);
break;
case OHCI_PIPE_STATE_ACTIVE:
"ohci_start_periodic_pipe_polling: "
"Polling is already in progress");
error = USB_FAILURE;
break;
case OHCI_PIPE_STATE_ERROR:
"ohci_start_periodic_pipe_polling: "
"Pipe is halted and perform reset before restart polling");
error = USB_FAILURE;
break;
default:
"ohci_start_periodic_pipe_polling: Undefined state");
error = USB_FAILURE;
break;
}
return (error);
}
/*
* ohci_start_pipe_polling:
*
* Insert the number of periodic requests corresponding to polling
* interval as calculated during pipe open.
*/
static int
{
int i, total_tws;
int error = USB_SUCCESS;
"ohci_start_pipe_polling:");
/*
* 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) {
}
/* Allocate all the necessary resources for the IN transfer */
for (i = 0; i < total_tws; i++) {
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
/* There are not enough resources, deallocate the TWs */
}
return (error);
} else {
}
}
}
i = 0;
"ohci_start_pipe_polling: max = %d curr = %d tw = %p:",
tw_list);
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
if (error == USB_SUCCESS) {
} else {
/*
* Deallocate the remaining tw
* The current tw should have already been deallocated
*/
}
/*
* If this is the first req return an error.
* Otherwise return success.
*/
if (i != 0) {
error = USB_SUCCESS;
}
break;
}
i++;
}
return (error);
}
/*
* ohci_set_periodic_pipe_polling:
*
* Calculate the number of periodic requests needed corresponding to the
* interrupt/isochronous IN endpoints polling interval. Table below gives
* the number of periodic requests needed for the interrupt/isochronous
* IN endpoints according to endpoint polling interval.
*
* Polling interval Number of periodic requests
*
* 1ms 4
* 2ms 2
* 4ms to 32ms 1
*/
static void
{
"ohci_set_periodic_pipe_polling:");
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.
*/
(pp->pp_client_periodic_in_reqp)) {
if (intr_reqp->intr_attributes &
return;
}
}
/*
* The ohci_adjust_polling_interval function will not fail
* at this instance since bandwidth allocation is already
* done. Here we are getting only the periodic interval.
*/
switch (interval) {
case INTR_1MS_POLL:
break;
case INTR_2MS_POLL:
break;
default:
break;
}
"ohci_set_periodic_pipe_polling: Max periodic requests = %d",
}
/*
* ohci_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.
*/
static ohci_trans_wrapper_t *
{
int pipe_dir;
"ohci_allocate_intr_resources:");
/* Get the length of interrupt transfer & alloc data */
if (intr_reqp) {
} else {
(((usb_intr_req_t *)pp->
}
/* Check the size of interrupt request */
if (tw_length > OHCI_MAX_TD_XFER_SIZE) {
"ohci_allocate_intr_resources: Intr request size 0x%lx is "
return (NULL);
}
return (NULL);
}
if (pipe_dir == USB_EP_DIR_IN) {
USB_SUCCESS) {
return (NULL);
}
} else {
if (tw_length) {
/* Copy the data into the message */
}
}
if (intr_reqp) {
}
/*
* Initialize the callback and any callback
* data required when the td completes.
*/
return (tw);
}
/*
* ohci_insert_intr_req:
*
* Insert an Interrupt request into the Host Controller's periodic list.
*/
/* ARGSUSED */
static void
{
/* Get the current interrupt request pointer */
}
/* Insert another interrupt TD */
/* Start the timer for this Interrupt transfer */
}
/*
* ohci_stop_periodic_pipe_polling:
*/
/* ARGSUSED */
static int
{
"ohci_stop_periodic_pipe_polling: Flags = 0x%x", flags);
/*
* Check and handle stop polling on root hub interrupt pipe.
*/
USB_EP_ATTR_INTR)) {
return (USB_SUCCESS);
}
"ohci_stop_periodic_pipe_polling: Polling already stopped");
return (USB_SUCCESS);
}
/* Set pipe state to pipe stop polling */
return (USB_SUCCESS);
}
/*
* ohci_allocate_isoc_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.
*/
static ohci_trans_wrapper_t *
{
int pipe_dir;
"ohci_allocate_isoc_resources: flags = ox%x", flags);
/*
* Check whether pipe is in halted state.
*/
"ohci_allocate_isoc_resources:"
"Pipe is in error state, need pipe reset to continue");
return (NULL);
}
/* Calculate the maximum isochronous transfer size */
if (isoc_reqp) {
} else {
isoc_pkt_descr = ((usb_isoc_req_t *)
isoc_pkt_count = ((usb_isoc_req_t *)
isoc_pkts_length = ((usb_isoc_req_t *)
}
/*
* For isochronous IN pipe, get value of number of isochronous
* packets per usb isochronous request
*/
if (pipe_dir == USB_EP_DIR_IN) {
}
"ohci_allocate_isoc_resources: "
"isoc_pkts_length 0x%x is not equal to the sum of "
"all pkt lengths 0x%x in an isoc request",
return (NULL);
}
} else {
}
"ohci_allocate_isoc_resources: length = 0x%lx", tw_length);
/* Check the size of isochronous request */
if (tw_length > max_isoc_xfer_size) {
"ohci_allocate_isoc_resources: Maximum isoc request"
"size 0x%x Given isoc request size 0x%lx",
return (NULL);
}
/*
* Each isochronous TD can hold data upto eight isochronous
* data packets. Calculate the number of isochronous TDs needs
* to be insert to complete current isochronous request.
*/
if (isoc_pkt_count % OHCI_ISOC_PKTS_PER_TD) {
td_count++;
}
"ohci_create_isoc_transfer_wrapper: "
"Unable to allocate TW");
return (NULL);
}
USB_SUCCESS) {
} else {
return (NULL);
}
if (pipe_dir == USB_EP_DIR_IN) {
USB_SUCCESS) {
return (NULL);
}
} else {
uchar_t *p;
int i;
/* Copy the data into the message */
for (i = 0; i < td_count; i++) {
}
}
}
/*
* Initialize the callback and any callback
* data required when the td completes.
*/
return (tw);
}
/*
* ohci_insert_isoc_req:
*
* Insert an isochronous request into the Host Controller's
* isochronous list. If there is an error is will appropriately
* deallocate the unused resources.
*/
static int
{
"ohci_insert_isoc_req: flags = 0x%x", flags);
/*
* Get the current isochronous request and packet
* descriptor pointers.
*/
/*
* Save address of first usb isochronous packet descriptor.
*/
/* Insert all the isochronous TDs */
for (count = 0, curr_isoc_xfer_offset = 0,
/* Check for inserting residue data */
(residue < OHCI_ISOC_PKTS_PER_TD)) {
} else {
}
/*
* Calculate length of isochronous transfer
* for the current TD.
*/
for (i = 0, curr_isoc_xfer_len = 0;
i < frame_count; i++, curr_isoc_pkt_descr++) {
}
/*
* Programm td control field by checking whether this
* is last td.
*/
} else {
}
/* Insert the TD into the endpoint */
USB_SUCCESS) {
break;
}
isoc_pkts += frame_count;
}
if (error != USB_SUCCESS) {
/* Free periodic in resources */
}
if (pp->pp_cur_periodic_req_cnt) {
/*
* Set pipe state to stop polling and
* error to no resource. Don't insert
* any more isochronous polling requests.
*/
} else {
/* Set periodic in pipe state to idle */
}
}
} else {
/*
* Reset back to the address of first usb isochronous
* packet descriptor.
*/
/* Reset the CONTINUE flag */
}
return (error);
}
/*
* ohci_insert_hc_td:
*
* Insert a Transfer Descriptor (TD) on an Endpoint Descriptor (ED).
* Always returns USB_SUCCESS, except for ISOCH.
*/
static int
{
int error;
/* Retrieve preallocated td from the TW */
/* Fill in the current dummy */
cpu_current_dummy = (ohci_td_t *)
/*
* Fill in the current dummy td and
* add the new dummy to the end.
*/
/*
* If this is an isochronous TD, first write proper
* starting usb frame number in which this TD must
* can be processed. After writing the frame number
* insert this TD into the ED's list.
*/
if (error != USB_SUCCESS) {
/* Reset the current dummy back to a dummy */
/* return the new dummy back to the free list */
tw->tw_hctd_free_list));
}
return (error);
}
} else {
/*
* For control, bulk and interrupt TD, just
* add the new dummy to the ED's list. When
* this occurs, the Host Controller ill see
* the newly filled in dummy TD.
*/
}
/* Insert this td onto the tw */
return (USB_SUCCESS);
}
/*
* ohci_allocate_td_from_pool:
*
* Allocate a Transfer Descriptor (TD) from the TD buffer pool.
*/
static ohci_td_t *
{
int i, state;
/*
* Search for a blank Transfer Descriptor (TD)
* in the TD buffer pool.
*/
for (i = 0; i < ohci_td_pool_size; i ++) {
if (state == HC_TD_FREE) {
break;
}
}
if (i >= ohci_td_pool_size) {
"ohci_allocate_td_from_pool: TD exhausted");
return (NULL);
}
"ohci_allocate_td_from_pool: Allocated %d", i);
/* Create a new dummy for the end of the TD list */
"ohci_allocate_td_from_pool: td 0x%p", (void *)td);
/* Mark the newly allocated TD as a dummy */
return (td);
}
/*
* ohci_fill_in_td:
*
* Fill in the fields of a Transfer Descriptor (TD).
*
* hctd_dma_offs - different meanings for non-isoc and isoc TDs:
* starting offset into the TW buffer for a non-isoc TD
* and the index into the isoc TD list for an isoc TD.
* For non-isoc TDs, the starting offset should be 4k
* aligned and the TDs in one transfer must be filled in
* increasing order.
*/
static void
{
"ohci_fill_in_td: td 0x%p bufoffs 0x%x len 0x%lx",
/* Assert that the td to be filled in is a dummy */
/* Change TD's state Active */
/* Update the TD special fields */
} else {
/* Update the dummy with control information */
}
/* The current dummy now points to the new dummy */
/*
* For Control transfer, hctd_ctrl_phase is a valid field.
*/
if (hctd_ctrl_phase) {
}
/* Print the td */
/* Fill in the wrapper portion of the TD */
/* Set the transfer wrapper */
}
/*
* ohci_init_td:
*
* Initialize the buffer address portion of non-isoc Transfer
* Descriptor (TD).
*/
void
{
int rem_len, i;
/*
* TDs 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.
*/
/* Computing the starting buffer address and end buffer address */
for (i = 0; (i < 2) && (buf_len > 0); i++) {
/* Advance to the next DMA cookie if necessary */
/*
* tw_dma_offs always points to the starting offset
* of a cookie
*/
tw->tw_cookie_idx++;
}
/*
* Counting the remained buffer length to be filled in
* the TD for current DMA cookie
*/
/* Get the beginning address of the buffer */
if (i == 0) {
}
"ohci_init_td: page_addr 0x%p dmac_size "
tw->tw_cookie_idx);
if (buf_len <= OHCI_MAX_TD_BUF_SIZE) {
buf_len = 0;
break;
} else {
}
}
}
/*
* ohci_init_itd:
*
* Initialize the buffer address portion of isoc Transfer Descriptor (TD).
*/
static void
{
int i;
"ohci_init_itd: ctrl = 0x%x", hctd_ctrl);
/*
* Write control information except starting
* usb frame number.
*/
}
/*
* For an isochronous transfer, the hctd_cbp contains,
* the 4k page, and not the actual start of the buffer.
*/
toggle = 0;
buf = start_addr;
/*
* Get the address of first isochronous data packet
* for the current isochronous TD.
*/
/* The offsets are actually offsets into the page */
for (i = 0; i <= fc; i++) {
flag = ((start_addr &
if (flag) {
}
if (toggle) {
toggle = 0;
} else {
toggle = 1;
}
}
}
/*
* ohci_insert_td_with_frame_number:
*
* Insert current isochronous TD into the ED's list. with proper
* usb frame number in which this TD can be processed.
*/
static int
{
"ohci_insert_td_with_frame_number:"
/* Get the TD ctrl information */
/*
* Enter critical, while programming the usb frame number
* and inserting current isochronous TD into the ED's list.
*/
ddic = ddi_enter_critical();
/* Get the current frame number */
/* Check the given isochronous flags */
switch (isoc_reqp->isoc_attributes &
/* Starting frame number is specified */
/* Get the starting usb frame number */
} else {
/* Check for the Starting usb frame number */
if ((isoc_reqp->isoc_frame_no == 0) ||
((isoc_reqp->isoc_frame_no +
/* Exit the critical */
"ohci_insert_td_with_frame_number:"
"Invalid starting frame number");
return (USB_INVALID_START_FRAME);
}
/* Get the starting usb frame number */
pp->pp_next_frame_number = 0;
}
break;
case USB_ATTRS_ISOC_XFER_ASAP:
/* ohci has to specify starting frame number */
if ((pp->pp_next_frame_number) &&
/*
* Get the next usb frame number.
*/
} else {
/*
* Add appropriate offset to the current usb
* frame number and use it as a starting frame
* number.
*/
}
}
break;
default:
/* Exit the critical */
"ohci_insert_td_with_frame_number: Either starting "
"frame number or ASAP flags are not set, attrs = 0x%x",
return (USB_NO_FRAME_NUMBER);
}
/* Get the TD ctrl information */
/* Set the frame number field */
/*
* Add the new dummy to the ED's list. When this occurs,
* the Host Controller will see newly filled in dummy TD.
*/
/* Exit the critical */
"ohci_insert_td_with_frame_number:"
"current frame number 0x%llx start frame number 0x%llx",
/*
* Increment this saved frame number by current number
* of data packets needs to be transfer.
*/
/*
* Set OHCI_ISOC_XFER_CONTINUE flag in order to send other
* isochronous packets, part of the current isoch request
* in the subsequent frames.
*/
return (USB_SUCCESS);
}
/*
* ohci_insert_td_on_tw:
*
* The transfer wrapper keeps a list of all Transfer Descriptors (TD) that
* are allocated for this transfer. Insert a TD onto this list. The list
* of TD's does not include the dummy TD that is at the end of the list of
* TD's for the endpoint.
*/
static void
{
/*
* Set the next pointer to NULL because
* this is the last TD on list.
*/
} else {
/* Add the td to the end of the list */
}
}
/*
* ohci_traverse_tds:
* NOTE: This function is also called from POLLED MODE.
*
* Traverse the list of TD's for an endpoint. Since the endpoint is marked
* as sKipped, the Host Controller (HC) is no longer accessing these TD's.
* Remove all the TD's that are attached to the endpoint.
*/
void
{
"ohci_traverse_tds: ph = 0x%p ept = 0x%p",
"ohci_traverse_tds: addr (head) = 0x%x", addr);
"ohci_traverse_tds: addr (tail) = 0x%x", addr);
"ohci_traverse_tds: cpu head = 0x%p cpu tail = 0x%p",
"ohci_traverse_tds: iommu head = 0x%x iommu tail = 0x%x",
/*
* Traverse the list of TD's that are currently on the endpoint.
* These TD's have not been processed and will not be processed
* because the endpoint processing is stopped.
*/
/* Stop the the transfer timer */
}
/* Both head and tail pointers must be same */
"ohci_traverse_tds: head = 0x%p tail = 0x%p",
/* Update the pointer in the endpoint descriptor */
"ohci_traverse_tds: new head = 0x%x",
"ohci_traverse_tds: tailp = 0x%x headp = 0x%x",
}
/*
* ohci_done_list_tds:
*
* There may be TD's on the done list that have not been processed yet. Walk
* through these TD's and mark them as RECLAIM. All the mappings for the TD
* will be torn down, so the interrupt handle is alerted of this fact through
* the RECLAIM flag.
*/
static void
{
"ohci_done_list_tds:");
/* Process the transfer wrappers for this pipe */
while (next_tw) {
if (head_td) {
/*
* Walk through each TD for this transfer
* wrapper. If a TD still exists, then it
* is currently on the done list.
*/
while (next_td) {
/* To free TD, set TD state to RECLAIM */
}
}
/* Stop the the transfer timer */
}
}
/*
* ohci_deallocate_td:
* NOTE: This function is also called from POLLED MODE.
*
* Deallocate a Host Controller's (HC) Transfer Descriptor (TD).
*/
void
{
"ohci_deallocate_td: old_td = 0x%p", (void *)old_td);
/*
* Obtain the transaction wrapper and tw will be
* NULL for the dummy and for the reclaim TD's.
*/
} else {
tw = (ohci_trans_wrapper_t *)
}
/*
* If this TD should be reclaimed, don't try to access its
* transfer wrapper.
*/
/*
* Take this TD off the transfer wrapper's list since
* the pipe is FIFO, this must be the first TD on the
* list.
*/
tw->tw_hctd_head =
if (tw->tw_hctd_head) {
}
/*
* If the head becomes NULL, then there are no more
* active TD's for this transfer wrapper. Also set
* the tail to NULL.
*/
} else {
/*
* If this is the last td on the list, make
* sure it doesn't point to yet another td.
*/
}
}
}
"ohci_deallocate_td: td 0x%p", (void *)old_td);
}
/*
* ohci_td_cpu_to_iommu:
* NOTE: This function is also called from POLLED MODE.
*
* This function converts for the given Transfer Descriptor (TD) CPU address
* to IO address.
*/
{
(ohcip->ohci_td_pool_addr))));
sizeof (ohci_td_t) * ohci_td_pool_size);
return (td);
}
/*
* ohci_td_iommu_to_cpu:
* NOTE: This function is also called from POLLED MODE.
*
* This function converts for the given Transfer Descriptor (TD) IO address
* to CPU address.
*/
{
return (NULL);
}
return (td);
}
/*
* ohci_allocate_tds_for_tw:
*
* Allocate n Transfer Descriptors (TD) from the TD buffer pool and places it
* into the TW.
*
* Returns USB_NO_RESOURCES if it was not able to allocate all the requested TD
* otherwise USB_SUCCESS.
*/
static int
{
int i;
int error = USB_SUCCESS;
for (i = 0; i < td_count; i++) {
"ohci_allocate_tds_for_tw: "
"Unable to allocate %lu TDs",
td_count);
break;
}
}
}
return (error);
}
/*
* ohci_allocate_tw_resources:
*
* Allocate a Transaction Wrapper (TW) and n Transfer Descriptors (TD)
* from the TD 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 ohci_trans_wrapper_t *
{
"ohci_allocate_tw_resources: Unable to allocate TW");
} else {
USB_SUCCESS) {
} else {
}
}
return (tw);
}
/*
* ohci_free_tw_tds_resources:
*
* Free all allocated resources for Transaction Wrapper (TW).
* Does not free the TW itself.
*/
static void
{
/* Save the pointer to the next td before destroying it */
}
}
/*
* Transfer Wrapper functions
*
* ohci_create_transfer_wrapper:
*
* Create a Transaction Wrapper (TW) for non-isoc transfer types
* and this involves the allocating of DMA resources.
*/
static ohci_trans_wrapper_t *
{
int result;
int kmem_flag;
int (*dmamem_wait)(caddr_t);
"ohci_create_transfer_wrapper: length = 0x%lx flags = 0x%x",
/* isochronous pipe should not call into this function */
return (NULL);
}
/* SLEEP flag should not be used in interrupt context */
if (servicing_interrupt()) {
} else {
}
/* Allocate space for the transfer wrapper */
"ohci_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 */
/* Allocate the DMA handle */
if (result != DDI_SUCCESS) {
"ohci_create_transfer_wrapper: Alloc handle failed");
return (NULL);
}
/* The host controller will be little endian */
/* Allocate the memory */
if (result != DDI_SUCCESS) {
"ohci_create_transfer_wrapper: dma_mem_alloc fail");
return (NULL);
}
/* Bind the handle */
if (result != DDI_DMA_MAPPED) {
return (NULL);
}
tw->tw_cookie_idx = 0;
tw->tw_dma_offs = 0;
/*
* Only allow one wrapper to be added at a time. Insert the
* new transaction wrapper into the list for this pipe.
*/
} else {
}
/* Store the transfer length */
/* Store a back pointer to the pipe private structure */
/* Store the transfer type - synchronous or asynchronous */
/* Get and Store 32bit ID */
"ohci_create_transfer_wrapper: tw = 0x%p, ncookies = %u",
return (tw);
}
/*
* Transfer Wrapper functions
*
* ohci_create_isoc_transfer_wrapper:
*
* Create a Transaction Wrapper (TW) for isoc transfer
* and this involves the allocating of DMA resources.
*/
static ohci_trans_wrapper_t *
{
int result;
int kmem_flag;
int (*dmamem_wait)(caddr_t);
"ohci_create_isoc_transfer_wrapper: length = 0x%lx flags = 0x%x",
/* non-isochronous pipe should not call into this function */
return (NULL);
}
/* SLEEP flag should not be used in interrupt context */
if (servicing_interrupt()) {
} else {
}
/* Allocate space for the transfer wrapper */
"ohci_create_transfer_wrapper: kmem_zalloc failed");
return (NULL);
}
/* Allocate space for the isoc buffer handles */
"ohci_create_isoc_transfer_wrapper: kmem_alloc "
"isoc buffer failed");
return (NULL);
}
/* allow sg lists for transfer wrapper dma memory */
/* The host controller will be little endian */
for (i = 0; i < td_count; i++) {
} else {
}
/* Allocate the DMA handle */
if (result != DDI_SUCCESS) {
"ohci_create_isoc_transfer_wrapper: "
"Alloc handle failed");
for (j = 0; j < i; j++) {
}
return (NULL);
}
/* Compute the memory length */
for (xfer_size = 0, j = 0; j < frame_count; j++) {
}
/* Allocate the memory */
if (result != DDI_SUCCESS) {
"ohci_create_isoc_transfer_wrapper: "
"dma_mem_alloc %d fail", i);
for (j = 0; j < i; j++) {
}
return (NULL);
}
/* Bind the handle */
if ((result == DDI_DMA_MAPPED) &&
(ccount <= OHCI_DMA_ATTR_TD_SGLLEN)) {
continue;
} else {
"ohci_create_isoc_transfer_wrapper: "
"Bind handle %d failed", i);
if (result == DDI_DMA_MAPPED) {
}
for (j = 0; j < i; j++) {
}
return (NULL);
}
}
/*
* Only allow one wrapper to be added at a time. Insert the
* new transaction wrapper into the list for this pipe.
*/
} else {
}
/* Store the transfer length */
/* Store the td numbers */
/* Store a back pointer to the pipe private structure */
/* Store the transfer type - synchronous or asynchronous */
/* Get and Store 32bit ID */
"ohci_create_isoc_transfer_wrapper: tw = 0x%p", tw);
return (tw);
}
/*
* ohci_start_xfer_timer:
*
* Start the timer for the control, bulk and for one time interrupt
* transfers.
*/
/* ARGSUSED */
static void
{
"ohci_start_xfer_timer: tw = 0x%p", tw);
/*
* 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) {
/*
* Increase timeout value by one second and this extra one
* second is used to halt the endpoint if given transfer
* times out.
*/
tw->tw_timeout++;
/*
* Add this transfer wrapper into the transfer timeout list.
*/
if (ohcip->ohci_timeout_list) {
}
}
}
/*
* ohci_stop_xfer_timer:
*
* Start the timer for the control, bulk and for one time interrupt
* transfers.
*/
void
{
"ohci_stop_xfer_timer: tw = 0x%p", tw);
/*
* The timeout handling is done only for control, bulk
* and for one time Interrupt transfers.
*/
return;
}
switch (flag) {
case OHCI_REMOVE_XFER_IFLAST:
break;
}
/* FALLTHRU */
case OHCI_REMOVE_XFER_ALWAYS:
(ohcip->ohci_timer_id)) {
/* Reset the timer id to zero */
ohcip->ohci_timer_id = 0;
}
break;
default:
break;
}
}
/*
* ohci_xfer_timeout_handler:
*
* Control or bulk transfer timeout handler.
*/
static void
ohci_xfer_timeout_handler(void *arg)
{
"ohci_xfer_timeout_handler: ohcip = 0x%p", ohcip);
/* Set the required flags */
/*
* Check whether still timeout handler is valid.
*/
if (ohcip->ohci_timer_id) {
/* Reset the timer id to zero */
ohcip->ohci_timer_id = 0;
} else {
return;
}
/* Get the transfer timeout list head */
/*
* Process ohci timeout list and look whether the timer
* has expired for any transfers. Create a temporary list
* of expired transfers and process them later.
*/
while (tw) {
/* Get the transfer on the timeout list */
tw->tw_timeout--;
/*
* Set the sKip bit to stop all transactions on
* this pipe
*/
/* Reset dma sync flag */
flags &= ~OHCI_FLAGS_DMA_SYNC;
}
/* Remove tw from the timeout list */
if (tw->tw_timeout <= 0) {
/* Add tw to the end of expire list */
if (exp_xfer_list_head) {
} else {
}
}
}
/* Get the expired transfer timeout list head */
/* Sync ED and TD pool */
}
/*
* Process the expired transfers by notifing the corrsponding
* client driver through the exception callback.
*/
while (tw) {
/* Get the transfer on the expired transfer timeout list */
while (td) {
/* Set TD state to TIMEOUT */
/* Get the next TD from the wrapper */
}
}
}
/*
* ohci_remove_tw_from_timeout_list:
*
* Remove Control or bulk transfer from the timeout list.
*/
static void
{
"ohci_remove_tw_from_timeout_list: tw = 0x%p", tw);
} else {
}
}
}
/* Reset the xfer timeout */
}
/*
* ohci_start_timer:
*
* Start the ohci timer
*/
static void
{
"ohci_start_timer: ohcip = 0x%p", ohcip);
/*
* Start the global timer only if currently timer is not
* running and if there are any transfers on the timeout
* list. This timer will be per USB Host Controller.
*/
}
}
/*
* ohci_deallocate_tw_resources:
* NOTE: This function is also called from POLLED MODE.
*
* Deallocate of a Transaction Wrapper (TW) and this involves the freeing of
* of DMA resources.
*/
void
{
"ohci_deallocate_tw_resources: tw = 0x%p", tw);
/*
* If the transfer wrapper has no Host Controller (HC)
* Transfer Descriptors (TD) associated with it, then
* remove the transfer wrapper.
*/
if (tw->tw_hctd_head) {
return;
}
/* Make sure we return all the unused td's to the pool as well */
/*
* 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.
*/
} else {
}
} else {
}
}
}
}
}
/*
* ohci_free_dma_resources:
*
* Free dma resources of a Transfer Wrapper (TW) and also free the TW.
*/
static void
{
"ohci_free_dma_resources: ph = 0x%p", (void *)ph);
/* Process the Transfer Wrappers */
while (next_tw) {
"ohci_free_dma_resources: Free TW = 0x%p", (void *)tw);
}
/* Adjust the head and tail pointers */
}
/*
* ohci_free_tw:
*
* Free the Transfer Wrapper (TW).
*/
static void
{
int rval, i;
"ohci_free_tw: tw = 0x%p", tw);
/* Free 32bit ID */
if (tw->tw_isoc_strtlen > 0) {
for (i = 0; i < tw->tw_ncookies; i++) {
}
}
if (tw->tw_ncookies > 0) {
}
}
/* Free transfer wrapper */
}
/*
* Interrupt Handling functions
*/
/*
* ohci_intr:
*
* OpenHCI (OHCI) interrupt handling routine.
*/
static uint_t
{
/*
* Suppose if we switched to the polled mode from the normal
* mode when interrupt handler is executing then we need to
* save the interrupt status information in the polled mode
* to avoid race conditions. The following flag will be set
* and reset on entering & exiting of ohci interrupt handler
* respectively. This flag will be used in the polled mode
* to check whether the interrupt handler was running when we
* switched to the polled mode from the normal mode.
*/
/* Temporarily turn off interrupts */
/*
* Handle any missed ohci interrupt especially WriteDoneHead
* and SOF interrupts because of previous polled mode switch.
*/
/*
* Now process the actual ohci interrupt events that caused
* invocation of this ohci interrupt handler.
*/
/*
* Updating the WriteDoneHead interrupt:
*
* (a) Host Controller
*
* - First Host controller (HC) checks whether WDH bit
* in the interrupt status register is cleared.
*
* - If WDH bit is cleared then HC writes new done head
* list information into the HCCA done head field.
*
* - Set WDH bit in the interrupt status register.
*
* (b) Host Controller Driver (HCD)
*
* - First read the interrupt status register. The HCCA
* done head and WDH bit may be set or may not be set
* while reading the interrupt status register.
*
* - Read the HCCA done head list. By this time may be
* HC has updated HCCA done head and WDH bit in ohci
* interrupt status register.
*
* - If done head is non-null and if WDH bit is not set
* then Host Controller has updated HCCA done head &
* WDH bit in the interrupt stats register in between
* reading the interrupt status register & HCCA done
* head. In that case, definitely WDH bit will be set
* in the interrupt status register & driver can take
* it for granted.
*
* Now read the Interrupt Status & Interrupt enable register
* to determine the exact interrupt events.
*/
if (ohcip->ohci_hccap) {
/* Sync HCCA area */
/* Read and Save the HCCA DoneHead value */
"ohci_intr: Done head! 0x%p", (void *)done_head);
}
/* Update kstat values */
/*
* Look at the HccaDoneHead, if it is a non-zero valid address,
* a done list update interrupt is indicated. Otherwise, this
* intr bit is cleared.
*/
/* Set the WriteDoneHead bit in the interrupt events */
intr |= HCR_INTR_WDH;
} else {
/* Clear the WriteDoneHead bit */
intr &= ~HCR_INTR_WDH;
}
/*
* 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) {
/* Reset the interrupt handler flag */
return (DDI_INTR_UNCLAIMED);
}
"Interrupt status 0x%x", intr);
/*
* Check for Frame Number Overflow.
*/
if (intr & HCR_INTR_FNO) {
"ohci_intr: Frame Number Overflow");
}
if (intr & HCR_INTR_SOF) {
"ohci_intr: Start of Frame");
/* Set ohci_sof_flag indicating SOF interrupt occurred */
/* Disabel SOF interrupt */
/*
* Call cv_broadcast on every SOF interrupt to wakeup
* all the threads that are waiting the SOF. Calling
* cv_broadcast on every SOF has no effect even if no
* threads are waiting for the SOF.
*/
}
if (intr & HCR_INTR_SO) {
"ohci_intr: Schedule overrun");
ohcip->ohci_so_error++;
}
"ohci_intr: Done Head");
/*
* Currently if we are processing one WriteDoneHead
* interrupt and also if we switched to the polled
* mode at least once during this time, then there
* may be chance that Host Controller generates one
* more Write DoneHead or Start of Frame interrupts
* for the normal since the polled code clears WDH &
* SOF interrupt bits before returning to the normal
* mode. Under this condition, we must not clear the
* HCCA done head field & also we must not clear WDH
* interrupt bit in the interrupt status register.
*/
/* Reset the done head to NULL */
} else {
intr &= ~HCR_INTR_WDH;
}
/* Clear the current done head field */
}
/* Process endpoint reclaimation list */
if (ohcip->ohci_reclaim_list) {
}
if (intr & HCR_INTR_RD) {
"ohci_intr: Resume Detected");
}
if (intr & HCR_INTR_RHSC) {
"ohci_intr: Root hub status change");
}
if (intr & HCR_INTR_OC) {
"ohci_intr: Change ownership");
}
if (intr & HCR_INTR_UE) {
"ohci_intr: Unrecoverable error");
}
/* Acknowledge the interrupt */
/* Clear the current interrupt event field */
/*
* Reset the following flag indicating exiting the interrupt
* handler and this flag will be used in the polled mode to
* do some extra processing.
*/
/*
* 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(hcr_intr_status);
"Interrupt handling completed");
return (DDI_INTR_CLAIMED);
}
/*
* Check whether done_head is a valid td point address.
* It should be non-zero, 16-byte aligned, and fall in ohci_td_pool.
*/
static int
{
return (USB_SUCCESS);
} else {
return (USB_FAILURE);
}
}
/*
* ohci_handle_missed_intr:
*
* Handle any ohci missed interrupts because of polled mode switch.
*/
static void
{
/*
* Check whether we have missed any ohci interrupts because
* of the polled mode switch during previous ohci interrupt
* handler execution. Only Write Done Head & SOF interrupts
* saved in the polled mode. First process these interrupts
* before processing actual interrupts that caused invocation
* of ohci interrupt handler.
*/
if (!ohci_intr_sts->ohci_missed_intr_sts) {
/* No interrupts are missed, simply return */
return;
}
"ohci_handle_missed_intr: Handle ohci missed interrupts");
/*
* The functionality and importance of critical code section
* in the normal mode ohci interrupt handler & its usage in
* the polled mode is explained below.
*
* (a) Normal mode:
*
* - Set the flag indicating that processing critical
* code in ohci interrupt handler.
*
* - Process the missed ohci interrupts by copying the
* miised interrupt events and done head list fields
* information to the critical interrupt event & done
* list fields.
*
* - Reset the missed ohci interrupt events & done head
* list fields so that the new missed interrupt event
* and done head list information can be saved.
*
* - All above steps will be executed with in critical
* section of the interrupt handler.Then ohci missed
* interrupt handler will be called to service missed
* ohci interrupts.
*
* (b) Polled mode:
*
* - On entering the polled code,it checks for critical
* section code execution within the normal mode ohci
* interrupt handler.
*
* - If the critical section code is executing in normal
* mode ohci interrupt handler and if copying of ohci
* missed interrupt events & done head list fields to
* the critical fields is finished then save the "any
* missed interrupt events & done head list" because
* of current polled mode switch into "critical missed
* interrupt events & done list fields" instead actual
* missed events and done list fields.
*
* - Otherwise save "any missed interrupt events & done
* list" because of this current polled mode switch
* in the actual missed interrupt events & done head
* list fields.
*/
/*
* Set flag indicating that interrupt handler is processing
* critical interrupt code, so that polled mode code checks
* for this condition & will do extra processing as explained
* above in order to aviod the race conditions.
*/
if (ohci_intr_sts->ohci_missed_done_lst) {
}
if (intr & HCR_INTR_SOF) {
"ohci_handle_missed_intr: Start of Frame");
/*
* Call cv_broadcast on every SOF interrupt to wakeup
* all the threads that are waiting the SOF. Calling
* cv_broadcast on every SOF has no effect even if no
* threads are waiting for the SOF.
*/
}
"ohci_handle_missed_intr: Done Head");
/* Clear the critical done head field */
}
/* Clear the critical interrupt event field */
}
/*
* ohci_handle_ue:
*
* Handling of Unrecoverable Error interrupt (UE).
*/
static void
{
"ohci_handle_ue: Handling of UE interrupt");
/*
* First check whether current UE error occured due to USB or
* due to some other subsystem. This can be verified by reading
* usb frame numbers before & after a delay of few milliseconds.
* If usb frame number read after delay is greater than the one
* read before delay, then, USB subsystem is fine. In this case,
* disable UE error interrupt and return without shutdowning the
* USB subsystem.
*
* Otherwise, if usb frame number read after delay is less than
* or equal to one read before the delay, then, current UE error
* occured from USB susbsystem. In this case,go ahead with actual
* UE error recovery procedure.
*
* Get the current usb frame number before waiting for few
* milliseconds.
*/
/* Wait for few milliseconds */
/*
* Get the current usb frame number after waiting for
* milliseconds.
*/
"ohci_handle_ue: Before Frm No 0x%llx After Frm No 0x%llx",
if (after_frame_number > before_frame_number) {
/* Disable UE interrupt */
return;
}
/*
* This UE is due to USB hardware error. Reset ohci controller
* and reprogram to bring it back to functional state.
*/
"Unrecoverable USB Hardware Error");
/* Disable UE interrupt */
/* Set host controller soft state to error */
}
}
/*
* ohci_handle_frame_number_overflow:
*
* Update software based usb frame number part on every frame number
* overflow interrupt.
*
* NOTE: This function is also called from POLLED MODE.
*
* Refer ohci spec 1.0a, section 5.3, page 81 for more details.
*/
void
{
"ohci_handle_frame_number_overflow:");
"ohci_handle_frame_number_overflow:"
}
/*
* ohci_handle_endpoint_reclaimation:
*
* Reclamation of Host Controller (HC) Endpoint Descriptors (ED).
*/
static void
{
"ohci_handle_endpoint_reclaimation:");
/*
* Deallocate all Endpoint Descriptors (ED) which are on the
* reclaimation list. These ED's are already removed from the
* interrupt lattice tree.
*/
while (ohcip->ohci_reclaim_list) {
"ohci_handle_endpoint_reclaimation:"
"current frame number 0x%llx endpoint frame number 0x%llx",
/*
* Deallocate current endpoint only if endpoint's usb frame
* number is less than or equal to current usb frame number.
*
* If endpoint's usb frame number is greater than the current
* usb frame number, ignore rest of the endpoints in the list
* since rest of the endpoints are inserted into the reclaim
* list later than the current reclaim endpoint.
*/
break;
}
/* Get the next endpoint from the rec. list */
/* Free 32bit ID */
/* Deallocate the endpoint */
}
}
/*
* ohci_traverse_done_list:
*/
static void
{
"ohci_traverse_done_list:");
/* Sync ED and TD pool */
/* Reverse the done list */
/* Traverse the list of transfer descriptors */
while (td) {
/* Check for TD state */
"ohci_traverse_done_list:\n\t"
/*
* Obtain the transfer wrapper only if the TD is
* not marked as RECLAIM.
*
* A TD that is marked as RECLAIM has had its DMA
* mappings, ED, TD and pipe private structure are
* ripped down. Just deallocate this TD.
*/
if (state != HC_TD_RECLAIM) {
"ohci_traverse_done_list: PP = 0x%p TW = 0x%p",
}
/*
* Don't process the TD if its state is marked as
* either RECLAIM or TIMEOUT.
*
* A TD that is marked as TIMEOUT has already been
* processed by TD timeout handler & client driver
* has been informed through exception callback.
*/
/* Look at the error status */
} else {
/* handle the error condition */
}
} else {
"ohci_traverse_done_list: TD State = %d", state);
}
/*
* Save a pointer to the current transfer descriptor
*/
/* Deallocate this transfer descriptor */
/*
* Deallocate the transfer wrapper if there are no more
* TD's for the transfer wrapper. ohci_deallocate_tw_resources()
* will not deallocate the tw for a periodic endpoint
* since it will always have a TD attached to it.
*
* Do not deallocate the TW if it is a isoc or intr pipe in.
* The tw's are reused.
*
* An TD that is marked as reclaim doesn't have a pipe
* or a TW associated with it anymore so don't call this
* function.
*/
if (state != HC_TD_RECLAIM) {
}
}
}
/*
* ohci_reverse_done_list:
*
* Reverse the order of the Transfer Descriptor (TD) Done List.
*/
static ohci_td_t *
{
"ohci_reverse_done_list:");
/* At first, both the tail and head pointers point to the same elem */
/* See if the list has only one element */
return (cpu_new_head);
}
/* Advance the head pointer */
cpu_new_head = (ohci_td_t *)
/* The new tail now points to nothing */
/* Reverse the list and store the pointers as CPU addresses */
while (cpu_save) {
}
return (cpu_new_head);
}
/*
* ohci_parse_error:
*
* Parse the result for any errors.
*/
static usb_cr_t
{
"ohci_parse_error:");
/* Obtain the transfer wrapper from the TD */
tw = (ohci_trans_wrapper_t *)
/* Obtain the pipe private structure */
/*
* Check the condition code of completed TD and report errors
* if any. This checking will be done both for the general and
* the isochronous TDs.
*/
USB_CR_OK) {
} else {
}
/* Stop the the transfer timer */
/*
* The isochronous endpoint needs additional error checking
* and special processing.
*/
/* always reset error */
}
return (error);
}
/*
* ohci_parse_isoc_error:
*
* Check for any errors in the isochronous data packets. Also fillup
* the status for each of the isochrnous data packets.
*/
void
{
int i;
"ohci_parse_isoc_error: td 0x%p", td);
HC_ITD_FC) >> HC_ITD_FC_SHIFT;
"ohci_parse_isoc_error: frame count %d", fc);
/*
* Get the address of current usb isochronous request
* and array of packet descriptors.
*/
for (i = 0; i <= fc; i++) {
if (toggle) {
toggle = 0;
} else {
toggle = 1;
}
/* Write the status of isoc data packet */
if (isoc_pkt_descr->isoc_pkt_status) {
/* Increment isoc data packet error count */
}
/*
* Get the address of next isoc data packet descriptor.
*/
}
}
/*
* ohci_check_for_error:
*
* Check for any errors.
*/
static usb_cr_t
{
"ohci_check_for_error: td = 0x%p ctrl = 0x%x",
switch (ctrl) {
case HC_TD_CC_NO_E:
"ohci_check_for_error: No Error");
break;
case HC_TD_CC_CRC:
"ohci_check_for_error: CRC error");
error = USB_CR_CRC;
break;
case HC_TD_CC_BS:
"ohci_check_for_error: Bit stuffing");
break;
case HC_TD_CC_DTM:
"ohci_check_for_error: Data Toggle Mismatch");
break;
case HC_TD_CC_STALL:
"ohci_check_for_error: Stall");
break;
case HC_TD_CC_DNR:
"ohci_check_for_error: Device not responding");
break;
case HC_TD_CC_PCF:
"ohci_check_for_error: PID check failure");
break;
case HC_TD_CC_UPID:
"ohci_check_for_error: Unexpected PID");
break;
case HC_TD_CC_DO:
"ohci_check_for_error: Data overrrun");
break;
case HC_TD_CC_DU:
/*
* Check whether short packets are acceptable.
* If so don't report error to client drivers
* and restart the endpoint. Otherwise report
* data underrun error to client driver.
*/
if (xfer_attrs & USB_ATTRS_SHORT_XFER_OK) {
if ((ep_attrs & USB_EP_ATTR_MASK) !=
/*
* Cleanup the remaining resources that may have
* been allocated for this transfer.
*/
td) == USB_SUCCESS) {
/* Clear the halt bit */
~HC_EPT_Halt));
} else {
}
}
} else {
"ohci_check_for_error: Data underrun");
}
break;
case HC_TD_CC_BO:
"ohci_check_for_error: Buffer overrun");
break;
case HC_TD_CC_BU:
"ohci_check_for_error: Buffer underrun");
break;
case HC_TD_CC_NA:
default:
"ohci_check_for_error: Not accessed");
break;
}
if (error) {
"ohci_check_for_error: Error %d Device address %d "
}
return (error);
}
/*
* ohci_handle_error:
*
* Inform USBA about occured transaction errors by calling the USBA callback
* routine.
*/
static void
{
"ohci_handle_error: error = 0x%x", error);
/* Print the values in the td */
/* Obtain the transfer wrapper from the TD */
tw = (ohci_trans_wrapper_t *)
/* Obtain the pipe private structure */
/*
* Special error handling
*/
switch (attributes) {
case USB_EP_ATTR_CONTROL:
USB_EP_ATTR_MASK) ==
break;
}
/* FALLTHROUGH */
case USB_EP_ATTR_BULK:
/*
* Call ohci_sendup_td_message
* to send message to upstream.
*/
return;
case USB_EP_ATTR_INTR:
if (curr_intr_reqp->intr_attributes &
}
/* Decrement periodic in request count */
break;
case USB_EP_ATTR_ISOCH:
default:
break;
}
} else {
switch (attributes) {
case USB_EP_ATTR_BULK:
case USB_EP_ATTR_INTR:
/*
* If "CurrentBufferPointer" of Transfer
* Descriptor (TD) is not equal to zero,
* then we sent less data to the device
* than requested by client. In that case,
* return the mblk after updating the
* data->r_ptr.
*/
"ohci_handle_error: requested data %lu "
if (attributes == USB_EP_ATTR_BULK) {
} else {
}
/* Increment the read pointer */
}
break;
default:
break;
}
}
/*
* Callback the client with the
* failure reason.
*/
/* Check anybody is waiting for transfers completion event */
}
/*
* ohci_cleanup_data_underrun:
*
* Cleans up resources when a short xfer occurs
*/
static int
{
/* Check if this TD is the last td in the tw */
/* There is no need for cleanup */
return (USB_SUCCESS);
}
/*
* Make sure the ED is halted before we change any td's.
* If for some reason it is not halted, return error to client
* driver so they can reset the port.
*/
if (!(hced_head & HC_EPT_Halt)) {
"ohci_cleanup_data_underrun: Unable to clean up a short "
" Device address %d Endpoint number %d",
(hced_ctrl & HC_EPT_FUNC),
return (USB_FAILURE);
}
/*
* Get the address of the first td of the next transfer (tw).
* This td, may currently be a dummy td, but when a new request
* arrives, it will be transformed into a regular td.
*/
/* Set ED head to this last td */
(last_td_addr & HC_EPT_TD_HEAD) |
(hced_head & ~HC_EPT_TD_HEAD));
/*
* Start removing all the unused TD's from the TW,
* but keep the first one.
*/
/*
* Get the last_td, the next td in the tw list.
* Afterwards completely disassociate the current td from other tds
*/
/*
* Iterate down the tw list and deallocate them
*/
tw->tw_num_tds--;
/* Disassociate this td from it's TW and set to RECLAIM */
}
return (USB_SUCCESS);
}
/*
* ohci_handle_normal_td:
*/
static void
{
"ohci_handle_normal_td:");
/* Obtain the pipe private structure */
/* Check anybody is waiting for transfers completion event */
}
/*
* ohci_handle_ctrl_td:
*
* Handle a control Transfer Descriptor (TD).
*/
/* ARGSUSED */
static void
void *tw_handle_callback_value)
{
"ohci_handle_ctrl_td: pp = 0x%p tw = 0x%p td = 0x%p state = 0x%x",
/*
* Check which control transfer phase got completed.
*/
tw->tw_num_tds--;
case OHCI_CTRL_SETUP_PHASE:
break;
case OHCI_CTRL_DATA_PHASE:
/*
* If "CurrentBufferPointer" of Transfer Descriptor (TD)
* is not equal to zero, then we received less data from
* the device than requested by us. In that case, get the
* actual received data size.
*/
"ohci_handle_ctrl_qtd: requested data %lu "
/* Save actual received data length */
}
"Data complete: pp 0x%p td 0x%p",
break;
case OHCI_CTRL_STATUS_PHASE:
/*
* Call ohci_sendup_td_message
* to send message to upstream.
*/
} else {
}
break;
}
}
/*
* ohci_handle_bulk_td:
*
* Handle a bulk Transfer Descriptor (TD).
*/
/* ARGSUSED */
static void
void *tw_handle_callback_value)
{
"ohci_handle_bulk_td:");
/*
* Decrement the TDs counter and check whether all the bulk
* data has been send or received. If TDs counter reaches
* zero then inform client driver about completion current
* bulk request. Other wise wait for completion of other bulk
* TDs or transactions on this pipe.
*/
if (--tw->tw_num_tds != 0) {
return;
}
/*
* If this is a bulk in pipe, return the data to the client.
* For a bulk out pipe, there is no need to do anything.
*/
if ((eptd->bEndpointAddress &
USB_EP_DIR_MASK) == USB_EP_DIR_OUT) {
"ohci_handle_bulk_td: Bulk out pipe");
/* Do the callback */
return;
}
/* Call ohci_sendup_td_message to send message to upstream */
}
/*
* ohci_handle_intr_td:
*
* Handle a interrupt Transfer Descriptor (TD).
*/
/* ARGSUSED */
static void
void *tw_handle_callback_value)
{
int error = USB_SUCCESS;
"ohci_handle_intr_td: pp=0x%p tw=0x%p td=0x%p"
/* Get the interrupt xfer attributes */
/*
* For a Interrupt OUT pipe, we just callback and we are done
*/
"ohci_handle_intr_td: Intr out pipe, intr_reqp=0x%p,"
/* Do the callback */
return;
}
/* Decrement number of interrupt request count */
/*
* Check usb flag whether USB_FLAGS_ONE_XFER flag is set
* and if so, free duplicate request.
*/
if (attrs & USB_ATTRS_ONE_XFER) {
}
/* Call ohci_sendup_td_message to callback into client */
/*
* If interrupt pipe state is still active, insert next Interrupt
* request into the Host Controller's Interrupt list. Otherwise
* you are done.
*/
return;
}
USB_SUCCESS) {
error = USB_FAILURE;
error = USB_FAILURE;
}
}
if (error != USB_SUCCESS) {
/*
* Set pipe state to stop polling and error to no
* resource. Don't insert any more interrupt polling
* requests.
*/
} else {
/* Increment number of interrupt request count */
}
}
/*
* ohci_handle_one_xfer_completion:
*/
static void
{
"ohci_handle_one_xfer_completion: tw = 0x%p", tw);
/*
* For one xfer, we need to copy back data ptr
* and free current request
*/
intr_data = ((usb_intr_req_t *)
/* Now free duplicate current request */
ph->p_req_count--;
/* Make client's request the current request */
}
/*
* ohci_handle_isoc_td:
*
* Handle an isochronous Transfer Descriptor (TD).
*/
/* ARGSUSED */
static void
void *tw_handle_callback_value)
{
int error = USB_SUCCESS;
"ohci_handle_isoc_td: pp=0x%p tw=0x%p td=0x%p"
/*
* Decrement the TDs counter and check whether all the isoc
* data has been send or received. If TDs counter reaches
* zero then inform client driver about completion current
* isoc request. Otherwise wait for completion of other isoc
* TDs or transactions on this pipe.
*/
if (--tw->tw_num_tds != 0) {
return;
}
/*
* If this is a isoc in pipe, return the data to the client.
* For a isoc out pipe, there is no need to do anything.
*/
"ohci_handle_isoc_td: Isoc out pipe, isoc_reqp=0x%p,"
/* Do the callback */
return;
}
/* Decrement number of IN isochronous request count */
/* Call ohci_sendup_td_message to send message to upstream */
/*
* If isochronous pipe state is still active, insert next isochronous
* request into the Host Controller's isochronous list.
*/
return;
}
USB_SUCCESS) {
tw->tw_num_tds =
tw->tw_num_tds++;
}
error = USB_FAILURE;
error = USB_FAILURE;
}
}
if (error != USB_SUCCESS ||
/*
* Set pipe state to stop polling and error to no
* resource. Don't insert any more isoch polling
* requests.
*/
} else {
/* Increment number of IN isochronous request count */
}
}
/*
* ohci_tw_rebind_cookie:
*
* If the cookie associated with a DMA buffer has been walked, the cookie
* is not usable any longer. To reuse the DMA buffer, the DMA handle needs
* to rebind for cookies.
*/
static int
{
int rval, i;
"ohci_tw_rebind_cookie:");
for (i = 0; i < tw->tw_num_tds; i++) {
/*
* no need to rebind when there is
* only one cookie in a buffer
*/
continue;
}
/* unbind the DMA handle before rebinding */
"rebind dma_handle %d", i);
/* rebind the handle to get cookies */
if ((rval == DDI_DMA_MAPPED) &&
(ccount <= OHCI_DMA_ATTR_TD_SGLLEN)) {
} else {
return (USB_NO_RESOURCES);
}
}
} else {
if (tw->tw_cookie_idx != 0) {
/* unbind the DMA handle before rebinding */
tw->tw_ncookies = 0;
"rebind dma_handle");
/* rebind the handle to get cookies */
if (rval == DDI_DMA_MAPPED) {
tw->tw_dma_offs = 0;
tw->tw_cookie_idx = 0;
} else {
return (USB_NO_RESOURCES);
}
}
}
return (USB_SUCCESS);
}
/*
* ohci_sendup_td_message:
* copy data, if necessary and do callback
*/
static void
{
"ohci_sendup_td_message:");
case USB_EP_ATTR_CONTROL:
/*
* Get the correct length, adjust it for the setup size
* which is not part of the data length in control end
* points. Update tw->tw_length for future references.
*/
} else {
}
/* Set the length of the buffer to skip */
break;
}
/* FALLTHRU */
case USB_EP_ATTR_BULK:
case USB_EP_ATTR_INTR:
/*
* If error is "data overrun", do not check for the
* "CurrentBufferPointer" and return whatever data
* received to the client driver.
*/
if (error == USB_CR_DATA_OVERRUN) {
break;
}
/*
* If "CurrentBufferPointer" of Transfer Descriptor
* (TD) is not equal to zero, then we received less
* data from the device than requested by us. In that
* case, get the actual received data size.
*/
"ohci_sendup_qtd_message: requested data %lu "
}
break;
case USB_EP_ATTR_ISOCH:
default:
break;
}
/* Copy the data into the mblk_t */
/* Get the message block */
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
if (length) {
int i;
/*
* Update kstat byte counts
* The control endpoints don't have direction bits so in
* order for control stats to be counted correctly an in
* bit must be faked on a control read.
*/
} else {
}
for (i = 0; i < tw->tw_ncookies; i++) {
}
tw->tw_pkt_idx = 0;
} else {
/* Sync IO buffer */
/* Copy the data into the message */
}
/* Increment the write pointer */
} else {
"ohci_sendup_td_message: Zero length packet");
}
}
/*
* ohci_get_td_residue:
*
* Calculate the bytes not transfered by the TD
*/
{
if ((buf_addr & 0xfffff000) ==
(end_addr & 0xfffff000)) {
} else {
(buf_addr & 0x00000fff) +
}
return (residue);
}
/*
* Miscellaneous functions
*/
/*
* ohci_obtain_state:
* NOTE: This function is also called from POLLED MODE.
*/
{
return (state);
}
/*
* ohci_state_is_operational:
*
* Check the Host controller state and return proper values.
*/
int
{
int val;
switch (ohcip->ohci_hc_soft_state) {
case OHCI_CTLR_INIT_STATE:
case OHCI_CTLR_SUSPEND_STATE:
val = USB_FAILURE;
break;
val = USB_SUCCESS;
break;
case OHCI_CTLR_ERROR_STATE:
break;
default:
val = USB_FAILURE;
break;
}
return (val);
}
/*
* ohci_do_soft_reset
*
* Do soft reset of ohci host controller.
*/
int
{
/* Increment host controller error count */
ohcip->ohci_hc_error++;
"ohci_do_soft_reset:"
/*
* Allocate space for saving current Host Controller
* registers. Don't do any recovery if allocation
* fails.
*/
ohci_save_regs = (ohci_regs_t *)
if (ohci_save_regs == NULL) {
"ohci_do_soft_reset: kmem_zalloc failed");
return (USB_FAILURE);
}
/* Save current ohci registers */
"ohci_do_soft_reset: Save reg = 0x%p", ohci_save_regs);
/* Disable all list processing and interrupts */
/* Wait for few milliseconds */
/* Root hub interrupt pipe timeout id */
/* Stop the root hub interrupt timer */
if (rh_timer_id) {
(void) untimeout(rh_timer_id);
}
/* Transfer timeout id */
/* Stop the global transfer timer */
if (xfer_timer_id) {
ohcip->ohci_timer_id = 0;
(void) untimeout(xfer_timer_id);
}
/* Process any pending HCCA DoneHead */
/* Reset the done head to NULL */
}
/* Process any pending hcr_done_head value */
}
/* Do soft reset of ohci host controller */
"ohci_do_soft_reset: Reset in progress");
/* Wait for reset to complete */
/* Reset HCCA HcFrameNumber */
/*
* Restore previous saved HC register value
* into the current HC registers.
*/
/*
* Set HcInterruptEnable to enable all interrupts except
* Root Hub Status change interrupt.
*/
/* Start Control and Bulk list processing */
/*
* Start up Control, Bulk, Periodic and Isochronous lists
* processing.
*/
/*
* Deallocate the space that allocated for saving
* HC registers.
*/
/* Resume the host controller */
(~HCR_CONTROL_HCFS)) | HCR_CONTROL_RESUME));
/* Wait for resume to complete */
/* Set the Host Controller Functional State to Operational */
(~HCR_CONTROL_HCFS)) | HCR_CONTROL_OPERAT));
/* Wait 10ms for HC to start sending SOF */
/*
* Get the current usb frame number before waiting for few
* milliseconds.
*/
/* Wait for few milliseconds */
/*
* Get the current usb frame number after waiting for few
* milliseconds.
*/
"ohci_do_soft_reset: Before Frm No 0x%llx After Frm No 0x%llx",
if (after_frame_number <= before_frame_number) {
"ohci_do_soft_reset: Soft reset failed");
return (USB_FAILURE);
}
/* Start the timer for the root hub interrupt pipe polling */
if (rh_timer_id) {
}
/* Start the global timer */
if (xfer_timer_id) {
}
return (USB_SUCCESS);
}
/*
* ohci_get_current_frame_number:
*
* Get the current software based usb frame number.
*/
{
/*
* Sync HCCA area only if this function
* is invoked in non interrupt context.
*/
if (!(ohci_intr_sts->ohci_intr_flag &
/* Sync HCCA area */
}
/*
* Calculate current software based usb frame number.
*
* This code accounts for the fact that frame number is
* updated by the Host Controller before the ohci driver
* gets an FrameNumberOverflow (FNO) interrupt that will
* adjust Frame higher part.
*
* Refer ohci specification 1.0a, section 5.4, page 86.
*/
return (usb_frame_number);
}
/*
* ohci_cpr_cleanup:
*
* Cleanup ohci state and other ohci specific informations across
* Check Point Resume (CPR).
*/
static void
{
/* Reset software part of usb frame number */
/* Reset Schedule Overrrun Error Counter */
ohcip->ohci_so_error = 0;
/* Reset HCCA HcFrameNumber */
}
/*
* ohci_get_xfer_attrs:
*
* Get the attributes of a particular xfer.
*/
static usb_req_attrs_t
{
usb_req_attrs_t attrs = 0;
"ohci_get_xfer_attrs:");
case USB_EP_ATTR_CONTROL:
attrs = ((usb_ctrl_req_t *)
break;
case USB_EP_ATTR_BULK:
attrs = ((usb_bulk_req_t *)
break;
case USB_EP_ATTR_INTR:
attrs = ((usb_intr_req_t *)
break;
case USB_EP_ATTR_ISOCH:
attrs = ((usb_isoc_req_t *)
break;
}
return (attrs);
}
/*
* ohci_allocate_periodic_in_resource
*
* Allocate interrupt/isochronous request structure for the
* interrupt/isochronous IN transfer.
*/
static int
{
"ohci_allocate_periodic_in_resource:"
/* Get the client periodic in request pointer */
/*
* 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 *)
} else {
}
if (curr_intr_reqp == NULL) {
"ohci_allocate_periodic_in_resource: Interrupt "
"request structure allocation failed");
return (USB_NO_RESOURCES);
}
if (client_periodic_in_reqp == NULL) {
/* For polled mode */
} else {
/* Check and save the timeout value */
}
} else {
if (curr_isoc_reqp == NULL) {
"ohci_allocate_periodic_in_resource: Isochronous"
"request structure allocation failed");
return (USB_NO_RESOURCES);
}
/*
* Save the client's isochronous request pointer and
* length of isochronous transfer in transfer wrapper.
* The dup'ed request is saved in pp_client_periodic_in_reqp
*/
}
ph->p_req_count++;
return (USB_SUCCESS);
}
/*
* ohci_wait_for_sof:
*
* Wait for couple of SOF interrupts
*/
static int
{
int rval, sof_wait_count;
"ohci_wait_for_sof");
if (rval != USB_SUCCESS) {
return (rval);
}
/* Get the number of clock ticks to wait */
sof_wait_count = 0;
/*
* Get the current usb frame number before waiting for the
* SOF interrupt event.
*/
while (sof_wait_count < MAX_SOF_WAIT_COUNT) {
/* Enable the SOF interrupt */
/* Wait for the SOF or timeout event */
/*
* Get the current usb frame number after woken up either
* from SOF interrupt or timer expired event.
*/
"ohci_wait_for_sof: before 0x%llx, after 0x%llx",
/*
* Return failure, if we are woken up becuase of timer expired
* event and if usb frame number has not been changed.
*/
if ((rval == -1) &&
/* Set host controller soft state to error */
return (USB_FAILURE);
}
/* Get new usb frame number */
}
}
return (USB_SUCCESS);
}
/*
* ohci_pipe_cleanup
*
* Cleanup ohci pipe.
*/
static void
{
"ohci_pipe_cleanup: ph = 0x%p", ph);
switch (pipe_state) {
case OHCI_PIPE_STATE_CLOSE:
if (OHCI_NON_PERIODIC_ENDPOINT(eptd)) {
/* Wait for the next SOF */
(void) ohci_wait_for_sof(ohcip);
break;
}
/* FALLTHROUGH */
case OHCI_PIPE_STATE_RESET:
/*
* Set the sKip bit to stop all transactions on
* this pipe
*/
break;
default:
return;
}
/*
* Wait for processing all completed transfers and
* to send results to upstream.
*/
/* Save the data toggle information */
/*
* Traverse the list of TD's on this endpoint and
* these TD's have outstanding transfer requests.
* Since the list processing is stopped, these tds
* can be deallocated.
*/
/*
* If all of the endpoint's TD's have been deallocated,
* then the DMA mappings can be torn down. If not there
* are some TD's on the done list that have not been
* processed. Tag these TD's so that they are thrown
* away when the done list is processed.
*/
/* Do callbacks for all unfinished requests */
/* Free DMA resources */
switch (pipe_state) {
case OHCI_PIPE_STATE_CLOSE:
break;
case OHCI_PIPE_STATE_RESET:
/* Set completion reason */
completion_reason = (pipe_state ==
/* Restore the data toggle information */
/*
* Clear the sKip bit to restart all the
* transactions on this pipe.
*/
/* Set pipe state to idle */
break;
}
/*
* Do the callback for the original client
* periodic IN request.
*/
if ((OHCI_PERIODIC_ENDPOINT(eptd)) &&
USB_EP_DIR_IN)) {
}
}
/*
* ohci_wait_for_transfers_completion:
*
* Wait for processing all completed transfers and to send results
* to upstream.
*/
static void
{
int rval;
"ohci_wait_for_transfers_completion: pp = 0x%p", pp);
if (rval != USB_SUCCESS) {
return;
}
pp->pp_count_done_tds = 0;
/* Process the transfer wrappers for this pipe */
while (next_tw) {
if (head_td) {
/*
* Walk through each TD for this transfer
* wrapper. If a TD still exists, then it
* is currently on the done list.
*/
while (next_td) {
/* TD is on the ED */
break;
}
HC_EPT_TD_TAIL)));
}
pp->pp_count_done_tds++;
}
}
}
}
"ohci_wait_for_transfers_completion: count_done_tds = 0x%x",
if (!pp->pp_count_done_tds) {
return;
}
/* Get the number of clock ticks to wait */
if (pp->pp_count_done_tds) {
"ohci_wait_for_transfers_completion: No transfers "
"completion confirmation received for 0x%x requests",
}
}
/*
* ohci_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.
*/
static void
{
"ohci_check_for_transfers_completion: pp = 0x%p", pp);
(pp->pp_cur_periodic_req_cnt == 0)) {
/* Reset pipe error to zero */
/* Do callback for original request */
}
if (pp->pp_count_done_tds) {
"ohci_check_for_transfers_completion:"
/* Decrement the done td count */
pp->pp_count_done_tds--;
if (!pp->pp_count_done_tds) {
"ohci_check_for_transfers_completion:"
"Sent transfers completion event pp = 0x%p", pp);
/* Send the transfer completion signal */
}
}
}
/*
* ohci_save_data_toggle:
*
* Save the data toggle information.
*/
static void
{
"ohci_save_data_toggle: ph = 0x%p", ph);
/* Reset the pipe error value */
/* Return immediately if it is a control or isoc pipe */
USB_EP_ATTR_MASK) == USB_EP_ATTR_ISOCH)) {
return;
}
/*
* Retrieve the data toggle information either from the endpoint
* (ED) or from the transfer descriptor (TD) depending on the
* situation.
*/
/* Get the data toggle information from the endpoint */
} else {
/*
* Retrieve the data toggle information depending on the
* master data toggle information saved in the transfer
* descriptor (TD) at the head of the endpoint (ED).
*
* Check for master data toggle information .
*/
/* Get the data toggle information from td */
} else {
/* Get the data toggle information from the endpoint */
}
}
/*
* 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.
*/
}
/*
* ohci_restore_data_toggle:
*
* Restore the data toggle information.
*/
static void
{
uint_t data_toggle = 0;
"ohci_restore_data_toggle: ph = 0x%p", ph);
/*
* Return immediately if it is a control or isoc pipe.
*/
USB_EP_ATTR_MASK) == USB_EP_ATTR_ISOCH)) {
return;
}
0);
/*
* Restore the data toggle bit depending on the
* previous data toggle information.
*/
if (data_toggle) {
} else {
}
}
/*
* ohci_handle_outstanding_requests
* NOTE: This function is also called from POLLED MODE.
*
* Deallocate interrupt/isochronous request structure for the
* interrupt/isochronous IN transfer. Do the callbacks for all
* unfinished requests.
*/
void
{
"ohci_handle_outstanding_requests: pp = 0x%p", pp);
/*
* Deallocate all the pre-allocated interrupt requests
*/
while (next_tw) {
/* Deallocate current interrupt request */
if (curr_xfer_reqp) {
if ((OHCI_PERIODIC_ENDPOINT(eptd)) &&
/* Decrement periodic in request count */
} else {
}
}
}
}
/*
* ohci_deallocate_periodic_in_resource
*
* Deallocate interrupt/isochronous request structure for the
* interrupt/isochronous IN transfer.
*/
static void
{
"ohci_deallocate_periodic_in_resource: "
/* Check the current periodic in request pointer */
if (curr_xfer_reqp) {
/*
* Reset periodic in request usb isoch
* packet request pointers to null.
*/
ph->p_req_count--;
/*
* Free pre-allocated interrupt
* or isochronous requests.
*/
switch (ep_attr & USB_EP_ATTR_MASK) {
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
}
}
/*
* ohci_do_client_periodic_in_req_callback
*
* Do callback for the original client periodic IN request.
*/
static void
{
"ohci_do_client_periodic_in_req_callback: "
/*
* 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) {
}
}
/*
* ohci_hcdi_callback()
*
* Convenience wrapper around usba_hcdi_cb() other than root hub.
*/
static void
{
uint_t pipe_state = 0;
"ohci_hcdi_callback: ph = 0x%p, tw = 0x%p, cr = 0x%x",
/* Set the pipe state as per completion reason */
switch (completion_reason) {
case USB_CR_OK:
break;
case USB_CR_NO_RESOURCES:
case USB_CR_NOT_SUPPORTED:
case USB_CR_STOPPED_POLLING:
case USB_CR_PIPE_RESET:
break;
case USB_CR_PIPE_CLOSING:
break;
default:
/*
* Set the pipe state to error
* except for the isoc pipe.
*/
if (attributes != USB_EP_ATTR_ISOCH) {
}
break;
}
} else {
}
}
/*
* ohci kstat functions
*/
/*
* ohci_create_stats:
*
* Allocate and initialize the ohci kstat structures
*/
static void
{
char kstatname[KSTAT_STRLEN];
char *usbtypes[USB_N_COUNT_KSTATS] =
{"ctrl", "isoch", "bulk", "intr"};
int i;
sizeof (ohci_intrs_stats_t) / sizeof (kstat_named_t),
if (OHCI_INTRS_STATS(ohcip)) {
"Interrupts Total", KSTAT_DATA_UINT64);
"Not Claimed", KSTAT_DATA_UINT64);
"Schedule Overruns", KSTAT_DATA_UINT64);
"Writeback Done Head", KSTAT_DATA_UINT64);
"Start Of Frame", KSTAT_DATA_UINT64);
"Resume Detected", KSTAT_DATA_UINT64);
"Unrecoverable Error", KSTAT_DATA_UINT64);
"Frame No. Overflow", KSTAT_DATA_UINT64);
"Root Hub Status Change", KSTAT_DATA_UINT64);
"Change In Ownership", KSTAT_DATA_UINT64);
}
}
if (OHCI_TOTAL_STATS(ohcip)) {
}
}
for (i = 0; i < USB_N_COUNT_KSTATS; i++) {
if (ohcip->ohci_count_stats[i]) {
}
}
}
}
/*
* ohci_destroy_stats:
*
* Clean up ohci kstat structures
*/
static void
{
int i;
if (OHCI_INTRS_STATS(ohcip)) {
}
if (OHCI_TOTAL_STATS(ohcip)) {
}
for (i = 0; i < USB_N_COUNT_KSTATS; i++) {
if (ohcip->ohci_count_stats[i]) {
}
}
}
/*
* ohci_do_intrs_stats:
*
* ohci status information
*/
static void
int val)
{
if (OHCI_INTRS_STATS(ohcip)) {
switch (val) {
case HCR_INTR_SO:
break;
case HCR_INTR_WDH:
break;
case HCR_INTR_SOF:
break;
case HCR_INTR_RD:
break;
case HCR_INTR_UE:
break;
case HCR_INTR_FNO:
break;
case HCR_INTR_RHSC:
break;
case HCR_INTR_OC:
break;
default:
break;
}
}
}
/*
* ohci_do_byte_stats:
*
* ohci data xfer information
*/
static void
{
if (dir == USB_EP_DIR_IN) {
switch (type) {
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
} else if (dir == USB_EP_DIR_OUT) {
switch (type) {
case USB_EP_ATTR_CONTROL:
break;
case USB_EP_ATTR_BULK:
break;
case USB_EP_ATTR_INTR:
break;
case USB_EP_ATTR_ISOCH:
break;
}
}
}
/*
* ohci_print_op_regs:
*
* Print Host Controller's (HC) Operational registers.
*/
static void
{
uint_t i;
"\n\tOHCI%d Operational Registers\n",
"\thcr_revision: 0x%x \t\thcr_control: 0x%x",
"\thcr_cmd_status: 0x%x \t\thcr_intr_enable: 0x%x",
"\thcr_intr_disable: 0x%x \thcr_HCCA: 0x%x",
"\thcr_periodic_curr: 0x%x \t\thcr_ctrl_head: 0x%x",
"\thcr_ctrl_curr: 0x%x \t\thcr_bulk_head: 0x%x",
"\thcr_bulk_curr: 0x%x \t\thcr_done_head: 0x%x",
"\thcr_frame_interval: 0x%x "
"\thcr_frame_number: 0x%x \thcr_periodic_strt: 0x%x",
"\thcr_transfer_ls: 0x%x \t\thcr_rh_descriptorA: 0x%x",
"\thcr_rh_descriptorB: 0x%x \thcr_rh_status: 0x%x",
"\tRoot hub port status");
"\thcr_rh_portstatus 0x%x: 0x%x ", i,
Get_OpReg(hcr_rh_portstatus[i]));
}
}
/*
* ohci_print_ed:
*/
static void
{
"ohci_print_ed: ed = 0x%p", (void *)ed);
"\thced_ctrl: 0x%x %s", ctrl,
}
/*
* ohci_print_td:
*/
static void
{
uint_t i;
"ohci_print_td: td = 0x%p", (void *)td);
for (i = 0; i < 4; i++) {
}
}