ohci_polled.c revision a195726fa33097e56cf1c25c31feddb827e140f0
/*
* 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
* 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.
*/
/*
* Internal Function Prototypes
*/
/* Polled initialization routines */
static int ohci_polled_init(
/* Polled deinitialization routines */
/* Polled save state routines */
static void ohci_polled_stop_processing(
/* Polled restore state routines */
static void ohci_polled_start_processing(
/* Polled read routines */
static ohci_td_t *ohci_polled_pickup_done_list(
static int ohci_polled_check_done_list(
static void ohci_polled_create_input_list(
static int ohci_polled_process_input_list(
static int ohci_polled_handle_normal_td(
static void ohci_polled_insert_td_on_tw(
static void ohci_polled_handle_frame_number_overflow(
static void ohci_polled_finish_interrupt(
/*
* 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
{
int ret;
/*
* Grab the ohci_int_mutex so that things don't change on us
* if an interrupt comes in.
*/
if (ret != USB_SUCCESS) {
/* Allow interrupts to continue */
return (ret);
}
/*
* Mark the structure so that if we are using it, we don't free
* the structures if one of them is unplugged.
*/
/* increase the polled kbd counter for keyboard connected */
/*
* 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.
*/
/*
* 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.
*/
DDI_PROP_NOTPROM, "no-prom-cdma-sync")) {
}
/* Allow interrupts to continue */
return (USB_SUCCESS);
}
/*
* ohci_hcdi_polled_input_fini:
*/
int
{
int ret;
/*
* Reset the POLLED_INPUT_MODE flag so that we can tell if
* this structure is in use in the ohci_polled_fini routine.
*/
/* Decrease the polled kbd counter for keyboard disconnected */
/* Free the buffer that we copied data into */
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
{
/*
* If the controller is already switched over, just return
*/
return (USB_SUCCESS);
}
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
{
/*
* If there are still outstanding "enters", just return
*/
if (ohci_polledp->ohci_polled_entry > 0)
return (USB_SUCCESS);
return (USB_SUCCESS);
}
/*
* ohci_hcdi_polled_read:
*
* Get a key character
*/
int
{
#ifndef lint
#endif
*num_characters = 0;
/*
* Check whether any Frame Number Overflow interrupt is pending
* and if it is pending, process this interrupt.
*/
if (intr & HCR_INTR_FNO) {
/* Acknowledge the FNO interrupt */
}
if (intr & HCR_INTR_WDH) {
/* Check to see if there are any TD's for this input device */
/* Process any TD's on the input done list */
}
/*
* 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.
*/
/* Acknowledge the WDH interrupt */
}
}
#ifndef lint
#endif
return (USB_SUCCESS);
}
/*
* Internal Functions
*/
/*
* Polled initialization routines
*/
/*
* ohci_polled_init:
*
* support.
*/
static int
{
/*
* 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 *)
/*
* 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.
*/
/*
* Save usb device and endpoint number information from the usb
* pipe handle.
*/
/*
* Allocate memory to make duplicate of original usb pipe handle.
*/
/*
* Copy the USB handle into the new pipe handle. Also
* create new lock for the new pipe handle.
*/
(void *)ohci_polledp->ohci_polled_input_pipe_handle,
sizeof (usba_pipe_handle_data_t));
/*
* uint64_t typecast to make sure amd64 can compile
*/
/* Create a new ohci pipe private structure */
pp = (ohci_pipe_private_t *)
/*
* Store the pointer in the pipe handle. This structure was also
* just allocated.
*/
/*
* 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.
*/
/*
* 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.
*/
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.
*/
return (USB_NO_RESOURCES);
}
/* Set the state of pipe as idle */
/* Insert the endpoint onto the pipe handle */
/*
* Set soft interrupt handler flag in the normal mode usb
* pipe handle.
*/
/*
* 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.
*/
return (USB_NO_RESOURCES);
}
return (USB_SUCCESS);
}
/*
* Polled deinitialization routines
*/
/*
* ohci_polled_fini:
*/
static int
{
/*
* If the structure is already in use, then don't free it.
*/
return (USB_SUCCESS);
}
pp = (ohci_pipe_private_t *)
/*
* Deallocate all the pre-allocated interrupt requests
*/
/*
* 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.
*/
/*
* 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.
*/
while (next_tw) {
/*
* Walk through each TD for this transfer
* wrapper and free that TD.
*/
while (next_td) {
}
/* Free the transfer wrapper */
}
/*
* Deallocate the endpoint descriptors that we allocated
* with ohci_alloc_hc_ed.
*/
if (ohci_polledp->ohci_polled_dummy_ed) {
}
if (ohci_polledp->ohci_polled_ed) {
}
/*
* Destroy everything about the pipe that we allocated in
* ohci_polled_duplicate_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.
*/
/*
* Finally, free off the structure that we use to keep track
* of all this.
*/
return (USB_SUCCESS);
}
/*
* Polled save state routines
*/
/*
* ohci_polled_save_state:
*/
static void
{
int i;
#ifndef lint
#endif
/*
* If either of these two flags are set, then we have already
* saved off the state information and setup the controller.
*/
#ifndef lint
#endif
return;
}
/*
* Check if the number of keyboard reach the max number we can
* support in polled mode
*/
#ifndef lint
#endif
return;
}
/* Get the endpoint addr. */
/* Get the normal mode usb pipe handle */
/*
* Only the first enter keyboard entry disable the interrupt, save the
* information of normal mode, stop the processing, initialize the
* frame list table.
*/
/*
* 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.
*/
/*
* 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.
*/
/*
* 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.
*/
(ohci_intr_sts->ohci_critical_intr_sts != 0)) {
((Get_OpReg(hcr_intr_status) &
} else {
((Get_OpReg(hcr_intr_status) &
}
/* Process any missed Frame Number Overflow (FNO) interrupt */
/*
* 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 */
if ((done_head) &&
if ((ohci_intr_sts->ohci_intr_flag &
} else {
}
if (*done_list) {
while (td) {
}
} else {
}
}
/*
* Save the latest hcr_done_head ohci register value, so that
* this value can be replaced when exit from the POLLED mode.
*/
/*
* Reset the HCCA done head and ohci done head register.
*/
/*
* Clear the WriteDoneHead interrupt bit in the ohci interrupt
* status register.
*/
/*
* 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++) {
}
/*
* 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++) {
}
}
/* Get the polled mode ohci pipe private structure */
polled_pp = (ohci_pipe_private_t *)
/*
* Before replacing the lattice, adjust the data togggle on the
* on the ohci's interrupt ed
*/
/*
* 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 */
} else {
}
if (polled_toggle != real_toggle) {
if (real_toggle == DATA0) {
~HC_EPT_Carry);
} else {
}
}
/*
* Check whether Halt bit is set in the ED and if so clear the
* halt bit.
*/
/* Clear the halt bit */
}
/*
* 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.
*/
i = i + MIN_LOW_SPEED_POLL_INTERVAL) {
}
/*
* Only the first enter keyboard entry clear the contents of
* periodic ED register and enable the WDH interrupt and
* start process the periodic list.
*/
/*
* Clear the contents of current ohci periodic ED register that
* is physical address of current Isochronous or Interrupt ED.
*/
/* Make sure WriteDoneHead interrupt is enabled */
/*
* Enable the periodic list. We will now start processing EDs &
* TDs again.
*/
}
#ifndef lint
#endif
}
/*
* ohci_polled_stop_processing:
*/
static void
{
/*
* Turn off all list processing. This will take place starting
* at the next frame.
*/
/*
* Make sure that the SOF interrupt bit is cleared in the ohci
* interrupt status register.
*/
/* Enable SOF interrupt */
/*
* 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.
*/
continue;
}
/* Acknowledge the SOF interrupt */
}
}
/*
* Polled restore state routines
*/
/*
* ohci_polled_restore_state:
*/
static void
{
int i;
#ifndef lint
#endif
/*
* If this flag is set, then we are still using this structure,
* so don't restore any controller state information yet.
*/
#ifndef lint
#endif
return;
}
/* Get the endpoint addr. */
/* Get the normal mode usb pipe handle */
/*
* Only the first leave keyboard entry turn off all list processing.
* This will take place starting at the next frame.
*/
}
/*
* Only the last leave keyboard entry restore the info for
* normal mode.
*/
if (ohcip->ohci_polled_enter_count == 0) {
/*
* 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.
*/
continue;
}
/* Acknowledge the SOF interrupt */
}
/*
* Check any Frame Number Overflow interrupt (FNO) is pending.
*/
/*
* 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.
*/
while (td) {
/*
* Insert valid interrupt TD back into ED's
* TD list. No periodic TD's will be processed
* since all processing has been stopped.
*/
}
/*
* 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.
*/
while (td) {
/*
* Insert valid interrupt TD back into ED's
* TD list. No periodic TD's will be processed
* since all processing has been stopped.
*/
}
/* Reset the HCCA done head list to NULL */
/*
* Replace the hcr_done_head register field with the saved copy
* of current normal mode hcr_done_head register contents.
*/
/*
* Clear the WriteDoneHead and SOF interrupt bits in the ohci
* interrupt status register.
*/
}
/* Get the polled mode ohci pipe private structure */
polled_pp = (ohci_pipe_private_t *)
/*
* Before replacing the lattice, adjust the data togggle
* on the on the ohci's interrupt ed
*/
/*
* 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 */
if (polled_toggle != real_toggle) {
if (polled_toggle == DATA0) {
~HC_EPT_Carry);
} else {
}
}
} else {
}
/*
* 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++) {
}
/*
* Clear the contents of current ohci periodic ED register that
* is physical address of current Isochronous or Interrupt ED.
*/
/*
* Check and enable required ohci interrupts before switching
* back to normal mode from the POLLED mode.
*/
(HCR_INTR_SOF | HCR_INTR_WDH);
} else {
}
}
#ifndef lint
#endif
}
/*
* ohci_polled_start_processing:
*/
static void
{
}
/*
* 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
{
/* Sync HCCA area */
}
/* Read and Save the HCCA DoneHead value */
/*
* 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 (ohcip->ohci_polled_done_list) {
} else {
return (USB_FAILURE);
}
} else {
/* Reset the done head to NULL */
}
/* Sync ED and TD pool */
}
/* Pickup own tds in the done head */
/*
* Look at the own done list which is pickup'ed
* and if it is NULL, just return.
*/
return (USB_FAILURE);
}
/* Create the input done list */
return (USB_SUCCESS);
}
/*
* ohci_polled_pickup_done_list:
*
* Pickup the TDs of own in the Done Head List
*/
static ohci_td_t *
{
/*
* Current_td pointers point to the done head.
*/
current_td = (ohci_td_t *)
while (current_td) {
/* Obtain the transfer wrapper from the TD */
/* Get the pipe handle for this transfer wrapper. */
/*
* 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 ==
if (create_head == NULL) {
} else {
}
} else {
if (reserve_head == NULL) {
} else {
}
}
current_td = td;
}
/* Check if there is other TDs left for other input devices */
if (reserve_head) {
} else {
}
return (create_head);
}
/*
* ohci_polled_create_input_list:
*
* Create the input done list from the actual done head list.
*/
static void
{
/* Get the done head 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.
*/
/*
* Terminate this TD by setting its next pointer to NULL.
*/
/* This is an input TD, so put it on the input done list */
/*
* There is nothing on the input done list,
* so put this TD on the head.
*/
} else {
}
/* The tail points to the new TD */
}
}
/*
* 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
{
/*
* Get the first TD on the input done head.
*/
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 */
/* Look at the status */
/*
* 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 */
/* Get the pipe handle for this transfer wrapper */
/* Clear the halt bit */
} else {
}
/* Insert this interrupt TD back onto the ED's TD list */
}
return (num_characters);
}
/*
* ohci_polled_handle_normal_td:
*/
static int
{
int length;
/* Obtain the transfer wrapper from the TD */
/*
* 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.
*/
}
/* Sync IO buffer */
}
/* Copy the data into the message */
return (length);
}
/*
* ohci_polled_insert_td:
*
* Insert a Transfer Descriptor (TD) on an Endpoint Descriptor (ED).
*/
static void
{
/* Obtain the transfer wrapper from the TD */
/*
* Take this TD off the transfer wrapper's list since
* the pipe is FIFO, this must be the first TD on the
* list.
*/
/*
* If the head becomes NULL, then there are no more
* active TD's for this transfer wrapper. Also set
* the tail to NULL.
*/
}
/* Convert current valid TD as new dummy TD */
/* Obtain the endpoint and interrupt request */
} else {
}
/* Get the current dummy */
cpu_current_dummy = (ohci_td_t *)
/*
* Fill in the current dummy td and
* add the new dummy to the end.
*/
/* Insert this td onto the tw */
/*
* Add the new dummy to the ED's list. When this occurs,
* the Host Controller will see the newly filled in dummy
* TD.
*/
}
/*
* ohci_polled_fill_in_td:
*
* Fill in the fields of a Transfer Descriptor (TD).
*/
static void
{
/* Assert that the td to be filled in is a dummy */
/* Clear the TD */
/* Update the dummy with control information */
/* Update the beginning of the buffer */
/* The current dummy now points to the new dummy */
/* Fill in the end of the buffer */
if (hctd_length == 0) {
ASSERT(hctd_iommu_cbp == 0);
} else {
}
/* Fill in the wrapper portion of the TD */
}
/*
* 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
{
/*
* 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_polled_handle_frame_number_overflow:
*
* Process Frame Number Overflow (FNO) interrupt in polled mode.
*/
static void
{
/* Read the Interrupt Status & Interrupt enable register */
/*
* Check whether any Frame Number Overflow interrupt is pending
* and if it is pending, process this interrupt.
*/
if (intr & HCR_INTR_FNO) {
/* Acknowledge the FNO interrupt */
}
}
/*
* ohci_polled_finish_interrupt:
*/
static void
{
/* Acknowledge the interrupt */
/*
* 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);
}