/* * 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 * 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 2006 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * EHCI Host Controller Driver (EHCI) * * The EHCI driver is a software driver which interfaces to the Universal * Serial Bus layer (USBA) and the Host Controller (HC). The interface to * the Host Controller is defined by the EHCI Host Controller Interface. * * This module contains the specific EHCI code used in POLLED mode. This * code is in a separate file since it will never become part of the EHCI * driver. */ #include #include #include #include #include #include /* * Internal Function Prototypes */ /* Polled initialization routines */ static int ehci_polled_init( usba_pipe_handle_data_t *ph, ehci_state_t *ehcip, usb_console_info_impl_t *console_input_info); /* Polled deinitialization routines */ static int ehci_polled_fini(ehci_polled_t *ehci_polledp); /* Polled save state routines */ static void ehci_polled_save_state(ehci_polled_t *ehci_polledp); /* Polled restore state routines */ static void ehci_polled_restore_state(ehci_polled_t *ehci_polledp); static void ehci_polled_stop_processing( ehci_polled_t *ehci_polledp); static void ehci_polled_start_processing( ehci_polled_t *ehci_polledp); /* Polled read routines */ static int ehci_polled_process_active_intr_qtd_list( ehci_polled_t *ehci_polledp); static int ehci_polled_handle_normal_qtd( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd); static void ehci_polled_insert_qtd( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd); static void ehci_polled_fill_in_qtd( ehci_state_t *ehcip, ehci_qtd_t *qtd, uint_t qtd_ctrl, size_t qtd_dma_offs, size_t qtd_length, ehci_trans_wrapper_t *tw); static void ehci_polled_insert_qtd_on_tw( ehci_state_t *ehcip, ehci_trans_wrapper_t *tw, ehci_qtd_t *qtd); static ehci_qtd_t *ehci_polled_create_done_qtd_list( ehci_polled_t *ehci_polledp); static void ehci_polled_insert_qtd_into_active_intr_qtd_list( ehci_polled_t *ehci_polledp, ehci_qtd_t *curr_qtd); static void ehci_polled_remove_qtd_from_active_intr_qtd_list( ehci_polled_t *ehci_polledp, ehci_qtd_t *curr_qtd); static void ehci_polled_traverse_qtds( ehci_polled_t *ehci_polledp, usba_pipe_handle_data_t *ph); static void ehci_polled_finish_interrupt( ehci_state_t *ehcip, uint_t intr); /* * POLLED entry points * * These functions are entry points into the POLLED code. */ /* * ehci_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 ehci_hcdi_polled_input_init( usba_pipe_handle_data_t *ph, uchar_t **polled_buf, usb_console_info_impl_t *console_input_info) { ehci_polled_t *ehci_polledp; ehci_state_t *ehcip; int ret; ehcip = ehci_obtain_state(ph->p_usba_device->usb_root_hub_dip); /* * Grab the ehci_int_mutex so that things don't change on us * if an interrupt comes in. */ mutex_enter(&ehcip->ehci_int_mutex); ret = ehci_polled_init(ph, ehcip, console_input_info); if (ret != USB_SUCCESS) { /* Allow interrupts to continue */ mutex_exit(&ehcip->ehci_int_mutex); return (ret); } ehci_polledp = (ehci_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. */ ehci_polledp->ehci_polled_flags |= POLLED_INPUT_MODE; /* increase the counter for keyboard connected */ ehcip->ehci_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. */ ehci_polledp->ehci_polled_buf = (uchar_t *)kmem_zalloc(POLLED_RAW_BUF_SIZE, KM_SLEEP); *polled_buf = ehci_polledp->ehci_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, ehcip->ehci_dip, DDI_PROP_NOTPROM, "no-prom-cdma-sync")) { ehci_polledp->ehci_polled_no_sync_flag = B_TRUE; } /* Allow interrupts to continue */ mutex_exit(&ehcip->ehci_int_mutex); return (USB_SUCCESS); } /* * ehci_hcdi_polled_input_fini: */ int ehci_hcdi_polled_input_fini(usb_console_info_impl_t *info) { ehci_polled_t *ehci_polledp; ehci_state_t *ehcip; int ret; ehci_polledp = (ehci_polled_t *)info->uci_private; ehcip = ehci_polledp->ehci_polled_ehcip; mutex_enter(&ehcip->ehci_int_mutex); /* * Reset the POLLED_INPUT_MODE flag so that we can tell if * this structure is in use in the ehci_polled_fini routine. */ ehci_polledp->ehci_polled_flags &= ~POLLED_INPUT_MODE; /* decrease the counter for keyboard disconnected */ ehcip->ehci_polled_kbd_count --; /* Free the buffer that we copied data into */ kmem_free(ehci_polledp->ehci_polled_buf, POLLED_RAW_BUF_SIZE); ret = ehci_polled_fini(ehci_polledp); mutex_exit(&ehcip->ehci_int_mutex); return (ret); } /* * ehci_hcdi_polled_input_enter: * * This is where we enter into POLLED mode. This routine sets up * everything so that calls to ehci_hcdi_polled_read will return * characters. */ int ehci_hcdi_polled_input_enter(usb_console_info_impl_t *info) { ehci_polled_t *ehci_polledp; ehci_polledp = (ehci_polled_t *)info->uci_private; ehci_polledp->ehci_polled_entry++; /* * If the controller is already switched over, just return */ if (ehci_polledp->ehci_polled_entry > 1) { return (USB_SUCCESS); } ehci_polled_save_state(ehci_polledp); ehci_polledp->ehci_polled_flags |= POLLED_INPUT_MODE_INUSE; return (USB_SUCCESS); } /* * ehci_hcdi_polled_input_exit: * * This is where we exit POLLED mode. This routine restores * everything that is needed to continue operation. */ int ehci_hcdi_polled_input_exit(usb_console_info_impl_t *info) { ehci_polled_t *ehci_polledp; ehci_polledp = (ehci_polled_t *)info->uci_private; ehci_polledp->ehci_polled_entry--; /* * If there are still outstanding "enters", just return */ if (ehci_polledp->ehci_polled_entry > 0) { return (USB_SUCCESS); } ehci_polledp->ehci_polled_flags &= ~POLLED_INPUT_MODE_INUSE; ehci_polled_restore_state(ehci_polledp); return (USB_SUCCESS); } /* * ehci_hcdi_polled_read: * * Get a key character */ int ehci_hcdi_polled_read( usb_console_info_impl_t *info, uint_t *num_characters) { ehci_state_t *ehcip; ehci_polled_t *ehci_polledp; uint_t intr; ehci_polledp = (ehci_polled_t *)info->uci_private; ehcip = ehci_polledp->ehci_polled_ehcip; #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif *num_characters = 0; intr = ((Get_OpReg(ehci_status) & Get_OpReg(ehci_interrupt)) & (EHCI_INTR_FRAME_LIST_ROLLOVER | EHCI_INTR_USB | EHCI_INTR_USB_ERROR)); /* * Check whether any frame list rollover interrupt is pending * and if it is pending, process this interrupt. */ if (intr & EHCI_INTR_FRAME_LIST_ROLLOVER) { /* Check any frame list rollover interrupt is pending */ ehci_handle_frame_list_rollover(ehcip); ehci_polled_finish_interrupt(ehcip, EHCI_INTR_FRAME_LIST_ROLLOVER); } /* Check for any USB transaction completion notification */ if (intr & (EHCI_INTR_USB | EHCI_INTR_USB_ERROR)) { ehcip->ehci_polled_read_count ++; /* Process any QTD's on the active interrupt qtd list */ *num_characters = ehci_polled_process_active_intr_qtd_list(ehci_polledp); if (ehcip->ehci_polled_read_count == ehcip->ehci_polled_enter_count) { /* Acknowledge the frame list rollover interrupt */ ehci_polled_finish_interrupt(ehcip, intr & (EHCI_INTR_USB | EHCI_INTR_USB_ERROR)); ehcip->ehci_polled_read_count = 0; } } #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return (USB_SUCCESS); } /* * Internal Functions */ /* * Polled initialization routines */ /* * ehci_polled_init: * * Initialize generic information Uthat is needed to provide USB/POLLED * support. */ static int ehci_polled_init( usba_pipe_handle_data_t *ph, ehci_state_t *ehcip, usb_console_info_impl_t *console_info) { ehci_polled_t *ehci_polledp; ehci_pipe_private_t *pp; ehci_qtd_t *qtd; ASSERT(mutex_owned(&ehcip->ehci_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 */ ehci_polledp = (ehci_polled_t *) kmem_zalloc(sizeof (ehci_polled_t), KM_SLEEP); console_info->uci_private = (usb_console_info_private_t)ehci_polledp; /* * Store away the ehcip so that we can get to it when we are in * POLLED mode. We don't want to have to call ehci_obtain_state * every time we want to access this structure. */ ehci_polledp->ehci_polled_ehcip = ehcip; /* * Save usb device and endpoint number information from the usb * pipe handle. */ mutex_enter(&ph->p_mutex); ehci_polledp->ehci_polled_usb_dev = ph->p_usba_device; ehci_polledp->ehci_polled_ep_addr = ph->p_ep.bEndpointAddress; mutex_exit(&ph->p_mutex); /* * Allocate memory to make duplicate of original usb pipe handle. */ ehci_polledp->ehci_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 *)ehci_polledp->ehci_polled_input_pipe_handle, sizeof (usba_pipe_handle_data_t)); /* * uint64_t typecast to make sure amd64 can compile */ mutex_init(&ehci_polledp->ehci_polled_input_pipe_handle->p_mutex, NULL, MUTEX_DRIVER, DDI_INTR_PRI(ehcip->ehci_intr_pri)); /* * Create a new ehci pipe private structure */ pp = (ehci_pipe_private_t *) kmem_zalloc(sizeof (ehci_pipe_private_t), KM_SLEEP); /* * Store the pointer in the pipe handle. This structure was also * just allocated. */ mutex_enter(&ehci_polledp->ehci_polled_input_pipe_handle->p_mutex); ehci_polledp->ehci_polled_input_pipe_handle-> p_hcd_private = (usb_opaque_t)pp; mutex_exit(&ehci_polledp->ehci_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 = ehci_polledp->ehci_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 * ehci_hcdi_polled_enter. Dummy is placed on the unused lattice * entries. When the QH is allocated we will replace dummy QH by * valid interrupt QH in one or more locations in the interrupt * lattice depending on the requested polling interval. Also we * will hang a dummy QTD to the QH & dummy QTD is used to indicate * the end of the QTD chain. */ ehci_polledp->ehci_polled_dummy_qh = ehci_alloc_qh(ehcip, NULL, EHCI_POLLED_MODE_FLAG); if (ehci_polledp->ehci_polled_dummy_qh == NULL) { return (USB_NO_RESOURCES); } /* * Allocate the interrupt endpoint. This QH will be inserted in * to the lattice chain for the keyboard device. This endpoint * will have the QTDs hanging off of it for the processing. */ ehci_polledp->ehci_polled_qh = ehci_alloc_qh( ehcip, ph, EHCI_POLLED_MODE_FLAG); if (ehci_polledp->ehci_polled_qh == NULL) { return (USB_NO_RESOURCES); } /* Set the state of pipe as idle */ pp->pp_state = EHCI_PIPE_STATE_IDLE; /* Set polled mode flag */ pp->pp_flag = EHCI_POLLED_MODE_FLAG; /* Insert the endpoint onto the pipe handle */ pp->pp_qh = ehci_polledp->ehci_polled_qh; /* * 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 QTDs on the QH, one is the dummy QTD that * was allocated above in the ehci_alloc_qh and this new one. */ if ((ehci_start_periodic_pipe_polling(ehcip, ehci_polledp->ehci_polled_input_pipe_handle, NULL, USB_FLAGS_SLEEP)) != USB_SUCCESS) { return (USB_NO_RESOURCES); } /* Get the given new interrupt qtd */ qtd = (ehci_qtd_t *)(ehci_qtd_iommu_to_cpu(ehcip, (Get_QH(pp->pp_qh->qh_next_qtd) & EHCI_QH_NEXT_QTD_PTR))); /* Insert this qtd into active interrupt QTD list */ ehci_polled_insert_qtd_into_active_intr_qtd_list(ehci_polledp, qtd); return (USB_SUCCESS); } /* * Polled deinitialization routines */ /* * ehci_polled_fini: */ static int ehci_polled_fini(ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_pipe_private_t *pp; ASSERT(mutex_owned(&ehcip->ehci_int_mutex)); /* If the structure is already in use, then don't free it */ if (ehci_polledp->ehci_polled_flags & POLLED_INPUT_MODE) { return (USB_SUCCESS); } pp = (ehci_pipe_private_t *) ehci_polledp->ehci_polled_input_pipe_handle->p_hcd_private; /* Deallocate all the pre-allocated interrupt requests */ ehci_handle_outstanding_requests(ehcip, pp); /* * Traverse the list of QTD's on this endpoint and these QTD's * have outstanding transfer requests. Since list processing * is stopped, these QTDs can be deallocated. */ ehci_polled_traverse_qtds(ehci_polledp, pp->pp_pipe_handle); /* Free DMA resources */ ehci_free_dma_resources(ehcip, pp->pp_pipe_handle); /* * Deallocate the endpoint descriptors that we allocated * with ehci_alloc_qh. */ if (ehci_polledp->ehci_polled_dummy_qh) { ehci_deallocate_qh(ehcip, ehci_polledp->ehci_polled_dummy_qh); } if (ehci_polledp->ehci_polled_qh) { ehci_deallocate_qh(ehcip, ehci_polledp->ehci_polled_qh); } mutex_destroy(&ehci_polledp->ehci_polled_input_pipe_handle->p_mutex); /* * Destroy everything about the pipe that we allocated in * ehci_polled_duplicate_pipe_handle */ kmem_free(pp, sizeof (ehci_pipe_private_t)); kmem_free(ehci_polledp->ehci_polled_input_pipe_handle, sizeof (usba_pipe_handle_data_t)); /* * We use this field to determine if a QTD is for input or not, * so NULL the pointer so we don't check deallocated data. */ ehci_polledp->ehci_polled_input_pipe_handle = NULL; /* * Finally, free off the structure that we use to keep track * of all this. */ kmem_free(ehci_polledp, sizeof (ehci_polled_t)); return (USB_SUCCESS); } /* * Polled save state routines */ /* * ehci_polled_save_state: */ static void ehci_polled_save_state(ehci_polled_t *ehci_polledp) { int i; ehci_state_t *ehcip; uint_t polled_toggle; uint_t real_toggle; ehci_pipe_private_t *pp = NULL; /* Normal mode Pipe */ ehci_pipe_private_t *polled_pp; /* Polled mode Pipe */ usba_pipe_handle_data_t *ph; uint8_t ep_addr; ehci_regs_t *ehci_polled_regsp; ehci_qh_t *qh; #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 (ehci_polledp->ehci_polled_flags & POLLED_INPUT_MODE_INUSE) { #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return; } ehcip = ehci_polledp->ehci_polled_ehcip; /* * Check if the number of keyboard reach the max number we can * support in polled mode */ if (++ ehcip->ehci_polled_enter_count > MAX_NUM_FOR_KEYBOARD) { #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return; } ehci_polled_regsp = &ehcip->ehci_polled_save_regs; /* Get the endpoint addr. */ ep_addr = ehci_polledp->ehci_polled_ep_addr; /* Get the normal mode usb pipe handle */ ph = usba_hcdi_get_ph_data(ehci_polledp->ehci_polled_usb_dev, ep_addr); /* * The first enter keyboard entry should save info of the normal mode, * disable all list processing and interrupt, initialize the * frame list table with dummy QHs. */ if (ehcip->ehci_polled_enter_count == 1) { /* * Save the current normal mode ehci registers and later this * saved register copy is used to replace some of required ehci * registers before switching from polled mode to normal mode. */ bzero((void *)ehci_polled_regsp, sizeof (ehci_regs_t)); /* Save current ehci registers */ ehci_polled_regsp->ehci_command = Get_OpReg(ehci_command); ehci_polled_regsp->ehci_interrupt = Get_OpReg(ehci_interrupt); ehci_polled_regsp->ehci_ctrl_segment = Get_OpReg(ehci_ctrl_segment); ehci_polled_regsp-> ehci_async_list_addr = Get_OpReg(ehci_async_list_addr); ehci_polled_regsp->ehci_config_flag = Get_OpReg(ehci_config_flag); ehci_polled_regsp->ehci_periodic_list_base = Get_OpReg(ehci_periodic_list_base); /* Disable all list processing and interrupts */ Set_OpReg(ehci_command, Get_OpReg(ehci_command) & ~(EHCI_CMD_ASYNC_SCHED_ENABLE | EHCI_CMD_PERIODIC_SCHED_ENABLE)); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); /* Save any unprocessed normal mode ehci interrupts */ ehcip->ehci_missed_intr_sts = EHCI_INTR_USB; /* * 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 < EHCI_NUM_PERIODIC_FRAME_LISTS; i++) { ehcip->ehci_polled_frame_list_table[i] = (ehci_qh_t *)(uintptr_t)Get_PFLT(ehcip-> ehci_periodic_frame_list_tablep-> ehci_periodic_frame_list_table[i]); } /* * Fill in the lattice with dummy QHs. These QHs are used so the * controller can tell that it is at the end of the QH list. */ for (i = 0; i < EHCI_NUM_PERIODIC_FRAME_LISTS; i++) { Set_PFLT(ehcip->ehci_periodic_frame_list_tablep-> ehci_periodic_frame_list_table[i], ehci_qh_cpu_to_iommu(ehcip, ehci_polledp->ehci_polled_dummy_qh) | (EHCI_QH_LINK_REF_QH | EHCI_QH_LINK_PTR_VALID)); } } /* Get the polled mode ehci pipe private structure */ polled_pp = (ehci_pipe_private_t *) ehci_polledp->ehci_polled_input_pipe_handle->p_hcd_private; /* * Before replacing the lattice, adjust the data togggle on the * on the ehci's interrupt ed */ polled_toggle = (Get_QH(polled_pp->pp_qh->qh_status) & EHCI_QH_STS_DATA_TOGGLE) ? 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 ehci pipe private structure */ pp = (ehci_pipe_private_t *)ph->p_hcd_private; real_toggle = (Get_QH(pp->pp_qh->qh_status) & EHCI_QH_STS_DATA_TOGGLE) ? DATA1:DATA0; } else { real_toggle = usba_hcdi_get_data_toggle( ehci_polledp->ehci_polled_usb_dev, ep_addr); } if (polled_toggle != real_toggle) { if (real_toggle == DATA0) { Set_QH(polled_pp->pp_qh->qh_status, Get_QH(polled_pp->pp_qh->qh_status) & ~EHCI_QH_STS_DATA_TOGGLE); } else { Set_QH(polled_pp->pp_qh->qh_status, Get_QH(polled_pp->pp_qh->qh_status) | EHCI_QH_STS_DATA_TOGGLE); } } /* * Check whether Halt bit is set in the QH and if so clear the * halt bit. */ if (polled_pp->pp_qh->qh_status & EHCI_QH_STS_HALTED) { /* Clear the halt bit */ Set_QH(polled_pp->pp_qh->qh_status, (Get_QH(polled_pp->pp_qh->qh_status) & ~EHCI_QH_STS_HALTED)); } /* * Initialize the qh overlay area */ qh = ehci_polledp->ehci_polled_qh; for (i = 0; i < 5; i++) { Set_QH(qh->qh_buf[i], NULL); Set_QH(qh->qh_buf_high[i], NULL); } Set_QH(qh->qh_next_qtd, ehci_qtd_cpu_to_iommu(ehcip, ehci_polledp->ehci_polled_active_intr_qtd_list)); /* * Now, add the endpoint to the lattice that we will hang our * QTD's off of. We need to poll this device at every 8 ms and * hence add this QH needs 4 entries in interrupt lattice. */ for (i = ehcip->ehci_polled_enter_count - 1; i < EHCI_NUM_PERIODIC_FRAME_LISTS; i = i + LS_MIN_POLL_INTERVAL) { Set_PFLT(ehcip->ehci_periodic_frame_list_tablep-> ehci_periodic_frame_list_table[i], ehci_qh_cpu_to_iommu(ehcip, ehci_polledp->ehci_polled_qh) | EHCI_QH_LINK_REF_QH); } /* The first enter keyboard entry enable interrupts and periodic list */ if (ehcip->ehci_polled_enter_count == 1) { /* Enable USB and Frame list rollover interrupts */ Set_OpReg(ehci_interrupt, (EHCI_INTR_USB | EHCI_INTR_USB_ERROR | EHCI_INTR_FRAME_LIST_ROLLOVER)); /* Enable the periodic list */ Set_OpReg(ehci_command, (Get_OpReg(ehci_command) | EHCI_CMD_PERIODIC_SCHED_ENABLE)); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); } #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif } /* * Polled restore state routines */ /* * ehci_polled_restore_state: */ static void ehci_polled_restore_state(ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip; int i; uint_t polled_toggle; uint_t real_toggle; ehci_pipe_private_t *pp = NULL; /* Normal mode Pipe */ ehci_pipe_private_t *polled_pp; /* Polled mode Pipe */ 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 (ehci_polledp->ehci_polled_flags & POLLED_INPUT_MODE_INUSE) { #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return; } ehcip = ehci_polledp->ehci_polled_ehcip; ehcip->ehci_polled_enter_count --; /* Get the endpoint addr */ ep_addr = ehci_polledp->ehci_polled_ep_addr; /* Get the normal mode usb pipe handle */ ph = usba_hcdi_get_ph_data(ehci_polledp->ehci_polled_usb_dev, ep_addr); /* Disable list processing and other things */ ehci_polled_stop_processing(ehci_polledp); /* Get the polled mode ehci pipe private structure */ polled_pp = (ehci_pipe_private_t *) ehci_polledp->ehci_polled_input_pipe_handle->p_hcd_private; /* * Before replacing the lattice, adjust the data togggle * on the on the ehci's interrupt ed */ polled_toggle = (Get_QH(polled_pp->pp_qh->qh_status) & EHCI_QH_STS_DATA_TOGGLE) ? 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 ehci pipe private structure */ pp = (ehci_pipe_private_t *)ph->p_hcd_private; real_toggle = (Get_QH(pp->pp_qh->qh_status) & EHCI_QH_STS_DATA_TOGGLE) ? DATA1:DATA0; if (polled_toggle != real_toggle) { if (polled_toggle == DATA0) { Set_QH(pp->pp_qh->qh_status, Get_QH(pp->pp_qh->qh_status) & ~EHCI_QH_STS_DATA_TOGGLE); } else { Set_QH(pp->pp_qh->qh_status, Get_QH(pp->pp_qh->qh_status) | EHCI_QH_STS_DATA_TOGGLE); } } } else { usba_hcdi_set_data_toggle(ehci_polledp->ehci_polled_usb_dev, ep_addr, polled_toggle); } /* * Only the last leave keyboard entry restore the save frame * list table and start processing. */ if (ehcip->ehci_polled_enter_count == 0) { /* Replace the lattice */ for (i = 0; i < EHCI_NUM_PERIODIC_FRAME_LISTS; i++) { Set_PFLT(ehcip->ehci_periodic_frame_list_tablep-> ehci_periodic_frame_list_table[i], ehcip->ehci_polled_frame_list_table[i]); } ehci_polled_start_processing(ehci_polledp); } #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif } /* * ehci_polled_stop_processing: */ static void ehci_polled_stop_processing(ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip; ehci_qh_t *qh = ehci_polledp->ehci_polled_qh; ehcip = ehci_polledp->ehci_polled_ehcip; /* First inactive this QH */ Set_QH(qh->qh_ctrl, Get_QH(qh->qh_ctrl) | EHCI_QH_CTRL_ED_INACTIVATE); /* Only first leave keyboard entry turn off periodic list processing */ if (Get_OpReg(ehci_command) & EHCI_CMD_PERIODIC_SCHED_ENABLE) { Set_OpReg(ehci_command, (Get_OpReg(ehci_command) & ~EHCI_CMD_PERIODIC_SCHED_ENABLE)); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); } /* * Now clear all required fields of QH * including inactive bit. */ Set_QH(qh->qh_ctrl, Get_QH(qh->qh_ctrl) & ~(EHCI_QH_CTRL_ED_INACTIVATE)); Set_QH(qh->qh_status, Get_QH(qh->qh_status) & ~(EHCI_QH_STS_XACT_STATUS)); Set_QH(qh->qh_curr_qtd, NULL); Set_QH(qh->qh_alt_next_qtd, EHCI_QH_ALT_NEXT_QTD_PTR_VALID); /* * Now look up at the QTD's that are in the active qtd list & * re-insert them back into the QH's QTD list. */ (void) ehci_polled_process_active_intr_qtd_list(ehci_polledp); } /* * ehci_polled_start_processing: */ static void ehci_polled_start_processing(ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip; uint32_t mask; ehci_regs_t *ehci_polled_regsp; ehcip = ehci_polledp->ehci_polled_ehcip; ehci_polled_regsp = &ehcip->ehci_polled_save_regs; mask = ((uint32_t)ehci_polled_regsp->ehci_interrupt & (EHCI_INTR_HOST_SYSTEM_ERROR | EHCI_INTR_FRAME_LIST_ROLLOVER | EHCI_INTR_USB_ERROR | EHCI_INTR_USB | EHCI_INTR_ASYNC_ADVANCE)); /* Enable all required EHCI interrupts */ Set_OpReg(ehci_interrupt, mask); mask = ((uint32_t)ehci_polled_regsp->ehci_command & (EHCI_CMD_ASYNC_SCHED_ENABLE | EHCI_CMD_PERIODIC_SCHED_ENABLE)); /* Enable all reuired list processing */ Set_OpReg(ehci_command, (Get_OpReg(ehci_command) | mask)); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); } /* * Polled read routines */ /* * ehci_polled_process_active_intr_qtd_list: * * This routine takes the QTD's off of the input done head and processes * them. It returns the number of characters that have been copied for * input. */ static int ehci_polled_process_active_intr_qtd_list(ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_qtd_t *qtd, *next_qtd; uint_t num_characters = 0; uint_t ctrl; ehci_trans_wrapper_t *tw; ehci_pipe_private_t *pp; usb_cr_t error; /* Sync QH and QTD pool */ if (ehci_polledp->ehci_polled_no_sync_flag == B_FALSE) { Sync_QH_QTD_Pool(ehcip); } /* Create done qtd list */ qtd = ehci_polled_create_done_qtd_list(ehci_polledp); /* * Traverse the list of transfer descriptors. We can't destroy * the qtd_next pointers of these QTDs because we are using it * to traverse the done list. Therefore, we can not put these * QTD's back on the QH until we are done processing all of them. */ while (qtd) { /* Get next active QTD from the active QTD list */ next_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(qtd->qtd_active_qtd_next)); /* Obtain the transfer wrapper from the QTD */ tw = (ehci_trans_wrapper_t *)EHCI_LOOKUP_ID( (uint32_t)Get_QTD(qtd->qtd_trans_wrapper)); /* Get ohci pipe from transfer wrapper */ pp = tw->tw_pipe_private; /* Look at the status */ ctrl = (uint_t)Get_QTD(qtd->qtd_ctrl) & (uint32_t)EHCI_QTD_CTRL_XACT_STATUS; error = ehci_check_for_error(ehcip, pp, tw, qtd, ctrl); /* * Check to see if there is an error. If there is error * clear the halt condition in the Endpoint Descriptor * (QH) associated with this Transfer Descriptor (QTD). */ if (error == USB_CR_OK) { num_characters += ehci_polled_handle_normal_qtd(ehci_polledp, qtd); } else { /* Clear the halt bit */ Set_QH(pp->pp_qh->qh_status, Get_QH(pp->pp_qh->qh_status) & ~(EHCI_QH_STS_XACT_STATUS)); } /* Insert this qtd back into QH's qtd list */ ehci_polled_insert_qtd(ehci_polledp, qtd); qtd = next_qtd; } return (num_characters); } /* * ehci_polled_handle_normal_qtd: */ static int ehci_polled_handle_normal_qtd( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; uchar_t *buf; ehci_trans_wrapper_t *tw; size_t length; uint32_t residue; /* Obtain the transfer wrapper from the QTD */ tw = (ehci_trans_wrapper_t *)EHCI_LOOKUP_ID((uint32_t) Get_QTD(qtd->qtd_trans_wrapper)); ASSERT(tw != NULL); buf = (uchar_t *)tw->tw_buf; length = tw->tw_length; /* * If "Total bytes of xfer" in control field of qtd is not equal to 0, * then we received less data from the usb device than requested by us. * In that case, get the actual received data size. */ residue = ((Get_QTD(qtd->qtd_ctrl) & EHCI_QTD_CTRL_BYTES_TO_XFER) >> EHCI_QTD_CTRL_BYTES_TO_XFER_SHIFT); if (residue) { length = Get_QTD(qtd->qtd_xfer_offs) + Get_QTD(qtd->qtd_xfer_len) - residue; } /* Sync IO buffer */ if (ehci_polledp->ehci_polled_no_sync_flag == B_FALSE) { Sync_IO_Buffer(tw->tw_dmahandle, length); } /* Copy the data into the message */ bcopy(buf, ehci_polledp->ehci_polled_buf, length); return (length); } /* * ehci_polled_insert_qtd: * * Insert a Transfer Descriptor (QTD) on an Endpoint Descriptor (QH). */ static void ehci_polled_insert_qtd( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_qtd_t *curr_dummy_qtd, *next_dummy_qtd; ehci_qtd_t *new_dummy_qtd; uint_t qtd_control; ehci_pipe_private_t *pp; ehci_qh_t *qh; ehci_trans_wrapper_t *tw; /* Obtain the transfer wrapper from the QTD */ tw = (ehci_trans_wrapper_t *)EHCI_LOOKUP_ID( (uint32_t)Get_QTD(qtd->qtd_trans_wrapper)); pp = tw->tw_pipe_private; /* Obtain the endpoint and interrupt request */ qh = pp->pp_qh; /* * Take this QTD off the transfer wrapper's list since * the pipe is FIFO, this must be the first QTD on the * list. */ ASSERT((ehci_qtd_t *)tw->tw_qtd_head == qtd); tw->tw_qtd_head = (ehci_qtd_t *) ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(qtd->qtd_tw_next_qtd)); /* * If the head becomes NULL, then there are no more * active QTD's for this transfer wrapper. Also set * the tail to NULL. */ if (tw->tw_qtd_head == NULL) { tw->tw_qtd_tail = NULL; } /* Convert current valid QTD as new dummy QTD */ bzero((char *)qtd, sizeof (ehci_qtd_t)); Set_QTD(qtd->qtd_state, EHCI_QTD_DUMMY); /* Rename qtd as new_dummy_qtd */ new_dummy_qtd = qtd; /* Get the current and next dummy QTDs */ curr_dummy_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QH(qh->qh_dummy_qtd)); next_dummy_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_dummy_qtd->qtd_next_qtd)); /* Update QH's dummy qtd field */ Set_QH(qh->qh_dummy_qtd, ehci_qtd_cpu_to_iommu(ehcip, next_dummy_qtd)); /* Update next dummy's next qtd pointer */ Set_QTD(next_dummy_qtd->qtd_next_qtd, ehci_qtd_cpu_to_iommu(ehcip, new_dummy_qtd)); qtd_control = (tw->tw_direction | EHCI_QTD_CTRL_INTR_ON_COMPLETE); /* * Fill in the current dummy qtd and * add the new dummy to the end. */ ehci_polled_fill_in_qtd(ehcip, curr_dummy_qtd, qtd_control, 0, tw->tw_length, tw); /* Insert this qtd onto the tw */ ehci_polled_insert_qtd_on_tw(ehcip, tw, curr_dummy_qtd); /* Insert this qtd into active interrupt QTD list */ ehci_polled_insert_qtd_into_active_intr_qtd_list( ehci_polledp, curr_dummy_qtd); } /* * ehci_polled_fill_in_qtd: * * Fill in the fields of a Transfer Descriptor (QTD). * The "Buffer Pointer" fields of a QTD are retrieved from the TW * it is associated with. * * Unlike the it's ehci_fill_in_qtd counterpart, we do not * set the alternative ptr in polled mode. There is not need * for it in polled mode, because it doesn't need to cleanup * short xfer conditions. * * Note: * qtd_dma_offs - the starting offset into the TW buffer, where the QTD * should transfer from. It should be 4K aligned. And when * a TW has more than one QTDs, the QTDs must be filled in * increasing order. * qtd_length - the total bytes to transfer. */ static void ehci_polled_fill_in_qtd( ehci_state_t *ehcip, ehci_qtd_t *qtd, uint_t qtd_ctrl, size_t qtd_dma_offs, size_t qtd_length, ehci_trans_wrapper_t *tw) { uint32_t buf_addr; size_t buf_len = qtd_length; uint32_t ctrl = qtd_ctrl; uint_t i = 0; int rem_len; /* Assert that the qtd to be filled in is a dummy */ ASSERT(Get_QTD(qtd->qtd_state) == EHCI_QTD_DUMMY); /* Change QTD's state Active */ Set_QTD(qtd->qtd_state, EHCI_QTD_ACTIVE); /* Set the total length data tarnsfer */ ctrl |= (((qtd_length << EHCI_QTD_CTRL_BYTES_TO_XFER_SHIFT) & EHCI_QTD_CTRL_BYTES_TO_XFER) | EHCI_QTD_CTRL_MAX_ERR_COUNTS); /* * QTDs 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. */ ASSERT(buf_len == 0 || qtd_dma_offs >= tw->tw_dma_offs); /* * Save the starting dma buffer offset used and * length of data that will be transfered in * the current QTD. */ Set_QTD(qtd->qtd_xfer_offs, qtd_dma_offs); Set_QTD(qtd->qtd_xfer_len, buf_len); while (buf_len) { /* * Advance to the next DMA cookie until finding the cookie * that qtd_dma_offs falls in. * It is very likely this loop will never repeat more than * once. It is here just to accommodate the case qtd_dma_offs * is increased by multiple cookies during two consecutive * calls into this function. In that case, the interim DMA * buffer is allowed to be skipped. */ while ((tw->tw_dma_offs + tw->tw_cookie.dmac_size) <= qtd_dma_offs) { /* * tw_dma_offs always points to the starting offset * of a cookie */ tw->tw_dma_offs += tw->tw_cookie.dmac_size; ddi_dma_nextcookie(tw->tw_dmahandle, &tw->tw_cookie); tw->tw_cookie_idx++; ASSERT(tw->tw_cookie_idx < tw->tw_ncookies); } /* * Counting the remained buffer length to be filled in * the QTD for current DMA cookie */ rem_len = (tw->tw_dma_offs + tw->tw_cookie.dmac_size) - qtd_dma_offs; /* Update the beginning of the buffer */ buf_addr = (qtd_dma_offs - tw->tw_dma_offs) + tw->tw_cookie.dmac_address; ASSERT((buf_addr % EHCI_4K_ALIGN) == 0); Set_QTD(qtd->qtd_buf[i], buf_addr); if (buf_len <= EHCI_MAX_QTD_BUF_SIZE) { ASSERT(buf_len <= rem_len); break; } else { ASSERT(rem_len >= EHCI_MAX_QTD_BUF_SIZE); buf_len -= EHCI_MAX_QTD_BUF_SIZE; qtd_dma_offs += EHCI_MAX_QTD_BUF_SIZE; } i++; } /* * For control, bulk and interrupt QTD, now * enable current QTD by setting active bit. */ Set_QTD(qtd->qtd_ctrl, (ctrl | EHCI_QTD_CTRL_ACTIVE_XACT)); Set_QTD(qtd->qtd_trans_wrapper, (uint32_t)tw->tw_id); } /* * ehci_polled_insert_qtd_on_tw: * * The transfer wrapper keeps a list of all Transfer Descriptors (QTD) that * are allocated for this transfer. Insert a QTD onto this list. The list * of QTD's does not include the dummy QTD that is at the end of the list of * QTD's for the endpoint. */ static void ehci_polled_insert_qtd_on_tw( ehci_state_t *ehcip, ehci_trans_wrapper_t *tw, ehci_qtd_t *qtd) { /* * Set the next pointer to NULL because * this is the last QTD on list. */ Set_QTD(qtd->qtd_tw_next_qtd, NULL); if (tw->tw_qtd_head == NULL) { ASSERT(tw->tw_qtd_tail == NULL); tw->tw_qtd_head = qtd; tw->tw_qtd_tail = qtd; } else { ehci_qtd_t *dummy = (ehci_qtd_t *)tw->tw_qtd_tail; ASSERT(dummy != NULL); ASSERT(dummy != qtd); ASSERT(Get_QTD(qtd->qtd_state) != EHCI_QTD_DUMMY); /* Add the qtd to the end of the list */ Set_QTD(dummy->qtd_tw_next_qtd, ehci_qtd_cpu_to_iommu(ehcip, qtd)); tw->tw_qtd_tail = qtd; ASSERT(Get_QTD(qtd->qtd_tw_next_qtd) == NULL); } } /* * ehci_polled_create_done_qtd_list: * * Create done qtd list from active qtd list. */ static ehci_qtd_t * ehci_polled_create_done_qtd_list( ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_qtd_t *curr_qtd = NULL, *next_qtd = NULL; ehci_qtd_t *done_qtd_list = NULL, *last_done_qtd = NULL; USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "ehci_polled_create_done_qtd_list:"); curr_qtd = ehci_polledp->ehci_polled_active_intr_qtd_list; while (curr_qtd) { /* Get next qtd from the active qtd list */ next_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_qtd->qtd_active_qtd_next)); /* Check this QTD has been processed by Host Controller */ if (!(Get_QTD(curr_qtd->qtd_ctrl) & EHCI_QTD_CTRL_ACTIVE_XACT)) { /* Remove this QTD from active QTD list */ ehci_polled_remove_qtd_from_active_intr_qtd_list( ehci_polledp, curr_qtd); Set_QTD(curr_qtd->qtd_active_qtd_next, NULL); if (done_qtd_list) { Set_QTD(last_done_qtd->qtd_active_qtd_next, ehci_qtd_cpu_to_iommu(ehcip, curr_qtd)); last_done_qtd = curr_qtd; } else { done_qtd_list = curr_qtd; last_done_qtd = curr_qtd; } } curr_qtd = next_qtd; } return (done_qtd_list); } /* * ehci_polled_insert_qtd_into_active_intr_qtd_list: * * Insert current QTD into active interrupt QTD list. */ static void ehci_polled_insert_qtd_into_active_intr_qtd_list( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_qtd_t *curr_qtd, *next_qtd; curr_qtd = ehci_polledp->ehci_polled_active_intr_qtd_list; /* Insert this qtd into active intr qtd list */ if (curr_qtd) { next_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_qtd->qtd_active_qtd_next)); while (next_qtd) { curr_qtd = next_qtd; next_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_qtd->qtd_active_qtd_next)); } Set_QTD(qtd->qtd_active_qtd_prev, ehci_qtd_cpu_to_iommu(ehcip, curr_qtd)); Set_QTD(curr_qtd->qtd_active_qtd_next, ehci_qtd_cpu_to_iommu(ehcip, qtd)); } else { ehci_polledp->ehci_polled_active_intr_qtd_list = qtd; Set_QTD(qtd->qtd_active_qtd_next, NULL); Set_QTD(qtd->qtd_active_qtd_prev, NULL); } } /* * ehci_polled_remove_qtd_from_active_intr_qtd_list: * * Remove current QTD from the active QTD list. */ void ehci_polled_remove_qtd_from_active_intr_qtd_list( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_qtd_t *curr_qtd, *prev_qtd, *next_qtd; ASSERT(qtd != NULL); curr_qtd = ehci_polledp->ehci_polled_active_intr_qtd_list; while ((curr_qtd) && (curr_qtd != qtd)) { curr_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_qtd->qtd_active_qtd_next)); } if ((curr_qtd) && (curr_qtd == qtd)) { prev_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_qtd->qtd_active_qtd_prev)); next_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(curr_qtd->qtd_active_qtd_next)); if (prev_qtd) { Set_QTD(prev_qtd->qtd_active_qtd_next, Get_QTD(curr_qtd->qtd_active_qtd_next)); } else { ehci_polledp-> ehci_polled_active_intr_qtd_list = next_qtd; } if (next_qtd) { Set_QTD(next_qtd->qtd_active_qtd_prev, Get_QTD(curr_qtd->qtd_active_qtd_prev)); } } } /* * ehci_polled_traverse_qtds: * * Traverse the list of QTDs for given pipe using transfer wrapper. Since * the endpoint is marked as Halted, the Host Controller (HC) is no longer * accessing these QTDs. Remove all the QTDs that are attached to endpoint. */ static void ehci_polled_traverse_qtds( ehci_polled_t *ehci_polledp, usba_pipe_handle_data_t *ph) { ehci_state_t *ehcip = ehci_polledp->ehci_polled_ehcip; ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private; ehci_trans_wrapper_t *next_tw; ehci_qtd_t *qtd; ehci_qtd_t *next_qtd; /* Process the transfer wrappers for this pipe */ next_tw = pp->pp_tw_head; while (next_tw) { qtd = (ehci_qtd_t *)next_tw->tw_qtd_head; /* Walk through each QTD for this transfer wrapper */ while (qtd) { /* Remove this QTD from active QTD list */ ehci_polled_remove_qtd_from_active_intr_qtd_list( ehci_polledp, qtd); next_qtd = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(qtd->qtd_tw_next_qtd)); /* Deallocate this QTD */ ehci_deallocate_qtd(ehcip, qtd); qtd = next_qtd; } next_tw = next_tw->tw_next; } /* Clear current qtd pointer */ Set_QH(pp->pp_qh->qh_curr_qtd, (uint32_t)0x00000000); /* Update the next qtd pointer in the QH */ Set_QH(pp->pp_qh->qh_next_qtd, Get_QH(pp->pp_qh->qh_dummy_qtd)); } /* * ehci_polled_finish_interrupt: */ static void ehci_polled_finish_interrupt( ehci_state_t *ehcip, uint_t intr) { /* Acknowledge the interrupt */ Set_OpReg(ehci_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(ehci_status); }