/* * 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * EHCI Host Controller Driver (EHCI) * * The EHCI driver is a software driver which interfaces to the Universal * Serial Bus layer (USBA) and the Host Controller (HC). The interface to * the Host Controller is defined by the EHCI Host Controller Interface. * * This 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 #ifndef __sparc extern void invalidate_cache(); #endif /* * 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_intr_qtd( ehci_polled_t *ehci_polledp, ehci_qtd_t *qtd); static void ehci_polled_insert_bulk_qtd( ehci_polled_t *ehci_polledp); 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); static int ehci_polled_create_tw( ehci_polled_t *ehci_polledp, usba_pipe_handle_data_t *ph, usb_flags_t usb_flags); static void ehci_polled_insert_async_qh( ehci_state_t *ehcip, ehci_pipe_private_t *pp); static void ehci_polled_remove_async_qh( ehci_state_t *ehcip, ehci_pipe_private_t *pp); /* * 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 input device * 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_state_t *ehcip; usba_pipe_handle_data_t *ph; ehci_pipe_private_t *pp; int pipe_attr; #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif ehci_polledp = (ehci_polled_t *)info->uci_private; ehcip = ehci_polledp->ehci_polled_ehcip; ph = ehci_polledp->ehci_polled_input_pipe_handle; pp = (ehci_pipe_private_t *)ph->p_hcd_private; pipe_attr = ph->p_ep.bmAttributes & USB_EP_ATTR_MASK; #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif ehci_polledp->ehci_polled_entry++; /* * If the controller is already switched over, just return */ if (ehci_polledp->ehci_polled_entry > 1) { return (USB_SUCCESS); } switch (pipe_attr) { case USB_EP_ATTR_INTR: ehci_polled_save_state(ehci_polledp); break; case USB_EP_ATTR_BULK: #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif Set_OpReg(ehci_command, (Get_OpReg(ehci_command) & ~(EHCI_CMD_PERIODIC_SCHED_ENABLE | EHCI_CMD_ASYNC_SCHED_ENABLE))); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); ehci_polled_insert_async_qh(ehcip, pp); Set_OpReg(ehci_command, (Get_OpReg(ehci_command) | EHCI_CMD_ASYNC_SCHED_ENABLE)); #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); break; default: return (USB_FAILURE); } 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_state_t *ehcip; ehci_pipe_private_t *pp; int pipe_attr; #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif ehci_polledp = (ehci_polled_t *)info->uci_private; ehcip = ehci_polledp->ehci_polled_ehcip; pp = (ehci_pipe_private_t *)ehci_polledp-> ehci_polled_input_pipe_handle->p_hcd_private; pipe_attr = ehci_polledp->ehci_polled_input_pipe_handle-> p_ep.bmAttributes & USB_EP_ATTR_MASK; #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif 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; switch (pipe_attr & USB_EP_ATTR_MASK) { case USB_EP_ATTR_INTR: ehci_polled_restore_state(ehci_polledp); break; case USB_EP_ATTR_BULK: #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif Set_OpReg(ehci_command, (Get_OpReg(ehci_command) & ~(EHCI_CMD_PERIODIC_SCHED_ENABLE | EHCI_CMD_ASYNC_SCHED_ENABLE))); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); ehci_polled_remove_async_qh(ehcip, pp); Set_OpReg(ehci_command, (Get_OpReg(ehci_command) | EHCI_CMD_ASYNC_SCHED_ENABLE | EHCI_CMD_ASYNC_SCHED_ENABLE)); #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); break; default: return (USB_FAILURE); } 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; int pipe_attr; 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; pipe_attr = ehci_polledp->ehci_polled_input_pipe_handle-> p_ep.bmAttributes & USB_EP_ATTR_MASK; if (pipe_attr == USB_EP_ATTR_BULK) { ehci_polled_insert_bulk_qtd(ehci_polledp); } 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); } /* Process any QTD's on the active interrupt qtd list */ *num_characters = ehci_polled_process_active_intr_qtd_list(ehci_polledp); #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return (USB_SUCCESS); } /* * ehci_hcdi_polled_output_init: * * This is the initialization routine for handling the USB serial output * in POLLED mode. This routine is not called from POLLED mode, so * it is OK to acquire mutexes. */ int ehci_hcdi_polled_output_init( usba_pipe_handle_data_t *ph, usb_console_info_impl_t *console_output_info) { ehci_polled_t *ehci_polledp; ehci_state_t *ehcip; ehci_pipe_private_t *pp; 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_output_info); if (ret != USB_SUCCESS) { /* Allow interrupts to continue */ mutex_exit(&ehcip->ehci_int_mutex); return (ret); } ehci_polledp = (ehci_polled_t *)console_output_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_OUTPUT_MODE; /* * Insert the Endpoint Descriptor to appropriate endpoint list. */ pp = (ehci_pipe_private_t *)ehci_polledp-> ehci_polled_input_pipe_handle->p_hcd_private; ehci_polled_insert_async_qh(ehcip, pp); /* * 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_output_fini: */ int ehci_hcdi_polled_output_fini(usb_console_info_impl_t *info) { ehci_polled_t *ehci_polledp; ehci_state_t *ehcip; ehci_pipe_private_t *pp; int ret; ehci_polledp = (ehci_polled_t *)info->uci_private; ehcip = ehci_polledp->ehci_polled_ehcip; mutex_enter(&ehcip->ehci_int_mutex); /* Remove the Endpoint Descriptor. */ pp = (ehci_pipe_private_t *)ehci_polledp-> ehci_polled_input_pipe_handle->p_hcd_private; ehci_polled_remove_async_qh(ehcip, pp); /* * 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_OUTPUT_MODE; ret = ehci_polled_fini(ehci_polledp); info->uci_private = NULL; mutex_exit(&ehcip->ehci_int_mutex); return (ret); } /* * ehci_hcdi_polled_output_enter: * * everything is done in input enter */ /*ARGSUSED*/ int ehci_hcdi_polled_output_enter(usb_console_info_impl_t *info) { return (USB_SUCCESS); } /* * ehci_hcdi_polled_output_exit: * * everything is done in input exit */ /*ARGSUSED*/ int ehci_hcdi_polled_output_exit(usb_console_info_impl_t *info) { return (USB_SUCCESS); } /* * ehci_hcdi_polled_write: * Put a key character. */ int ehci_hcdi_polled_write(usb_console_info_impl_t *info, uchar_t *buf, uint_t num_characters, uint_t *num_characters_written) { ehci_state_t *ehcip; ehci_polled_t *ehci_polledp; ehci_trans_wrapper_t *tw; ehci_pipe_private_t *pp; usba_pipe_handle_data_t *ph; int intr; #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif ehci_polledp = (ehci_polled_t *)info->uci_private; ehcip = ehci_polledp->ehci_polled_ehcip; ph = ehci_polledp->ehci_polled_input_pipe_handle; pp = (ehci_pipe_private_t *)ph->p_hcd_private; /* Disable all list processing */ 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); tw = pp->pp_tw_head; ASSERT(tw != NULL); /* copy transmit buffer */ if (num_characters > POLLED_RAW_BUF_SIZE) { cmn_err(CE_NOTE, "polled write size %d bigger than %d", num_characters, POLLED_RAW_BUF_SIZE); num_characters = POLLED_RAW_BUF_SIZE; } tw->tw_length = num_characters; ddi_rep_put8(tw->tw_accesshandle, buf, (uint8_t *)tw->tw_buf, tw->tw_length, DDI_DEV_AUTOINCR); Sync_IO_Buffer_for_device(tw->tw_dmahandle, tw->tw_length); ehci_polled_insert_bulk_qtd(ehci_polledp); /* Enable async list processing */ Set_OpReg(ehci_command, (Get_OpReg(ehci_command) | EHCI_CMD_ASYNC_SCHED_ENABLE)); /* Wait for few milliseconds */ drv_usecwait(EHCI_POLLED_TIMEWAIT); while (!((Get_OpReg(ehci_status)) & (EHCI_INTR_USB |EHCI_INTR_FRAME_LIST_ROLLOVER | EHCI_INTR_USB_ERROR))) { #ifndef __sparc invalidate_cache(); #else ; #endif } intr = (Get_OpReg(ehci_status)) & (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) { 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)) { (void) ehci_polled_process_active_intr_qtd_list(ehci_polledp); /* Acknowledge the USB and USB error interrupt */ ehci_polled_finish_interrupt(ehcip, intr & (EHCI_INTR_USB | EHCI_INTR_USB_ERROR)); } *num_characters_written = num_characters; #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; int pipe_attr; 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 endpoint. This QH will be inserted in * to the lattice chain for the 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; pipe_attr = ph->p_ep.bmAttributes & USB_EP_ATTR_MASK; switch (pipe_attr) { case USB_EP_ATTR_INTR: /* * 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); break; case USB_EP_ATTR_BULK: if ((ehci_polled_create_tw(ehci_polledp, ehci_polledp->ehci_polled_input_pipe_handle, USB_FLAGS_SLEEP)) != USB_SUCCESS) { return (USB_NO_RESOURCES); } break; default: return (USB_FAILURE); } 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], 0); Set_QH(qh->qh_buf_high[i], 0); } 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, 0); 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; int pipe_attr, pipe_dir; /* 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); pipe_attr = ehci_polledp->ehci_polled_input_pipe_handle-> p_ep.bmAttributes & USB_EP_ATTR_MASK; pipe_dir = ehci_polledp->ehci_polled_input_pipe_handle-> p_ep.bEndpointAddress & USB_EP_DIR_MASK; /* * 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 ehci 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) { /* Clear the halt bit */ Set_QH(pp->pp_qh->qh_status, Get_QH(pp->pp_qh->qh_status) & ~(EHCI_QH_STS_XACT_STATUS)); } else if (pipe_dir == USB_EP_DIR_IN) { num_characters += ehci_polled_handle_normal_qtd(ehci_polledp, qtd); } /* Insert this qtd back into QH's qtd list */ switch (pipe_attr) { case USB_EP_ATTR_INTR: ehci_polled_insert_intr_qtd(ehci_polledp, qtd); break; case USB_EP_ATTR_BULK: if (tw->tw_qtd_free_list != NULL) { uint32_t td_addr; td_addr = ehci_qtd_cpu_to_iommu(ehcip, tw->tw_qtd_free_list); Set_QTD(qtd->qtd_tw_next_qtd, td_addr); Set_QTD(qtd->qtd_state, EHCI_QTD_DUMMY); tw->tw_qtd_free_list = qtd; } else { tw->tw_qtd_free_list = qtd; Set_QTD(qtd->qtd_tw_next_qtd, 0); Set_QTD(qtd->qtd_state, EHCI_QTD_DUMMY); } break; } 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 ((int)length); } /* * ehci_polled_insert_intr_qtd: * * Insert a Transfer Descriptor (QTD) on an Endpoint Descriptor (QH). */ static void ehci_polled_insert_intr_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); } static void ehci_polled_insert_bulk_qtd( ehci_polled_t *ehci_polledp) { ehci_state_t *ehcip; ehci_pipe_private_t *pp; ehci_trans_wrapper_t *tw; ehci_qh_t *qh; ehci_qtd_t *new_dummy_qtd; ehci_qtd_t *curr_dummy_qtd, *next_dummy_qtd; uint_t qtd_control; ehcip = ehci_polledp->ehci_polled_ehcip; pp = (ehci_pipe_private_t *)ehci_polledp-> ehci_polled_input_pipe_handle->p_hcd_private; tw = pp->pp_tw_head; qh = ehci_polledp->ehci_polled_qh; new_dummy_qtd = tw->tw_qtd_free_list; if (new_dummy_qtd == NULL) { return; } tw->tw_qtd_free_list = ehci_qtd_iommu_to_cpu(ehcip, Get_QTD(new_dummy_qtd->qtd_tw_next_qtd)); Set_QTD(new_dummy_qtd->qtd_tw_next_qtd, 0); /* 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 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, 0); 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) == 0); } } /* * 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, 0); 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, 0); Set_QTD(qtd->qtd_active_qtd_prev, 0); } } /* * 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); } static int ehci_polled_create_tw( ehci_polled_t *ehci_polledp, usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { uint_t ccount; size_t real_length; ehci_trans_wrapper_t *tw; ddi_device_acc_attr_t dev_attr; int result, pipe_dir, qtd_count; ehci_state_t *ehcip; ehci_pipe_private_t *pp; ddi_dma_attr_t dma_attr; ehcip = ehci_polledp->ehci_polled_ehcip; pp = (ehci_pipe_private_t *)ph->p_hcd_private; /* Get the required qtd counts */ qtd_count = (POLLED_RAW_BUF_SIZE - 1) / EHCI_MAX_QTD_XFER_SIZE + 1; if ((tw = kmem_zalloc(sizeof (ehci_trans_wrapper_t), KM_NOSLEEP)) == NULL) { return (USB_FAILURE); } /* allow sg lists for transfer wrapper dma memory */ bcopy(&ehcip->ehci_dma_attr, &dma_attr, sizeof (ddi_dma_attr_t)); dma_attr.dma_attr_sgllen = EHCI_DMA_ATTR_TW_SGLLEN; dma_attr.dma_attr_align = EHCI_DMA_ATTR_ALIGNMENT; /* Allocate the DMA handle */ if ((result = ddi_dma_alloc_handle(ehcip->ehci_dip, &dma_attr, DDI_DMA_DONTWAIT, 0, &tw->tw_dmahandle)) != DDI_SUCCESS) { kmem_free(tw, sizeof (ehci_trans_wrapper_t)); return (USB_FAILURE); } dev_attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; dev_attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; dev_attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; /* Allocate the memory */ if ((result = ddi_dma_mem_alloc(tw->tw_dmahandle, POLLED_RAW_BUF_SIZE, &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &tw->tw_buf, &real_length, &tw->tw_accesshandle)) != DDI_SUCCESS) { ddi_dma_free_handle(&tw->tw_dmahandle); kmem_free(tw, sizeof (ehci_trans_wrapper_t)); return (USB_FAILURE); } /* Bind the handle */ if ((result = ddi_dma_addr_bind_handle(tw->tw_dmahandle, NULL, tw->tw_buf, real_length, DDI_DMA_RDWR|DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &tw->tw_cookie, &ccount)) != DDI_DMA_MAPPED) { ddi_dma_mem_free(&tw->tw_accesshandle); ddi_dma_free_handle(&tw->tw_dmahandle); kmem_free(tw, sizeof (ehci_trans_wrapper_t)); return (USB_FAILURE); } /* The cookie count should be 1 */ if (ccount != 1) { result = ddi_dma_unbind_handle(tw->tw_dmahandle); ASSERT(result == DDI_SUCCESS); ddi_dma_mem_free(&tw->tw_accesshandle); ddi_dma_free_handle(&tw->tw_dmahandle); kmem_free(tw, sizeof (ehci_trans_wrapper_t)); return (USB_FAILURE); } if (ehci_allocate_tds_for_tw(ehcip, pp, tw, qtd_count) == USB_SUCCESS) { tw->tw_num_qtds = qtd_count; } else { ehci_deallocate_tw(ehcip, pp, tw); return (USB_FAILURE); } tw->tw_cookie_idx = 0; tw->tw_dma_offs = 0; /* * Only allow one wrapper to be added at a time. Insert the * new transaction wrapper into the list for this pipe. */ if (pp->pp_tw_head == NULL) { pp->pp_tw_head = tw; pp->pp_tw_tail = tw; } else { pp->pp_tw_tail->tw_next = tw; pp->pp_tw_tail = tw; } /* Store the transfer length */ tw->tw_length = POLLED_RAW_BUF_SIZE; /* Store a back pointer to the pipe private structure */ tw->tw_pipe_private = pp; /* Store the transfer type - synchronous or asynchronous */ tw->tw_flags = usb_flags; /* Get and Store 32bit ID */ tw->tw_id = EHCI_GET_ID((void *)tw); pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK; tw->tw_direction = (pipe_dir == USB_EP_DIR_OUT)? EHCI_QTD_CTRL_OUT_PID : EHCI_QTD_CTRL_IN_PID; USB_DPRINTF_L4(PRINT_MASK_ALLOC, ehcip->ehci_log_hdl, "ehci_create_transfer_wrapper: tw = 0x%p, ncookies = %u", (void *)tw, tw->tw_ncookies); return (USB_SUCCESS); } /* * ehci_polled_insert_async_qh: * * Insert a bulk endpoint into the Host Controller's (HC) * Asynchronous schedule endpoint list. */ static void ehci_polled_insert_async_qh( ehci_state_t *ehcip, ehci_pipe_private_t *pp) { ehci_qh_t *qh = pp->pp_qh; ehci_qh_t *async_head_qh; ehci_qh_t *next_qh; uintptr_t qh_addr; /* Make sure this QH is not already in the list */ ASSERT((Get_QH(qh->qh_prev) & EHCI_QH_LINK_PTR) == 0); qh_addr = ehci_qh_cpu_to_iommu(ehcip, qh); /* Obtain a ptr to the head of the Async schedule list */ async_head_qh = ehcip->ehci_head_of_async_sched_list; if (async_head_qh == NULL) { /* Set this QH to be the "head" of the circular list */ Set_QH(qh->qh_ctrl, (Get_QH(qh->qh_ctrl) | EHCI_QH_CTRL_RECLAIM_HEAD)); /* Set new QH's link and previous pointer to itself */ Set_QH(qh->qh_link_ptr, qh_addr | EHCI_QH_LINK_REF_QH); Set_QH(qh->qh_prev, qh_addr); ehcip->ehci_head_of_async_sched_list = qh; /* Set the head ptr to the new endpoint */ Set_OpReg(ehci_async_list_addr, qh_addr); /* * For some reason this register might get nulled out by * the Uli M1575 South Bridge. To workaround the hardware * problem, check the value after write and retry if the * last write fails. * * If the ASYNCLISTADDR remains "stuck" after * EHCI_MAX_RETRY retries, then the M1575 is broken * and is stuck in an inconsistent state and is about * to crash the machine with a trn_oor panic when it * does a DMA read from 0x0. It is better to panic * now rather than wait for the trn_oor crash; this * way Customer Service will have a clean signature * that indicts the M1575 chip rather than a * mysterious and hard-to-diagnose trn_oor panic. */ if ((ehcip->ehci_vendor_id == PCI_VENDOR_ULi_M1575) && (ehcip->ehci_device_id == PCI_DEVICE_ULi_M1575) && (qh_addr != Get_OpReg(ehci_async_list_addr))) { int retry = 0; Set_OpRegRetry(ehci_async_list_addr, qh_addr, retry); if (retry >= EHCI_MAX_RETRY) cmn_err(CE_PANIC, "ehci_insert_async_qh:" " ASYNCLISTADDR write failed."); USB_DPRINTF_L2(PRINT_MASK_ATTA, ehcip->ehci_log_hdl, "ehci_insert_async_qh: ASYNCLISTADDR " "write failed, retry=%d", retry); } } else { ASSERT(Get_QH(async_head_qh->qh_ctrl) & EHCI_QH_CTRL_RECLAIM_HEAD); /* Ensure this QH's "H" bit is not set */ Set_QH(qh->qh_ctrl, (Get_QH(qh->qh_ctrl) & ~EHCI_QH_CTRL_RECLAIM_HEAD)); next_qh = ehci_qh_iommu_to_cpu(ehcip, Get_QH(async_head_qh->qh_link_ptr) & EHCI_QH_LINK_PTR); /* Set new QH's link and previous pointers */ Set_QH(qh->qh_link_ptr, Get_QH(async_head_qh->qh_link_ptr) | EHCI_QH_LINK_REF_QH); Set_QH(qh->qh_prev, ehci_qh_cpu_to_iommu(ehcip, async_head_qh)); /* Set next QH's prev pointer */ Set_QH(next_qh->qh_prev, ehci_qh_cpu_to_iommu(ehcip, qh)); /* Set QH Head's link pointer points to new QH */ Set_QH(async_head_qh->qh_link_ptr, qh_addr | EHCI_QH_LINK_REF_QH); } } /* * ehci_remove_async_qh: * * Remove a control/bulk endpoint into the Host Controller's (HC) * Asynchronous schedule endpoint list. */ static void ehci_polled_remove_async_qh( ehci_state_t *ehcip, ehci_pipe_private_t *pp) { ehci_qh_t *qh = pp->pp_qh; /* qh to be removed */ ehci_qh_t *prev_qh, *next_qh; prev_qh = ehci_qh_iommu_to_cpu(ehcip, Get_QH(qh->qh_prev) & EHCI_QH_LINK_PTR); next_qh = ehci_qh_iommu_to_cpu(ehcip, Get_QH(qh->qh_link_ptr) & EHCI_QH_LINK_PTR); /* Make sure this QH is in the list */ ASSERT(prev_qh != NULL); /* * If next QH and current QH are the same, then this is the last * QH on the Asynchronous Schedule list. */ if (qh == next_qh) { ASSERT(Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_RECLAIM_HEAD); /* * Null our pointer to the async sched list, but do not * touch the host controller's list_addr. */ ehcip->ehci_head_of_async_sched_list = NULL; } else { /* If this QH is the HEAD then find another one to replace it */ if (ehcip->ehci_head_of_async_sched_list == qh) { ASSERT(Get_QH(qh->qh_ctrl) & EHCI_QH_CTRL_RECLAIM_HEAD); ehcip->ehci_head_of_async_sched_list = next_qh; Set_QH(next_qh->qh_ctrl, Get_QH(next_qh->qh_ctrl) | EHCI_QH_CTRL_RECLAIM_HEAD); } Set_QH(prev_qh->qh_link_ptr, Get_QH(qh->qh_link_ptr)); Set_QH(next_qh->qh_prev, Get_QH(qh->qh_prev)); } /* qh_prev to indicate it is no longer in the circular list */ Set_QH(qh->qh_prev, 0); }