ohci_polled.c revision 7c478bd95313f5f23a4c958a745db2134aa03244
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* 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.
*
* This module contains the specific ohci code used in POLLED mode and this
* code is in a separate file since it will never become part of ohci driver.
*/
#include <sys/usb/hcd/openhci/ohcid.h>
#include <sys/usb/hcd/openhci/ohci_polled.h>
/*
* Internal Function Prototypes
*/
/* Polled initialization routines */
static int ohci_polled_init(
usba_pipe_handle_data_t *ph,
ohci_state_t *ohcip,
usb_console_info_impl_t *console_input_info);
/* Polled deinitialization routines */
static int ohci_polled_fini(ohci_polled_t *ohci_polledp);
/* Polled save state routines */
static void ohci_polled_save_state(ohci_polled_t *ohci_polledp);
static void ohci_polled_stop_processing(
ohci_polled_t *ohci_polledp);
/* Polled restore state routines */
static void ohci_polled_restore_state(ohci_polled_t *ohci_polledp);
static void ohci_polled_start_processing(
ohci_polled_t *ohci_polledp);
/* Polled read routines */
static ohci_td_t *ohci_polled_pickup_done_list(
ohci_polled_t *ohci_polledp,
ohci_td_t *done_head);
static int ohci_polled_check_done_list(
ohci_polled_t *ohci_polledp);
static void ohci_polled_create_input_list(
ohci_polled_t *ohci_polledp,
ohci_td_t *head_done_list);
static int ohci_polled_process_input_list(
ohci_polled_t *ohci_polledp);
static int ohci_polled_handle_normal_td(
ohci_polled_t *ohci_polledp,
ohci_td_t *td);
static void ohci_polled_insert_td(ohci_state_t *ohcip,
ohci_td_t *td);
static void ohci_polled_fill_in_td(ohci_state_t *ohcip,
ohci_td_t *td,
ohci_td_t *new_dummy,
uint_t hctd_ctrl,
uint32_t hctd_iommu_cbp,
size_t hctd_length,
ohci_trans_wrapper_t *tw);
static void ohci_polled_insert_td_on_tw(
ohci_state_t *ohcip,
ohci_trans_wrapper_t *tw,
ohci_td_t *td);
static void ohci_polled_handle_frame_number_overflow(
ohci_state_t *ohcip);
static void ohci_polled_finish_interrupt(
ohci_state_t *ohcip,
uint_t intr);
/*
* POLLED entry points
*
* These functions are entry points into the POLLED code.
*/
/*
* ohci_hcdi_polled_input_init:
*
* This is the initialization routine for handling the USB keyboard
* in POLLED mode. This routine is not called from POLLED mode, so
* it is OK to acquire mutexes.
*/
int
ohci_hcdi_polled_input_init(
usba_pipe_handle_data_t *ph,
uchar_t **polled_buf,
usb_console_info_impl_t *console_input_info)
{
ohci_polled_t *ohci_polledp;
ohci_state_t *ohcip;
int ret;
ohcip = ohci_obtain_state(ph->p_usba_device->usb_root_hub_dip);
/*
* Grab the ohci_int_mutex so that things don't change on us
* if an interrupt comes in.
*/
mutex_enter(&ohcip->ohci_int_mutex);
ret = ohci_polled_init(ph, ohcip, console_input_info);
if (ret != USB_SUCCESS) {
/* Allow interrupts to continue */
mutex_exit(&ohcip->ohci_int_mutex);
return (ret);
}
ohci_polledp = (ohci_polled_t *)console_input_info->uci_private;
/*
* Mark the structure so that if we are using it, we don't free
* the structures if one of them is unplugged.
*/
ohci_polledp->ohci_polled_flags |= POLLED_INPUT_MODE;
/* increase the polled kbd counter for keyboard connected */
ohcip->ohci_polled_kbd_count ++;
/*
* This is the buffer we will copy characters into. It will be
* copied into at this layer, so we need to keep track of it.
*/
ohci_polledp->ohci_polled_buf =
(uchar_t *)kmem_zalloc(POLLED_RAW_BUF_SIZE, KM_SLEEP);
*polled_buf = ohci_polledp->ohci_polled_buf;
/*
* This is a software workaround to fix schizo hardware bug.
* Existence of "no-prom-cdma-sync" property means consistent
* dma sync should not be done while in prom or polled mode.
*/
if (ddi_prop_exists(DDI_DEV_T_ANY, ohcip->ohci_dip,
DDI_PROP_NOTPROM, "no-prom-cdma-sync")) {
ohci_polledp->ohci_polled_no_sync_flag = B_TRUE;
}
/* Allow interrupts to continue */
mutex_exit(&ohcip->ohci_int_mutex);
return (USB_SUCCESS);
}
/*
* ohci_hcdi_polled_input_fini:
*/
int
ohci_hcdi_polled_input_fini(usb_console_info_impl_t *info)
{
ohci_polled_t *ohci_polledp;
ohci_state_t *ohcip;
int ret;
ohci_polledp = (ohci_polled_t *)info->uci_private;
ohcip = ohci_polledp->ohci_polled_ohcip;
mutex_enter(&ohcip->ohci_int_mutex);
/*
* Reset the POLLED_INPUT_MODE flag so that we can tell if
* this structure is in use in the ohci_polled_fini routine.
*/
ohci_polledp->ohci_polled_flags &= ~POLLED_INPUT_MODE;
/* Decrease the polled kbd counter for keyboard disconnected */
ohcip->ohci_polled_kbd_count --;
/* Free the buffer that we copied data into */
kmem_free(ohci_polledp->ohci_polled_buf, POLLED_RAW_BUF_SIZE);
ret = ohci_polled_fini(ohci_polledp);
mutex_exit(&ohcip->ohci_int_mutex);
return (ret);
}
/*
* ohci_hcdi_polled_input_enter:
*
* This is where we enter into POLLED mode. This routine sets up
* everything so that calls to ohci_hcdi_polled_read will return
* characters.
*/
int
ohci_hcdi_polled_input_enter(usb_console_info_impl_t *info)
{
ohci_polled_t *ohci_polledp;
ohci_polledp = (ohci_polled_t *)info->uci_private;
ohci_polledp->ohci_polled_entry++;
/*
* If the controller is already switched over, just return
*/
if (ohci_polledp->ohci_polled_entry > 1) {
return (USB_SUCCESS);
}
ohci_polled_save_state(ohci_polledp);
ohci_polledp->ohci_polled_flags |= POLLED_INPUT_MODE_INUSE;
return (USB_SUCCESS);
}
/*
* ohci_hcdi_polled_input_exit:
*
* This is where we exit POLLED mode. This routine restores
* everything that is needed to continue operation.
*/
int
ohci_hcdi_polled_input_exit(usb_console_info_impl_t *info)
{
ohci_polled_t *ohci_polledp;
ohci_polledp = (ohci_polled_t *)info->uci_private;
ohci_polledp->ohci_polled_entry--;
/*
* If there are still outstanding "enters", just return
*/
if (ohci_polledp->ohci_polled_entry > 0)
return (USB_SUCCESS);
ohci_polledp->ohci_polled_flags &= ~POLLED_INPUT_MODE_INUSE;
ohci_polled_restore_state(ohci_polledp);
return (USB_SUCCESS);
}
/*
* ohci_hcdi_polled_read:
*
* Get a key character
*/
int
ohci_hcdi_polled_read(
usb_console_info_impl_t *info,
uint_t *num_characters)
{
ohci_state_t *ohcip;
ohci_polled_t *ohci_polledp;
uint_t intr;
ohci_polledp = (ohci_polled_t *)info->uci_private;
ohcip = ohci_polledp->ohci_polled_ohcip;
#ifndef lint
_NOTE(NO_COMPETING_THREADS_NOW);
#endif
*num_characters = 0;
intr = (Get_OpReg(hcr_intr_status) & Get_OpReg(hcr_intr_enable));
/*
* Check whether any Frame Number Overflow interrupt is pending
* and if it is pending, process this interrupt.
*/
if (intr & HCR_INTR_FNO) {
ohci_handle_frame_number_overflow(ohcip);
/* Acknowledge the FNO interrupt */
ohci_polled_finish_interrupt(ohcip, HCR_INTR_FNO);
}
if (intr & HCR_INTR_WDH) {
/* Check to see if there are any TD's for this input device */
if (ohci_polled_check_done_list(ohci_polledp) == USB_SUCCESS) {
/* Process any TD's on the input done list */
*num_characters =
ohci_polled_process_input_list(ohci_polledp);
}
/*
* To make sure after we get the done list from DoneHead,
* every input device get his own TD's in the
* ohci_polled_done_list and then clear the interrupt status.
*/
if (ohcip->ohci_polled_done_list == NULL) {
/* Acknowledge the WDH interrupt */
ohci_polled_finish_interrupt(ohcip, HCR_INTR_WDH);
}
}
#ifndef lint
_NOTE(COMPETING_THREADS_NOW);
#endif
return (USB_SUCCESS);
}
/*
* Internal Functions
*/
/*
* Polled initialization routines
*/
/*
* ohci_polled_init:
*
* Initialize generic information Uthat is needed to provide USB/POLLED
* support.
*/
static int
ohci_polled_init(
usba_pipe_handle_data_t *ph,
ohci_state_t *ohcip,
usb_console_info_impl_t *console_info)
{
ohci_polled_t *ohci_polledp;
ohci_pipe_private_t *pp;
ASSERT(mutex_owned(&ohcip->ohci_int_mutex));
/*
* We have already initialized this structure. If the structure
* has already been initialized, then we don't need to redo it.
*/
if (console_info->uci_private) {
return (USB_SUCCESS);
}
/* Allocate and intitialize a state structure */
ohci_polledp = (ohci_polled_t *)
kmem_zalloc(sizeof (ohci_polled_t), KM_SLEEP);
console_info->uci_private = (usb_console_info_private_t)ohci_polledp;
/*
* Store away the ohcip so that we can get to it when we are in
* POLLED mode. We don't want to have to call ohci_obtain_state
* every time we want to access this structure. Also save ohci
* polled state information in ohcip.
*/
ohci_polledp->ohci_polled_ohcip = ohcip;
/*
* Save usb device and endpoint number information from the usb
* pipe handle.
*/
mutex_enter(&ph->p_mutex);
ohci_polledp->ohci_polled_usb_dev = ph->p_usba_device;
ohci_polledp->ohci_polled_ep_addr = ph->p_ep.bEndpointAddress;
mutex_exit(&ph->p_mutex);
/*
* Allocate memory to make duplicate of original usb pipe handle.
*/
ohci_polledp->ohci_polled_input_pipe_handle =
kmem_zalloc(sizeof (usba_pipe_handle_data_t), KM_SLEEP);
/*
* Copy the USB handle into the new pipe handle. Also
* create new lock for the new pipe handle.
*/
bcopy((void *)ph,
(void *)ohci_polledp->ohci_polled_input_pipe_handle,
sizeof (usba_pipe_handle_data_t));
/*
* uint64_t typecast to make sure amd64 can compile
*/
mutex_init(&ohci_polledp->ohci_polled_input_pipe_handle->p_mutex,
NULL, MUTEX_DRIVER, (void *)(uintptr_t)ohcip->ohci_intr_pri);
/* Create a new ohci pipe private structure */
pp = (ohci_pipe_private_t *)
kmem_zalloc(sizeof (ohci_pipe_private_t), KM_SLEEP);
/*
* Store the pointer in the pipe handle. This structure was also
* just allocated.
*/
mutex_enter(&ohci_polledp->ohci_polled_input_pipe_handle->p_mutex);
ohci_polledp->ohci_polled_input_pipe_handle->
p_hcd_private = (usb_opaque_t)pp;
mutex_exit(&ohci_polledp->ohci_polled_input_pipe_handle->p_mutex);
/*
* Store a pointer to the pipe handle. This structure was just
* allocated and it is not in use yet. The locking is there to
* satisfy warlock.
*/
mutex_enter(&ph->p_mutex);
bcopy(&ph->p_policy, &pp->pp_policy, sizeof (usb_pipe_policy_t));
mutex_exit(&ph->p_mutex);
pp->pp_pipe_handle = ohci_polledp->ohci_polled_input_pipe_handle;
/*
* Allocate a dummy for the interrupt table. This dummy will be
* put into the action when we switch interrupt tables during
* ohci_hcdi_polled_enter. Dummy is placed on the unused lattice
* entries. When the ED is allocated we will replace dummy ED by
* valid interrupt ED in one or more locations in the interrupt
* lattice depending on the requested polling interval. Also we
* will hang a dummy TD to the ED & dummy TD is used to indicate
* the end of the TD chain.
*/
ohci_polledp->ohci_polled_dummy_ed = ohci_alloc_hc_ed(ohcip, NULL);
if (ohci_polledp->ohci_polled_dummy_ed == NULL) {
return (USB_NO_RESOURCES);
}
/*
* Allocate the interrupt endpoint. This ED will be inserted in
* to the lattice chain for the keyboard device. This endpoint
* will have the TDs hanging off of it for the processing.
*/
ohci_polledp->ohci_polled_ed = ohci_alloc_hc_ed(ohcip,
ohci_polledp->ohci_polled_input_pipe_handle);
if (ohci_polledp->ohci_polled_ed == NULL) {
return (USB_NO_RESOURCES);
}
/* Set the state of pipe as idle */
pp->pp_state = OHCI_PIPE_STATE_IDLE;
/* Insert the endpoint onto the pipe handle */
pp->pp_ept = ohci_polledp->ohci_polled_ed;
/*
* Set soft interrupt handler flag in the normal mode usb
* pipe handle.
*/
mutex_enter(&ph->p_mutex);
ph->p_spec_flag |= USBA_PH_FLAG_USE_SOFT_INTR;
mutex_exit(&ph->p_mutex);
/*
* Insert a Interrupt polling request onto the endpoint.
*
* There will now be two TDs on the ED, one is the dummy TD that
* was allocated above in the ohci_alloc_hc_ed and this new one.
*/
if ((ohci_start_periodic_pipe_polling(ohcip,
ohci_polledp->ohci_polled_input_pipe_handle,
NULL, USB_FLAGS_SLEEP)) != USB_SUCCESS) {
return (USB_NO_RESOURCES);
}
return (USB_SUCCESS);
}
/*
* Polled deinitialization routines
*/
/*
* ohci_polled_fini:
*/
static int
ohci_polled_fini(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_pipe_private_t *pp;
ohci_td_t *curr_td, *next_td;
ohci_trans_wrapper_t *curr_tw, *next_tw;
ASSERT(mutex_owned(&ohcip->ohci_int_mutex));
/*
* If the structure is already in use, then don't free it.
*/
if (ohci_polledp->ohci_polled_flags & POLLED_INPUT_MODE) {
return (USB_SUCCESS);
}
pp = (ohci_pipe_private_t *)
ohci_polledp->ohci_polled_input_pipe_handle->p_hcd_private;
/*
* Deallocate all the pre-allocated interrupt requests
*/
ohci_handle_outstanding_requests(ohcip, pp);
/*
* Traverse the list of TD's on this endpoint and these TD's
* have outstanding transfer requests. Since list processing
* is stopped, these TDs can be deallocated.
*/
ohci_traverse_tds(ohcip, pp->pp_pipe_handle);
/*
* For each transfer wrapper on this pipe, free the TD and
* free the TW. We don't free the last TD in the chain
* because it will be freed by ohci_deallocate_ed. All TD's
* on this TW are also on the end point associated with this
* pipe.
*/
next_tw = pp->pp_tw_head;
while (next_tw) {
next_td = (ohci_td_t *)next_tw->tw_hctd_head;
/*
* Walk through each TD for this transfer
* wrapper and free that TD.
*/
while (next_td) {
curr_td = next_td;
next_td = ohci_td_iommu_to_cpu(ohcip,
Get_TD(next_td->hctd_tw_next_td));
ohci_deallocate_td(ohcip, curr_td);
}
curr_tw = next_tw;
next_tw = curr_tw->tw_next;
/* Free the transfer wrapper */
ohci_deallocate_tw_resources(ohcip, pp, curr_tw);
}
/*
* Deallocate the endpoint descriptors that we allocated
* with ohci_alloc_hc_ed.
*/
if (ohci_polledp->ohci_polled_dummy_ed) {
ohci_deallocate_ed(ohcip, ohci_polledp->ohci_polled_dummy_ed);
}
if (ohci_polledp->ohci_polled_ed) {
ohci_deallocate_ed(ohcip, ohci_polledp->ohci_polled_ed);
}
mutex_destroy(&ohci_polledp->ohci_polled_input_pipe_handle->p_mutex);
/*
* Destroy everything about the pipe that we allocated in
* ohci_polled_duplicate_pipe_handle
*/
kmem_free(pp, sizeof (ohci_pipe_private_t));
kmem_free(ohci_polledp->ohci_polled_input_pipe_handle,
sizeof (usba_pipe_handle_data_t));
/*
* We use this field to determine if a TD is for input or not,
* so NULL the pointer so we don't check deallocated data.
*/
ohci_polledp->ohci_polled_input_pipe_handle = NULL;
/*
* Finally, free off the structure that we use to keep track
* of all this.
*/
kmem_free(ohci_polledp, sizeof (ohci_polled_t));
return (USB_SUCCESS);
}
/*
* Polled save state routines
*/
/*
* ohci_polled_save_state:
*/
static void
ohci_polled_save_state(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip;
int i;
uint_t polled_toggle;
uint_t real_toggle;
ohci_pipe_private_t *pp = NULL; /* Normal mode Pipe */
ohci_pipe_private_t *polled_pp; /* Polled mode Pipe */
usba_pipe_handle_data_t *ph;
uint8_t ep_addr;
ohci_save_intr_sts_t *ohci_intr_sts;
ohci_regs_t *ohci_polled_regsp;
ohci_td_t *td, *prev_td;
ohci_td_t *done_head, **done_list;
#ifndef lint
_NOTE(NO_COMPETING_THREADS_NOW);
#endif
/*
* If either of these two flags are set, then we have already
* saved off the state information and setup the controller.
*/
if (ohci_polledp->ohci_polled_flags & POLLED_INPUT_MODE_INUSE) {
#ifndef lint
_NOTE(COMPETING_THREADS_NOW);
#endif
return;
}
ohcip = ohci_polledp->ohci_polled_ohcip;
/*
* Check if the number of keyboard reach the max number we can
* support in polled mode
*/
if (++ ohcip->ohci_polled_enter_count > MAX_NUM_FOR_KEYBOARD) {
#ifndef lint
_NOTE(COMPETING_THREADS_NOW);
#endif
return;
}
/* Get the endpoint addr. */
ep_addr = ohci_polledp->ohci_polled_ep_addr;
/* Get the normal mode usb pipe handle */
ph = usba_hcdi_get_ph_data(ohci_polledp->ohci_polled_usb_dev, ep_addr);
ohci_intr_sts = &ohcip->ohci_save_intr_sts;
ohci_polled_regsp = &ohcip->ohci_polled_save_regs;
/*
* Only the first enter keyboard entry disable the interrupt, save the
* information of normal mode, stop the processing, initialize the
* frame list table.
*/
if (ohcip->ohci_polled_enter_count == 1) {
/*
* Prevent the ohci interrupt handler from handling interrupt.
* We will turn off interrupts. This keeps us from generating
* a hardware interrupt.This is the useful for testing because
* in POLLED mode we can't get interrupts anyway. We can test
* this code by shutting off hardware interrupt generation and
* polling for the interrupts.
*/
Set_OpReg(hcr_intr_disable, HCR_INTR_MIE);
/*
* Save the current normal mode ohci registers and later this
* saved register copy is used to replace some of required ohci
* registers before switching from polled mode to normal mode.
*/
bzero((void *)ohci_polled_regsp, sizeof (ohci_regs_t));
ohci_polled_regsp->hcr_control = Get_OpReg(hcr_control);
ohci_polled_regsp->hcr_cmd_status = Get_OpReg(hcr_cmd_status);
ohci_polled_regsp->hcr_intr_enable = Get_OpReg(hcr_intr_enable);
ohci_polled_regsp->hcr_HCCA = Get_OpReg(hcr_HCCA);
ohci_polled_regsp->hcr_done_head = Get_OpReg(hcr_done_head);
ohci_polled_regsp->hcr_bulk_head = Get_OpReg(hcr_bulk_head);
ohci_polled_regsp->hcr_ctrl_head = Get_OpReg(hcr_ctrl_head);
/*
* The functionality & importance of critical code section in
* the normal mode ohci interrupt handler and 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 missed
* interrupt events & done head list fields information
* to the critical interrupt events & done list fields.
*
* - Reset the missed ohci interrupt events and done head
* list fields so that the new missed interrupt events
* and done head list information can be saved.
*
* - All above steps will be executed within the critical
* section of the interrupt handler. Then ohci missed
* interrupt handler will be called to service the ohci
* missed interrupts.
*
* (b) Polled mode:
*
* - On entering the polled code, checks for the critical
* section code execution within normal mode interrupt
* handler.
*
* - If critical section code is executing in the normal
* mode ohci interrupt handler & if copying of the ohci
* missed interrupt events and done head list fields to
* the critical fields is finished then, save the "any
* missed interrupt events and 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 and done
* list" because of this current polled mode switch in
* the actual missed interrupt events & done head list
* fields.
*/
/*
* Check and save the pending SOF interrupt condition for the
* ohci normal mode. This information will be saved either in
* the critical missed event fields or in actual missed event
* fields depending on the whether the critical code section's
* execution flag was set or not when switched to polled mode
* from normal mode.
*/
if ((ohci_intr_sts->ohci_intr_flag & OHCI_INTR_CRITICAL) &&
(ohci_intr_sts->ohci_critical_intr_sts != 0)) {
ohci_intr_sts->ohci_critical_intr_sts |=
((Get_OpReg(hcr_intr_status) &
Get_OpReg(hcr_intr_enable)) & HCR_INTR_SOF);
} else {
ohci_intr_sts->ohci_missed_intr_sts |=
((Get_OpReg(hcr_intr_status) &
Get_OpReg(hcr_intr_enable)) & HCR_INTR_SOF);
}
ohci_polled_stop_processing(ohci_polledp);
/* Process any missed Frame Number Overflow (FNO) interrupt */
ohci_polled_handle_frame_number_overflow(ohcip);
/*
* By this time all list processing has been stopped.Now check
* and save the information about the pending HCCA done list,
* done head ohci register and WDH bit in the interrupt status
* register. This information will be saved either in critical
* missed event fields or in actual missed event fields depend
* on the whether the critical code section's execution flag
* was set or not when switched to polled mode from the normal
* mode.
*/
/* Read and Save the HCCA DoneHead value */
done_head = (ohci_td_t *)(uintptr_t)(Get_HCCA(
ohcip->ohci_hccap->HccaDoneHead) & HCCA_DONE_HEAD_MASK);
if ((done_head) &&
(done_head != ohci_intr_sts->ohci_curr_done_lst)) {
if ((ohci_intr_sts->ohci_intr_flag &
OHCI_INTR_CRITICAL) &&
((ohci_intr_sts->ohci_critical_done_lst) ||
(ohci_intr_sts->ohci_missed_done_lst == NULL))) {
done_list =
&ohci_intr_sts->ohci_critical_done_lst;
ohci_intr_sts->ohci_critical_intr_sts |=
HCR_INTR_WDH;
} else {
done_list =
&ohci_intr_sts->ohci_missed_done_lst;
ohci_intr_sts->ohci_missed_intr_sts |=
HCR_INTR_WDH;
}
if (*done_list) {
td = (ohci_td_t *)
ohci_td_iommu_to_cpu(ohcip,
(uintptr_t)done_head);
while (td) {
prev_td = td;
td = ohci_td_iommu_to_cpu(ohcip,
Get_TD(td->hctd_next_td));
}
Set_TD(prev_td->hctd_next_td, *done_list);
*done_list = done_head;
} else {
*done_list = (ohci_td_t *)done_head;
}
}
/*
* Save the latest hcr_done_head ohci register value, so that
* this value can be replaced when exit from the POLLED mode.
*/
ohci_polled_regsp->hcr_done_head = Get_OpReg(hcr_done_head);
/*
* Reset the HCCA done head and ohci done head register.
*/
Set_HCCA(ohcip->ohci_hccap->HccaDoneHead, NULL);
Set_OpReg(hcr_done_head, (uint32_t)0x0);
/*
* Clear the WriteDoneHead interrupt bit in the ohci interrupt
* status register.
*/
Set_OpReg(hcr_intr_status, HCR_INTR_WDH);
/*
* Save the current interrupt lattice and replace this lattice
* with an lattice used in POLLED mode. We will restore lattice
* back when we exit from the POLLED mode.
*/
for (i = 0; i < NUM_INTR_ED_LISTS; i++) {
ohcip->ohci_polled_save_IntTble[i] =
(ohci_ed_t *)(uintptr_t)Get_HCCA(
ohcip->ohci_hccap->HccaIntTble[i]);
}
/*
* Fill in the lattice with dummy EDs. These EDs are used so the
* controller can tell that it is at the end of the ED list.
*/
for (i = 0; i < NUM_INTR_ED_LISTS; i++) {
Set_HCCA(ohcip->ohci_hccap->HccaIntTble[i],
ohci_ed_cpu_to_iommu(ohcip,
ohci_polledp->ohci_polled_dummy_ed));
}
}
/* Get the polled mode ohci pipe private structure */
polled_pp = (ohci_pipe_private_t *)
ohci_polledp->ohci_polled_input_pipe_handle->p_hcd_private;
/*
* Before replacing the lattice, adjust the data togggle on the
* on the ohci's interrupt ed
*/
polled_toggle = (Get_ED(polled_pp->pp_ept->hced_headp) &
HC_EPT_Carry) ? DATA1:DATA0;
/*
* If normal mode interrupt pipe endpoint is active, get the data
* toggle from the this interrupt endpoint through the corresponding
* interrupt pipe handle. Else get the data toggle information from
* the usb device structure and this information is saved during the
* normal mode interrupt pipe close. Use this data toggle information
* to fix the data toggle of polled mode interrupt endpoint.
*/
if (ph) {
/* Get the normal mode ohci pipe private structure */
pp = (ohci_pipe_private_t *)ph->p_hcd_private;
real_toggle = (Get_ED(pp->pp_ept->hced_headp) &
HC_EPT_Carry) ? DATA1:DATA0;
} else {
real_toggle = usba_hcdi_get_data_toggle(
ohci_polledp->ohci_polled_usb_dev, ep_addr);
}
if (polled_toggle != real_toggle) {
if (real_toggle == DATA0) {
Set_ED(polled_pp->pp_ept->hced_headp,
Get_ED(polled_pp->pp_ept->hced_headp) &
~HC_EPT_Carry);
} else {
Set_ED(polled_pp->pp_ept->hced_headp,
Get_ED(polled_pp->pp_ept->hced_headp) |
HC_EPT_Carry);
}
}
/*
* Check whether Halt bit is set in the ED and if so clear the
* halt bit.
*/
if (polled_pp->pp_ept->hced_headp & HC_EPT_Halt) {
/* Clear the halt bit */
Set_ED(polled_pp->pp_ept->hced_headp,
(Get_ED(polled_pp->pp_ept->hced_headp) & ~HC_EPT_Halt));
}
/*
* Now, add the endpoint to the lattice that we will hang our
* TD's off of. We need to poll this device at every 8 ms and
* hence add this ED needs 4 entries in interrupt lattice.
*/
for (i = (ohcip->ohci_polled_enter_count -1); i < NUM_INTR_ED_LISTS;
i = i + MIN_LOW_SPEED_POLL_INTERVAL) {
Set_HCCA(ohcip->ohci_hccap->HccaIntTble[i],
ohci_ed_cpu_to_iommu(ohcip,
ohci_polledp->ohci_polled_ed));
}
/*
* Only the first enter keyboard entry clear the contents of
* periodic ED register and enable the WDH interrupt and
* start process the periodic list.
*/
if (ohcip->ohci_polled_enter_count == 1) {
/*
* Clear the contents of current ohci periodic ED register that
* is physical address of current Isochronous or Interrupt ED.
*/
Set_OpReg(hcr_periodic_curr, (uint32_t)0x0);
/* Make sure WriteDoneHead interrupt is enabled */
Set_OpReg(hcr_intr_enable, HCR_INTR_WDH);
/*
* Enable the periodic list. We will now start processing EDs &
* TDs again.
*/
Set_OpReg(hcr_control,
(Get_OpReg(hcr_control) | HCR_CONTROL_PLE));
}
#ifndef lint
_NOTE(COMPETING_THREADS_NOW);
#endif
}
/*
* ohci_polled_stop_processing:
*/
static void
ohci_polled_stop_processing(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip;
uint_t count;
ohci_regs_t *ohci_polled_regsp;
ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_polled_regsp = &ohcip->ohci_polled_save_regs;
/*
* Turn off all list processing. This will take place starting
* at the next frame.
*/
Set_OpReg(hcr_control,
(ohci_polled_regsp->hcr_control) & ~(HCR_CONTROL_CLE|
HCR_CONTROL_PLE| HCR_CONTROL_BLE|HCR_CONTROL_IE));
/*
* Make sure that the SOF interrupt bit is cleared in the ohci
* interrupt status register.
*/
Set_OpReg(hcr_intr_status, HCR_INTR_SOF);
/* Enable SOF interrupt */
Set_OpReg(hcr_intr_enable, HCR_INTR_SOF);
/*
* According to OHCI Specification, we have to wait for eight
* start of frames to make sure that the Host Controller writes
* contents of done head register to done head filed of HCCA.
*/
for (count = 0; count <= DONE_QUEUE_INTR_COUNTER; count++) {
while (!((Get_OpReg(hcr_intr_status)) & HCR_INTR_SOF)) {
continue;
}
/* Acknowledge the SOF interrupt */
ohci_polled_finish_interrupt(ohcip, HCR_INTR_SOF);
}
Set_OpReg(hcr_intr_disable, HCR_INTR_SOF);
}
/*
* Polled restore state routines
*/
/*
* ohci_polled_restore_state:
*/
static void
ohci_polled_restore_state(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip;
int i;
uint_t polled_toggle;
uint_t real_toggle;
ohci_pipe_private_t *pp = NULL; /* Normal mode Pipe */
ohci_pipe_private_t *polled_pp; /* Polled mode Pipe */
ohci_td_t *td;
ohci_td_t *next_td; /* TD pointers */
uint_t count;
ohci_save_intr_sts_t *ohci_intr_sts;
ohci_regs_t *ohci_polled_regsp;
uint32_t mask;
usba_pipe_handle_data_t *ph;
uint8_t ep_addr;
#ifndef lint
_NOTE(NO_COMPETING_THREADS_NOW);
#endif
/*
* If this flag is set, then we are still using this structure,
* so don't restore any controller state information yet.
*/
if (ohci_polledp->ohci_polled_flags & POLLED_INPUT_MODE_INUSE) {
#ifndef lint
_NOTE(COMPETING_THREADS_NOW);
#endif
return;
}
ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_intr_sts = &ohcip->ohci_save_intr_sts;
ohci_polled_regsp = &ohcip->ohci_polled_save_regs;
ohcip->ohci_polled_enter_count --;
/* Get the endpoint addr. */
ep_addr = ohci_polledp->ohci_polled_ep_addr;
/* Get the normal mode usb pipe handle */
ph = usba_hcdi_get_ph_data(ohci_polledp->ohci_polled_usb_dev, ep_addr);
/*
* Only the first leave keyboard entry turn off all list processing.
* This will take place starting at the next frame.
*/
if (Get_OpReg(hcr_control) & HCR_CONTROL_PLE) {
Set_OpReg(hcr_control,
(Get_OpReg(hcr_control) & ~HCR_CONTROL_PLE));
}
/*
* Only the last leave keyboard entry restore the info for
* normal mode.
*/
if (ohcip->ohci_polled_enter_count == 0) {
Set_OpReg(hcr_intr_enable, HCR_INTR_SOF);
/*
* According to OHCI Specification, we have to wait for eight
* start of frames to make sure that the Host Controller writes
* contents of done head register to done head filed of HCCA.
*/
for (count = 0; count <= DONE_QUEUE_INTR_COUNTER; count++) {
while (!((Get_OpReg(hcr_intr_status)) & HCR_INTR_SOF)) {
continue;
}
/* Acknowledge the SOF interrupt */
ohci_polled_finish_interrupt(ohcip, HCR_INTR_SOF);
}
/*
* Check any Frame Number Overflow interrupt (FNO) is pending.
*/
ohci_polled_handle_frame_number_overflow(ohcip);
/*
* Before switching back, we have to process last TD in the
* POLLED mode. It may be in the hcr_done_head register or
* in done list or in the lattice. If it is either on the
* hcr_done_head register or in the done list, just re-inserted
* into the ED's TD list.
*
* First look up at the TD's that are in the hcr_done_head
* register and re-insert them back into the ED's TD list.
*/
td = ohci_td_iommu_to_cpu(ohcip,
(uintptr_t)Get_OpReg(hcr_done_head));
while (td) {
next_td = ohci_td_iommu_to_cpu(ohcip, Get_TD(td->hctd_next_td));
/*
* Insert valid interrupt TD back into ED's
* TD list. No periodic TD's will be processed
* since all processing has been stopped.
*/
ohci_polled_insert_td(ohcip, td);
td = next_td;
}
/*
* Now look up at the TD's that are in the HCCA done head list &
* re-insert them back into the ED's TD list.
*/
td = ohci_td_iommu_to_cpu(ohcip, (Get_HCCA(
ohcip->ohci_hccap->HccaDoneHead) & HCCA_DONE_HEAD_MASK));
while (td) {
next_td = ohci_td_iommu_to_cpu(ohcip,
Get_TD(td->hctd_next_td));
/*
* Insert valid interrupt TD back into ED's
* TD list. No periodic TD's will be processed
* since all processing has been stopped.
*/
ohci_polled_insert_td(ohcip, td);
td = next_td;
}
/* Reset the HCCA done head list to NULL */
Set_HCCA(ohcip->ohci_hccap->HccaDoneHead, NULL);
/*
* Replace the hcr_done_head register field with the saved copy
* of current normal mode hcr_done_head register contents.
*/
Set_OpReg(hcr_done_head,
(uint32_t)ohci_polled_regsp->hcr_done_head);
/*
* Clear the WriteDoneHead and SOF interrupt bits in the ohci
* interrupt status register.
*/
Set_OpReg(hcr_intr_status, (HCR_INTR_WDH | HCR_INTR_SOF));
}
/* Get the polled mode ohci pipe private structure */
polled_pp = (ohci_pipe_private_t *)
ohci_polledp->ohci_polled_input_pipe_handle->p_hcd_private;
/*
* Before replacing the lattice, adjust the data togggle
* on the on the ohci's interrupt ed
*/
polled_toggle = (Get_ED(polled_pp->pp_ept->hced_headp) &
HC_EPT_Carry) ? DATA1:DATA0;
/*
* If normal mode interrupt pipe endpoint is active, fix the
* data toggle for this interrupt endpoint by getting the data
* toggle information from the polled interrupt endpoint. Else
* save the data toggle information in usb device structure.
*/
if (ph) {
/* Get the normal mode ohci pipe private structure */
pp = (ohci_pipe_private_t *)ph->p_hcd_private;
real_toggle = (Get_ED(pp->pp_ept->hced_headp) &
HC_EPT_Carry) ? DATA1:DATA0;
if (polled_toggle != real_toggle) {
if (polled_toggle == DATA0) {
Set_ED(pp->pp_ept->hced_headp,
Get_ED(pp->pp_ept->hced_headp) &
~HC_EPT_Carry);
} else {
Set_ED(pp->pp_ept->hced_headp,
Get_ED(pp->pp_ept->hced_headp) |
HC_EPT_Carry);
}
}
} else {
usba_hcdi_set_data_toggle(ohci_polledp->ohci_polled_usb_dev,
ep_addr, polled_toggle);
}
/*
* Only the last leave keyboard entry restore the Interrupt table,
* start processing and enable the interrupt.
*/
if (ohcip->ohci_polled_enter_count == 0) {
/* Replace the lattice */
for (i = 0; i < NUM_INTR_ED_LISTS; i++) {
Set_HCCA(ohcip->ohci_hccap->HccaIntTble[i],
(uintptr_t)ohcip->ohci_polled_save_IntTble[i]);
}
/*
* Clear the contents of current ohci periodic ED register that
* is physical address of current Isochronous or Interrupt ED.
*/
Set_OpReg(hcr_periodic_curr, (uint32_t)0x0);
ohci_polled_start_processing(ohci_polledp);
/*
* Check and enable required ohci interrupts before switching
* back to normal mode from the POLLED mode.
*/
mask = (uint32_t)ohci_polled_regsp->hcr_intr_enable &
(HCR_INTR_SOF | HCR_INTR_WDH);
if (ohci_intr_sts->ohci_intr_flag & OHCI_INTR_HANDLING) {
Set_OpReg(hcr_intr_enable, mask);
} else {
Set_OpReg(hcr_intr_enable, mask | HCR_INTR_MIE);
}
}
#ifndef lint
_NOTE(COMPETING_THREADS_NOW);
#endif
}
/*
* ohci_polled_start_processing:
*/
static void
ohci_polled_start_processing(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip;
uint32_t control;
uint32_t mask;
ohci_regs_t *ohci_polled_regsp;
ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_polled_regsp = &ohcip->ohci_polled_save_regs;
mask = ((uint32_t)ohci_polled_regsp->hcr_control) & (HCR_CONTROL_CLE |
HCR_CONTROL_PLE | HCR_CONTROL_BLE | HCR_CONTROL_IE);
control = Get_OpReg(hcr_control) & ~(HCR_CONTROL_CLE |
HCR_CONTROL_PLE | HCR_CONTROL_BLE | HCR_CONTROL_IE);
Set_OpReg(hcr_control, (control | mask));
}
/*
* Polled read routines
*/
/*
* ohci_polled_check_done_list:
*
* Check to see it there are any TD's on the done head. If there are
* then reverse the done list and put the TD's on the appropriated list.
*/
static int
ohci_polled_check_done_list(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_td_t *done_head, *done_list;
/* Sync HCCA area */
if (ohci_polledp->ohci_polled_no_sync_flag == B_FALSE) {
Sync_HCCA(ohcip);
}
/* Read and Save the HCCA DoneHead value */
done_head = (ohci_td_t *)(uintptr_t)
(Get_HCCA(ohcip->ohci_hccap->HccaDoneHead) & HCCA_DONE_HEAD_MASK);
/*
* Look at the Done Head and if it is NULL and ohci done list is NULL,
* just return; else if ohci done list is not NULL, should check it.
*/
if (done_head == NULL) {
if (ohcip->ohci_polled_done_list) {
done_head = ohcip->ohci_polled_done_list;
} else {
return (USB_FAILURE);
}
} else {
/* Reset the done head to NULL */
Set_HCCA(ohcip->ohci_hccap->HccaDoneHead, NULL);
ohcip->ohci_polled_done_list = NULL;
}
/* Sync ED and TD pool */
if (ohci_polledp->ohci_polled_no_sync_flag == B_FALSE) {
Sync_ED_TD_Pool(ohcip);
}
/* Pickup own tds in the done head */
done_list = ohci_polled_pickup_done_list(ohci_polledp, done_head);
/*
* Look at the own done list which is pickup'ed
* and if it is NULL, just return.
*/
if (done_list == NULL) {
return (USB_FAILURE);
}
/* Create the input done list */
ohci_polled_create_input_list(ohci_polledp, done_list);
return (USB_SUCCESS);
}
/*
* ohci_polled_pickup_done_list:
*
* Pickup the TDs of own in the Done Head List
*/
static ohci_td_t *
ohci_polled_pickup_done_list(
ohci_polled_t *ohci_polledp,
ohci_td_t *done_head)
{
ohci_state_t *ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_td_t *reserve_head = NULL, *reserve_tail = NULL;
ohci_td_t *create_head = NULL, *current_td, *td;
ohci_trans_wrapper_t *tw;
ohci_pipe_private_t *pp;
/*
* Current_td pointers point to the done head.
*/
current_td = (ohci_td_t *)
ohci_td_iommu_to_cpu(ohcip, (uintptr_t)done_head);
while (current_td) {
td = (ohci_td_t *)ohci_td_iommu_to_cpu(ohcip,
Get_TD(current_td->hctd_next_td));
Set_TD(current_td->hctd_next_td, NULL);
/* Obtain the transfer wrapper from the TD */
tw = (ohci_trans_wrapper_t *)OHCI_LOOKUP_ID(
(uint32_t)Get_TD(current_td->hctd_trans_wrapper));
/* Get the pipe handle for this transfer wrapper. */
pp = tw->tw_pipe_private;
/*
* Figure out which done list to put this TD on and put it
* there. If the pipe handle of the TD matches the pipe
* handle we are using for the input device, then this must
* be an input TD, reverse the order and link to the list for
* this input device. Else put the TD to the reserve done list
* for other input devices.
*/
if (pp->pp_pipe_handle ==
ohci_polledp->ohci_polled_input_pipe_handle) {
if (create_head == NULL) {
create_head = current_td;
} else {
Set_TD(current_td->hctd_next_td,
ohci_td_cpu_to_iommu(ohcip, create_head));
create_head = current_td;
}
} else {
if (reserve_head == NULL) {
reserve_head = reserve_tail = current_td;
} else {
Set_TD(reserve_tail->hctd_next_td,
ohci_td_cpu_to_iommu(ohcip, current_td));
reserve_tail = current_td;
}
}
current_td = td;
}
/* Check if there is other TDs left for other input devices */
if (reserve_head) {
ohcip->ohci_polled_done_list = (ohci_td_t *)(uintptr_t)
ohci_td_cpu_to_iommu(ohcip, reserve_head);
} else {
ohcip->ohci_polled_done_list = NULL;
}
return (create_head);
}
/*
* ohci_polled_create_input_list:
*
* Create the input done list from the actual done head list.
*/
static void
ohci_polled_create_input_list(
ohci_polled_t *ohci_polledp,
ohci_td_t *head_done_list)
{
ohci_state_t *ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_td_t *cpu_save, *td;
ASSERT(head_done_list != NULL);
/* Get the done head list */
td = (ohci_td_t *)head_done_list;
/*
* Traverse the done list and create the input done list.
*/
while (td) {
/*
* Convert the iommu pointer to a cpu pointer. No point
* in doing this over and over, might as well do it once.
*/
cpu_save = ohci_td_iommu_to_cpu(ohcip,
Get_TD(td->hctd_next_td));
/*
* Terminate this TD by setting its next pointer to NULL.
*/
Set_TD(td->hctd_next_td, NULL);
/* This is an input TD, so put it on the input done list */
if (ohci_polledp->ohci_polled_input_done_head == NULL) {
/*
* There is nothing on the input done list,
* so put this TD on the head.
*/
ohci_polledp->ohci_polled_input_done_head = td;
} else {
Set_TD(ohci_polledp->
ohci_polled_input_done_tail->hctd_next_td,
ohci_td_cpu_to_iommu(ohcip, td));
}
/* The tail points to the new TD */
ohci_polledp->ohci_polled_input_done_tail = td;
td = cpu_save;
}
}
/*
* ohci_polled_process_input_list:
*
* This routine takes the TD's off of the input done head and processes
* them. It returns the number of characters that have been copied for
* input.
*/
static int
ohci_polled_process_input_list(ohci_polled_t *ohci_polledp)
{
ohci_state_t *ohcip = ohci_polledp->ohci_polled_ohcip;
ohci_td_t *td, *next_td;
uint_t ctrl;
uint_t num_characters;
ohci_trans_wrapper_t *tw;
ohci_pipe_private_t *pp;
/*
* Get the first TD on the input done head.
*/
td = ohci_polledp->ohci_polled_input_done_head;
ohci_polledp->ohci_polled_input_done_head = NULL;
num_characters = 0;
/*
* Traverse the list of transfer descriptors. We can't destroy
* hctd_next_td pointers of these TDs because we are using it
* to traverse the done list. Therefore, we can not put these
* TDs back on the ED until we are done processing all of them.
*/
while (td) {
/* Get the next TD from the input done list */
next_td = (ohci_td_t *)
ohci_td_iommu_to_cpu(ohcip, Get_TD(td->hctd_next_td));
/* Look at the status */
ctrl = (uint_t)Get_TD(td->hctd_ctrl) & (uint32_t)HC_TD_CC;
/*
* Check to see if there is an error. If there is error
* clear the halt condition in the Endpoint Descriptor
* (ED) associated with this Transfer Descriptor (TD).
*/
if (ctrl != HC_TD_CC_NO_E) {
/* Obtain the transfer wrapper from the TD */
tw = (ohci_trans_wrapper_t *)OHCI_LOOKUP_ID(
(uint32_t)Get_TD(td->hctd_trans_wrapper));
/* Get the pipe handle for this transfer wrapper */
pp = tw->tw_pipe_private;
/* Clear the halt bit */
Set_ED(pp->pp_ept->hced_headp,
(Get_ED(pp->pp_ept->hced_headp) & ~HC_EPT_Halt));
} else {
num_characters +=
ohci_polled_handle_normal_td(ohci_polledp, td);
}
/* Insert this interrupt TD back onto the ED's TD list */
ohci_polled_insert_td(ohcip, td);
td = next_td;
}
return (num_characters);
}
/*
* ohci_polled_handle_normal_td:
*/
static int
ohci_polled_handle_normal_td(
ohci_polled_t *ohci_polledp,
ohci_td_t *td)
{
ohci_state_t *ohcip = ohci_polledp->ohci_polled_ohcip;
uchar_t *buf;
ohci_trans_wrapper_t *tw;
int length;
/* Obtain the transfer wrapper from the TD */
tw = (ohci_trans_wrapper_t *)OHCI_LOOKUP_ID((uint32_t)
Get_TD(td->hctd_trans_wrapper));
ASSERT(tw != NULL);
buf = (uchar_t *)tw->tw_buf;
length = tw->tw_length;
/*
* 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.
*/
if (Get_TD(td->hctd_cbp)) {
length = Get_TD(td->hctd_cbp) -
tw->tw_cookie.dmac_address;
}
/* Sync IO buffer */
if (ohci_polledp->ohci_polled_no_sync_flag == B_FALSE) {
Sync_IO_Buffer(tw->tw_dmahandle, length);
}
/* Copy the data into the message */
ddi_rep_get8(tw->tw_accesshandle,
(uint8_t *)ohci_polledp->ohci_polled_buf,
(uint8_t *)buf, length, DDI_DEV_AUTOINCR);
return (length);
}
/*
* ohci_polled_insert_td:
*
* Insert a Transfer Descriptor (TD) on an Endpoint Descriptor (ED).
*/
static void
ohci_polled_insert_td(
ohci_state_t *ohcip,
ohci_td_t *td)
{
ohci_pipe_private_t *pp;
ohci_ed_t *ept;
uint_t td_control;
ohci_trans_wrapper_t *tw;
ohci_td_t *cpu_current_dummy;
usb_intr_req_t *intr_req;
/* Obtain the transfer wrapper from the TD */
tw = (ohci_trans_wrapper_t *)OHCI_LOOKUP_ID(
(uint32_t)Get_TD(td->hctd_trans_wrapper));
/*
* Take this TD off the transfer wrapper's list since
* the pipe is FIFO, this must be the first TD on the
* list.
*/
ASSERT((ohci_td_t *)tw->tw_hctd_head == td);
tw->tw_hctd_head = (ohci_td_t *)
ohci_td_iommu_to_cpu(ohcip, Get_TD(td->hctd_tw_next_td));
/*
* If the head becomes NULL, then there are no more
* active TD's for this transfer wrapper. Also set
* the tail to NULL.
*/
if (tw->tw_hctd_head == NULL) {
tw->tw_hctd_tail = NULL;
}
/* Convert current valid TD as new dummy TD */
bzero((char *)td, sizeof (ohci_td_t));
Set_TD(td->hctd_state, HC_TD_DUMMY);
pp = tw->tw_pipe_private;
/* Obtain the endpoint and interrupt request */
ept = pp->pp_ept;
intr_req = (usb_intr_req_t *)tw->tw_curr_xfer_reqp;
if (intr_req->intr_attributes & USB_ATTRS_SHORT_XFER_OK) {
td_control = HC_TD_IN|HC_TD_1I|HC_TD_R;
} else {
td_control = HC_TD_IN|HC_TD_1I;
}
/* Get the current dummy */
cpu_current_dummy = (ohci_td_t *)
(ohci_td_iommu_to_cpu(ohcip, Get_ED(ept->hced_tailp)));
/*
* Fill in the current dummy td and
* add the new dummy to the end.
*/
ohci_polled_fill_in_td(ohcip, cpu_current_dummy, td,
td_control, tw->tw_cookie.dmac_address, tw->tw_length, tw);
/* Insert this td onto the tw */
ohci_polled_insert_td_on_tw(ohcip, tw, cpu_current_dummy);
/*
* Add the new dummy to the ED's list. When this occurs,
* the Host Controller will see the newly filled in dummy
* TD.
*/
Set_ED(ept->hced_tailp, (ohci_td_cpu_to_iommu(ohcip, td)));
}
/*
* ohci_polled_fill_in_td:
*
* Fill in the fields of a Transfer Descriptor (TD).
*/
static void
ohci_polled_fill_in_td(
ohci_state_t *ohcip,
ohci_td_t *td,
ohci_td_t *new_dummy,
uint_t hctd_ctrl,
uint32_t hctd_iommu_cbp,
size_t hctd_length,
ohci_trans_wrapper_t *tw)
{
/* Assert that the td to be filled in is a dummy */
ASSERT(Get_TD(td->hctd_state) == HC_TD_DUMMY);
/* Clear the TD */
bzero((char *)td, sizeof (ohci_td_t));
/* Update the dummy with control information */
Set_TD(td->hctd_ctrl, (hctd_ctrl | HC_TD_CC_NA));
/* Update the beginning of the buffer */
Set_TD(td->hctd_cbp, hctd_iommu_cbp);
/* The current dummy now points to the new dummy */
Set_TD(td->hctd_next_td, (ohci_td_cpu_to_iommu(ohcip, new_dummy)));
/* Fill in the end of the buffer */
if (hctd_length == 0) {
ASSERT(Get_TD(td->hctd_cbp) == 0);
ASSERT(hctd_iommu_cbp == 0);
Set_TD(td->hctd_buf_end, 0);
} else {
Set_TD(td->hctd_buf_end,
hctd_iommu_cbp + hctd_length - 1);
}
/* Fill in the wrapper portion of the TD */
Set_TD(td->hctd_trans_wrapper, (uint32_t)tw->tw_id);
Set_TD(td->hctd_tw_next_td, NULL);
}
/*
* ohci_polled_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
ohci_polled_insert_td_on_tw(
ohci_state_t *ohcip,
ohci_trans_wrapper_t *tw,
ohci_td_t *td)
{
/*
* Set the next pointer to NULL because
* this is the last TD on list.
*/
Set_TD(td->hctd_tw_next_td, NULL);
if (tw->tw_hctd_head == NULL) {
ASSERT(tw->tw_hctd_tail == NULL);
tw->tw_hctd_head = td;
tw->tw_hctd_tail = td;
} else {
ohci_td_t *dummy = (ohci_td_t *)tw->tw_hctd_tail;
ASSERT(dummy != NULL);
ASSERT(dummy != td);
ASSERT(Get_TD(td->hctd_state) == HC_TD_DUMMY);
/* Add the td to the end of the list */
Set_TD(dummy->hctd_tw_next_td, ohci_td_cpu_to_iommu(ohcip, td));
tw->tw_hctd_tail = td;
ASSERT(Get_TD(td->hctd_tw_next_td) == NULL);
}
}
/*
* ohci_polled_handle_frame_number_overflow:
*
* Process Frame Number Overflow (FNO) interrupt in polled mode.
*/
static void
ohci_polled_handle_frame_number_overflow(ohci_state_t *ohcip)
{
uint_t intr;
/* Read the Interrupt Status & Interrupt enable register */
intr = (Get_OpReg(hcr_intr_status) & Get_OpReg(hcr_intr_enable));
/*
* Check whether any Frame Number Overflow interrupt is pending
* and if it is pending, process this interrupt.
*/
if (intr & HCR_INTR_FNO) {
ohci_handle_frame_number_overflow(ohcip);
/* Acknowledge the FNO interrupt */
ohci_polled_finish_interrupt(ohcip, HCR_INTR_FNO);
}
}
/*
* ohci_polled_finish_interrupt:
*/
static void
ohci_polled_finish_interrupt(
ohci_state_t *ohcip,
uint_t intr)
{
/* Acknowledge the interrupt */
Set_OpReg(hcr_intr_status, intr);
/*
* 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);
}