/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * This module contains the specific uhci code used in POLLED mode. */ #include #include #ifndef __sparc extern void invalidate_cache(); #endif /* * Internal Function Prototypes */ /* Polled initialization routine */ static int uhci_polled_init(usba_pipe_handle_data_t *, uhci_state_t *, usb_console_info_impl_t *); /* Polled fini routine */ static int uhci_polled_fini(uhci_polled_t *, uhci_state_t *); /* Polled save state routine */ static void uhci_polled_save_state(uhci_polled_t *); /* Polled restore state routine */ static void uhci_polled_restore_state(uhci_polled_t *); /* Polled read routines */ static int uhci_polled_insert_td_on_qh(uhci_polled_t *, usba_pipe_handle_data_t *); static uhci_trans_wrapper_t *uhci_polled_create_tw(uhci_state_t *); /* * POLLED entry points * * These functions are entry points into the POLLED code. */ /* * uhci_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 uhci_hcdi_polled_input_init(usba_pipe_handle_data_t *ph, uchar_t **polled_buf, usb_console_info_impl_t *console_input_info) { int ret; uhci_polled_t *uhci_polledp; uhci_state_t *uhcip; uhcip = uhci_obtain_state(ph->p_usba_device->usb_root_hub_dip); /* * Grab the uhci_int_mutex so that things don't change on us * if an interrupt comes in. */ mutex_enter(&uhcip->uhci_int_mutex); ret = uhci_polled_init(ph, uhcip, console_input_info); if (ret != USB_SUCCESS) { mutex_exit(&uhcip->uhci_int_mutex); return (ret); } uhci_polledp = (uhci_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. */ uhci_polledp->uhci_polled_flags |= POLLED_INPUT_MODE; /* * 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. */ uhci_polledp->uhci_polled_buf = (uchar_t *)kmem_zalloc(POLLED_RAW_BUF_SIZE, KM_SLEEP); *polled_buf = uhci_polledp->uhci_polled_buf; mutex_exit(&uhcip->uhci_int_mutex); return (USB_SUCCESS); } /* * uhci_hcdi_polled_input_fini: */ int uhci_hcdi_polled_input_fini(usb_console_info_impl_t *info) { int ret; uhci_state_t *uhcip; uhci_polled_t *uhci_polledp; uhci_polledp = (uhci_polled_t *)info->uci_private; uhcip = uhci_polledp->uhci_polled_uhcip; mutex_enter(&uhcip->uhci_int_mutex); /* Free the buffer that we copied data into */ kmem_free(uhci_polledp->uhci_polled_buf, POLLED_RAW_BUF_SIZE); ret = uhci_polled_fini(uhci_polledp, uhcip); info->uci_private = NULL; mutex_exit(&uhcip->uhci_int_mutex); return (ret); } /* * uhci_hcdi_polled_input_enter: * This is where we enter into POLLED mode. This routine sets up * everything so that calls to uhci_hcdi_polled_read will return * characters. */ int uhci_hcdi_polled_input_enter(usb_console_info_impl_t *info) { uhci_polled_t *uhci_polledp; uhci_polledp = (uhci_polled_t *)info->uci_private; uhci_polledp->uhci_polled_entry++; /* * If the controller is already switched over, just return */ if (uhci_polledp->uhci_polled_entry > 1) { return (USB_SUCCESS); } uhci_polled_save_state(uhci_polledp); uhci_polledp->uhci_polled_flags |= POLLED_INPUT_MODE_INUSE; return (USB_SUCCESS); } /* * uhci_hcdi_polled_input_exit: * This is where we exit POLLED mode. This routine restores * everything that is needed to continue operation. */ int uhci_hcdi_polled_input_exit(usb_console_info_impl_t *info) { uhci_polled_t *uhci_polledp; uhci_polledp = (uhci_polled_t *)info->uci_private; uhci_polledp->uhci_polled_entry--; /* * If there are still outstanding "enters", just return */ if (uhci_polledp->uhci_polled_entry > 0) { return (USB_SUCCESS); } uhci_polledp->uhci_polled_flags &= ~POLLED_INPUT_MODE_INUSE; uhci_polled_restore_state(uhci_polledp); return (USB_SUCCESS); } /* * uhci_hcdi_polled_read: * Get a key character */ int uhci_hcdi_polled_read(usb_console_info_impl_t *info, uint_t *num_characters) { uhci_state_t *uhcip; uhci_polled_t *uhci_polledp; uhci_td_t *td; uhci_trans_wrapper_t *tw; ushort_t intr_status; uhci_polledp = (uhci_polled_t *)info->uci_private; uhcip = uhci_polledp->uhci_polled_uhcip; /* * This is a temporary work around for halt problem. The upper * layer code does not call the right sequence of entry points * points for reading a character in a polled mode. Once the * upper layer code is fixed, the following code (two lines) * must be removed. */ if (uhci_polledp->uhci_polled_entry == 0) { if (uhci_hcdi_polled_input_enter(info) != USB_SUCCESS) { cmn_err(CE_WARN, "Entering Polled Mode failed"); } } #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif #ifndef __sparc invalidate_cache(); #endif td = uhci_polledp->uhci_polled_td; /* * Check to see if there are any TD's on the done head. */ if (GetTD_status(uhcip, td) & UHCI_TD_ACTIVE) { *num_characters = 0; } else { /* * If the TD does not complete, retry. */ if ((GetTD_status(uhcip, td) & TD_STATUS_MASK) || (GetTD_alen(uhcip, td) == ZERO_LENGTH)) { *num_characters = 0; SetTD_alen(uhcip, td, 0); } else { *num_characters = GetTD_alen(uhcip, td) + 1; tw = td->tw; /* Copy the data into the message */ ddi_rep_get8(tw->tw_accesshandle, (uint8_t *)uhci_polledp->uhci_polled_buf, (uint8_t *)td->tw->tw_buf, *num_characters, DDI_DEV_AUTOINCR); } /* * Insert the td again into the lattice. */ SetTD_dtogg(uhcip, td, GetTD_dtogg(uhcip, td) == 0 ? 1 : 0); SetTD_status(uhcip, td, UHCI_TD_ACTIVE); SetQH32(uhcip, uhci_polledp->uhci_polled_qh->element_ptr, TD_PADDR(td)); /* Clear the interrupt status register */ intr_status = Get_OpReg16(USBSTS); Set_OpReg16(USBSTS, intr_status); } #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return (USB_SUCCESS); } /* * uhci_polled_init: * Initialize generic information that is needed to provide USB/POLLED * support. */ static int uhci_polled_init(usba_pipe_handle_data_t *ph, uhci_state_t *uhcip, usb_console_info_impl_t *console_info) { uhci_polled_t *uhci_polledp; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* * If the structure has already been initialized, then we don't * need to redo it. */ if (console_info->uci_private != NULL) { return (USB_SUCCESS); } /* Allocate and intitialize a polled mode state structure */ uhci_polledp = (uhci_polled_t *)kmem_zalloc(sizeof (uhci_polled_t), KM_SLEEP); /* * Keep a copy of normal mode state structure and pipe handle. */ uhci_polledp->uhci_polled_uhcip = uhcip; uhci_polledp->uhci_polled_ph = ph; /* * Allocate a queue head for the device. This queue head wiil be * put in action when we switch to polled mode in _enter point. */ uhci_polledp->uhci_polled_qh = uhci_alloc_queue_head(uhcip); if (uhci_polledp->uhci_polled_qh == NULL) { kmem_free(uhci_polledp, sizeof (uhci_polled_t)); return (USB_NO_RESOURCES); } /* * Insert a TD onto the queue head. */ if ((uhci_polled_insert_td_on_qh(uhci_polledp, uhci_polledp->uhci_polled_ph)) != USB_SUCCESS) { uhci_polledp->uhci_polled_qh->qh_flag = QUEUE_HEAD_FLAG_FREE; kmem_free(uhci_polledp, sizeof (uhci_polled_t)); return (USB_NO_RESOURCES); } console_info->uci_private = (usb_console_info_private_t)uhci_polledp; return (USB_SUCCESS); } /* * uhci_polled_fini: */ static int uhci_polled_fini(uhci_polled_t *uhci_polledp, uhci_state_t *uhcip) { uhci_td_t *td = uhci_polledp->uhci_polled_td; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* * Free the transfer wrapper */ uhci_free_tw(uhcip, td->tw); /* * Free the queue head and transfer descriptor allocated. */ uhci_polledp->uhci_polled_qh->qh_flag = QUEUE_HEAD_FLAG_FREE; uhci_polledp->uhci_polled_td->flag = TD_FLAG_FREE; /* * Deallocate the memory for the polled mode state structure. */ kmem_free(uhci_polledp, sizeof (uhci_polled_t)); return (USB_SUCCESS); } /* * uhci_polled_save_state: */ static void uhci_polled_save_state(uhci_polled_t *uhci_polledp) { int i; uhci_td_t *td, *polled_td; uhci_state_t *uhcip; usba_pipe_handle_data_t *ph; #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 (uhci_polledp->uhci_polled_flags & POLLED_INPUT_MODE_INUSE) { #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return; } uhcip = uhci_polledp->uhci_polled_uhcip; /* * Check if the number of keyboard reaches the max number we can * support in polled mode */ if (++ uhcip->uhci_polled_count > MAX_NUM_FOR_KEYBORAD) { #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return; } /* * Get the normal mode usb pipe handle. */ ph = (usba_pipe_handle_data_t *)uhci_polledp->uhci_polled_ph; /* * Only the first keyboard enter disable the interrutps, stop the * host controller processing and initialize the interrupt table. */ if (uhcip->uhci_polled_count == 1) { /* * Disable interrupts to prevent the interrupt handler getting * called while we are switing to POLLed mode. */ Set_OpReg16(USBINTR, DISABLE_ALL_INTRS); /* * Stop the HC controller from processing TD's */ Set_OpReg16(USBCMD, 0); /* * Save the current interrupt lattice and replace this lattice * with an lattice used in POLLED mode. We will restore lattice * back when we exit from the POLLED mode. */ for (i = 0; i < NUM_FRAME_LST_ENTRIES; i++) { uhcip->uhci_polled_save_IntTble[i] = uhcip->uhci_frame_lst_tablep[i]; } /* * Zero out the entire interrupt lattice tree. */ for (i = 0; i < NUM_FRAME_LST_ENTRIES; i++) { SetFL32(uhcip, uhcip->uhci_frame_lst_tablep[i], HC_END_OF_LIST); } } /* * Now, add the endpoint to the lattice that we will hang our * TD's off of. We (assume always) need to poll this device at * every 8 ms. */ for (i = uhcip->uhci_polled_count - 1; i < NUM_FRAME_LST_ENTRIES; i += MIN_LOW_SPEED_POLL_INTERVAL) { SetFL32(uhcip, uhcip->uhci_frame_lst_tablep[i], QH_PADDR(uhci_polledp->uhci_polled_qh) | HC_QUEUE_HEAD); } /* * Adjust the data toggle */ td = uhcip->uhci_outst_tds_head; while (td != NULL) { if (td->tw->tw_pipe_private->pp_pipe_handle == ph) { polled_td = uhci_polledp->uhci_polled_td; if (GetTD_status(uhcip, td) & UHCI_TD_ACTIVE) { SetTD_dtogg(uhcip, polled_td, GetTD_dtogg(uhcip, td)); } else { SetTD_dtogg(uhcip, polled_td, (GetTD_dtogg(uhcip, td) ^ 1)); uhcip->uhci_polled_flag = UHCI_POLLED_FLAG_TD_COMPL; } break; } td = td->outst_td_next; } /* * Only the first keyboard enter reset the frame number and start * the host controler processing. */ if (uhcip->uhci_polled_count == 1) { /* Set the frame number to zero */ Set_OpReg16(FRNUM, 0); /* * Start the Host controller processing */ Set_OpReg16(USBCMD, (USBCMD_REG_HC_RUN | USBCMD_REG_MAXPKT_64 | USBCMD_REG_CONFIG_FLAG)); } #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif } /* * uhci_polled_restore_state: */ static void uhci_polled_restore_state(uhci_polled_t *uhci_polledp) { int i; ushort_t real_data_toggle; uhci_td_t *td, *polled_td; uhci_state_t *uhcip; uhci_pipe_private_t *pp; #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif /* * If this flags is set, then we are still using this structure, * so don't restore any controller state information yet. */ if (uhci_polledp->uhci_polled_flags & POLLED_INPUT_MODE_INUSE) { #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return; } uhcip = uhci_polledp->uhci_polled_uhcip; uhcip->uhci_polled_count --; /* Just first leave keyboard entry turn off the controller */ if (Get_OpReg16(USBCMD)) { Set_OpReg16(USBCMD, 0x0); } /* Only the last leave keyboard entry restore the interrupt table */ if (uhcip->uhci_polled_count == 0) { /* * Replace the lattice */ for (i = 0; i < NUM_FRAME_LST_ENTRIES; i++) { uhcip->uhci_frame_lst_tablep[i] = uhcip->uhci_polled_save_IntTble[i]; } } /* * Adjust data toggle */ pp = (uhci_pipe_private_t *) uhci_polledp->uhci_polled_ph->p_hcd_private; polled_td = uhci_polledp->uhci_polled_td; real_data_toggle = (GetTD_status(uhcip, polled_td) & UHCI_TD_ACTIVE) ? GetTD_dtogg(uhcip, polled_td) : !GetTD_dtogg(uhcip, polled_td); td = uhcip->uhci_outst_tds_head; while (td != NULL) { if (td->tw->tw_pipe_private->pp_pipe_handle == uhci_polledp->uhci_polled_ph) { if (GetTD_status(uhcip, td) & UHCI_TD_ACTIVE) { SetTD_dtogg(uhcip, td, real_data_toggle); pp->pp_data_toggle = (real_data_toggle == 0) ? 1 : 0; } else { pp->pp_data_toggle = real_data_toggle; } } td = td->outst_td_next; } /* * Only the last leave keyboard entry enable the interrupts, * start Host controller processing. */ if (uhcip->uhci_polled_count == 0) { Set_OpReg16(USBINTR, ENABLE_ALL_INTRS); Set_OpReg16(USBCMD, (USBCMD_REG_HC_RUN | USBCMD_REG_MAXPKT_64 | USBCMD_REG_CONFIG_FLAG)); if (uhcip->uhci_polled_flag == UHCI_POLLED_FLAG_TD_COMPL) { uhcip->uhci_polled_flag = UHCI_POLLED_FLAG_TRUE; } } #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif } /* * uhci_polled_insert_td: * Initializes the transfer descriptor for polling and inserts on the * polled queue head. This will be put in action when entered in to * polled mode. */ static int uhci_polled_insert_td_on_qh(uhci_polled_t *uhci_polledp, usba_pipe_handle_data_t *ph) { uhci_td_t *td; uhci_state_t *uhcip = uhci_polledp->uhci_polled_uhcip; usb_ep_descr_t *eptd; uhci_trans_wrapper_t *tw; /* Create the transfer wrapper */ if ((tw = uhci_polled_create_tw(uhci_polledp->uhci_polled_uhcip)) == NULL) { return (USB_FAILURE); } /* Use the dummy TD allocated for the queue head */ td = uhci_polledp->uhci_polled_qh->td_tailp; bzero((char *)td, sizeof (uhci_td_t)); uhci_polledp->uhci_polled_td = td; td->tw = tw; td->flag = TD_FLAG_BUSY; SetTD32(uhcip, td->link_ptr, HC_END_OF_LIST); mutex_enter(&ph->p_usba_device->usb_mutex); eptd = &ph->p_ep; if (ph->p_usba_device->usb_port_status == USBA_LOW_SPEED_DEV) { SetTD_ls(uhcip, td, LOW_SPEED_DEVICE); } SetTD_c_err(uhcip, td, UHCI_MAX_ERR_COUNT); SetTD_mlen(uhcip, td, 0x7); SetTD_devaddr(uhcip, td, ph->p_usba_device->usb_addr); SetTD_endpt(uhcip, td, eptd->bEndpointAddress & END_POINT_ADDRESS_MASK); SetTD_PID(uhcip, td, PID_IN); SetTD32(uhcip, td->buffer_address, tw->tw_cookie.dmac_address); SetTD_ioc(uhcip, td, INTERRUPT_ON_COMPLETION); SetTD_status(uhcip, td, UHCI_TD_ACTIVE); mutex_exit(&ph->p_usba_device->usb_mutex); SetQH32(uhcip, uhci_polledp->uhci_polled_qh->element_ptr, TD_PADDR(td)); return (USB_SUCCESS); } /* * uhci_polled_create_wrapper_t: * Creates the transfer wrapper used in polled mode. */ static uhci_trans_wrapper_t * uhci_polled_create_tw(uhci_state_t *uhcip) { uint_t result, ccount; size_t real_length; uhci_trans_wrapper_t *tw; ddi_device_acc_attr_t dev_attr; /* Allocate space for the transfer wrapper */ if ((tw = kmem_zalloc(sizeof (uhci_trans_wrapper_t), KM_NOSLEEP)) == NULL) { return (NULL); } tw->tw_length = POLLED_RAW_BUF_SIZE; /* Allocate the DMA handle */ if ((result = ddi_dma_alloc_handle(uhcip->uhci_dip, &uhcip->uhci_dma_attr, DDI_DMA_DONTWAIT, 0, &tw->tw_dmahandle)) != DDI_SUCCESS) { kmem_free(tw, sizeof (uhci_trans_wrapper_t)); return (NULL); } 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 (uhci_trans_wrapper_t)); return (NULL); } /* 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 (uhci_trans_wrapper_t)); return (NULL); } /* 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 (uhci_trans_wrapper_t)); return (NULL); } return (tw); }