/* * 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" /* * Universal Host Controller Driver (UHCI) * * The UHCI driver is a driver which interfaces to the Universal * Serial Bus Driver (USBA) and the Host Controller (HC). The interface to * the Host Controller is defined by the UHCI. * This file contains misc functions. */ #include #include #include /* Globals */ extern uint_t uhci_td_pool_size; /* Num TDs */ extern uint_t uhci_qh_pool_size; /* Num QHs */ extern ushort_t uhci_tree_bottom_nodes[]; extern void *uhci_statep; /* function prototypes */ static void uhci_build_interrupt_lattice(uhci_state_t *uhcip); static int uhci_init_frame_lst_table(dev_info_t *dip, uhci_state_t *uhcip); static uint_t uhci_lattice_height(uint_t bandwidth); static uint_t uhci_lattice_parent(uint_t node); static uint_t uhci_leftmost_leaf(uint_t node, uint_t height); static uint_t uhci_compute_total_bandwidth(usb_ep_descr_t *endpoint, usb_port_status_t port_status); static int uhci_bandwidth_adjust(uhci_state_t *uhcip, usb_ep_descr_t *endpoint, usb_port_status_t port_status); static uhci_td_t *uhci_allocate_td_from_pool(uhci_state_t *uhcip); static void uhci_fill_in_td(uhci_state_t *uhcip, uhci_td_t *td, uhci_td_t *current_dummy, uint32_t buffer_address, size_t length, uhci_pipe_private_t *pp, uchar_t PID, usb_req_attrs_t attrs); static uhci_trans_wrapper_t *uhci_create_transfer_wrapper( uhci_state_t *uhcip, uhci_pipe_private_t *pp, size_t length, usb_flags_t usb_flags); static int uhci_create_setup_pkt(uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw); static void uhci_insert_ctrl_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp); static void uhci_remove_ctrl_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp); static void uhci_insert_intr_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp); static void uhci_remove_intr_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp); static void uhci_remove_bulk_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp); static void uhci_insert_bulk_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp); static void uhci_handle_bulk_td_errors(uhci_state_t *uhcip, uhci_td_t *td); static int uhci_alloc_memory_for_tds(uhci_state_t *uhcip, uint_t num_tds, uhci_bulk_isoc_xfer_t *info); static int uhci_handle_isoc_receive(uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw); static void uhci_delete_isoc_td(uhci_state_t *uhcip, uhci_td_t *td); #ifdef DEBUG static void uhci_print_td(uhci_state_t *uhcip, uhci_td_t *td); static void uhci_print_qh(uhci_state_t *uhcip, queue_head_t *qh); #endif /* * uhci_build_interrupt_lattice: * * Construct the interrupt lattice tree using static Queue Head pointers. * This interrupt lattice tree will have total of 63 queue heads and the * Host Controller (HC) processes queue heads every frame. */ static void uhci_build_interrupt_lattice(uhci_state_t *uhcip) { int half_list = NUM_INTR_QH_LISTS / 2; uint16_t i, j, k; uhci_td_t *sof_td, *isoc_td; uintptr_t addr; queue_head_t *list_array = uhcip->uhci_qh_pool_addr; queue_head_t *tmp_qh; frame_lst_table_t *frame_lst_tablep = uhcip->uhci_frame_lst_tablep; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_build_interrupt_lattice:"); /* * Reserve the first 63 queue head structures in the pool as static * queue heads & these are required for constructing interrupt * lattice tree. */ for (i = 0; i < NUM_INTR_QH_LISTS; i++) { SetQH32(uhcip, list_array[i].link_ptr, HC_END_OF_LIST); SetQH32(uhcip, list_array[i].element_ptr, HC_END_OF_LIST); list_array[i].qh_flag = QUEUE_HEAD_FLAG_STATIC; list_array[i].node = i; } /* Build the interrupt lattice tree */ for (i = 0; i < half_list - 1; i++) { /* * The next pointer in the host controller queue head * descriptor must contain an iommu address. Calculate * the offset into the cpu address and add this to the * starting iommu address. */ addr = QH_PADDR(&list_array[i]) | HC_QUEUE_HEAD; SetQH32(uhcip, list_array[2*i + 1].link_ptr, addr); SetQH32(uhcip, list_array[2*i + 2].link_ptr, addr); } /* * Initialize the interrupt list in the Frame list Table * so that it points to the bottom of the tree. */ for (i = 0, j = 0; i < pow_2(TREE_HEIGHT); i++) { addr = QH_PADDR(&list_array[half_list + i - 1]); for (k = 0; k < pow_2(VIRTUAL_TREE_HEIGHT); k++) { SetFL32(uhcip, frame_lst_tablep[uhci_tree_bottom_nodes[j++]], addr | HC_QUEUE_HEAD); } } /* * Create a controller and bulk Queue heads */ uhcip->uhci_ctrl_xfers_q_head = uhci_alloc_queue_head(uhcip); tmp_qh = uhcip->uhci_ctrl_xfers_q_tail = uhcip->uhci_ctrl_xfers_q_head; SetQH32(uhcip, list_array[0].link_ptr, (QH_PADDR(tmp_qh) | HC_QUEUE_HEAD)); uhcip->uhci_bulk_xfers_q_head = uhci_alloc_queue_head(uhcip); uhcip->uhci_bulk_xfers_q_tail = uhcip->uhci_bulk_xfers_q_head; SetQH32(uhcip, tmp_qh->link_ptr, (QH_PADDR(uhcip->uhci_bulk_xfers_q_head)|HC_QUEUE_HEAD)); SetQH32(uhcip, uhcip->uhci_bulk_xfers_q_head->link_ptr, HC_END_OF_LIST); /* * Add a dummy TD to the static queue head 0. THis is used * to generate an at the end of frame. */ sof_td = uhci_allocate_td_from_pool(uhcip); SetQH32(uhcip, list_array[0].element_ptr, TD_PADDR(sof_td) | HC_TD_HEAD); SetTD32(uhcip, sof_td->link_ptr, HC_END_OF_LIST); uhcip->uhci_sof_td = sof_td; /* * Add a dummy td that is used to generate an interrupt for * every 1024 frames. */ isoc_td = uhci_allocate_td_from_pool(uhcip); SetTD32(uhcip, isoc_td->link_ptr, HC_END_OF_LIST); uhcip->uhci_isoc_td = isoc_td; uhcip->uhci_isoc_qh = uhci_alloc_queue_head(uhcip); SetQH32(uhcip, uhcip->uhci_isoc_qh->link_ptr, GetFL32(uhcip, uhcip->uhci_frame_lst_tablep[MAX_FRAME_NUM])); SetQH32(uhcip, uhcip->uhci_isoc_qh->element_ptr, TD_PADDR(isoc_td)); SetFL32(uhcip, uhcip->uhci_frame_lst_tablep[MAX_FRAME_NUM], QH_PADDR(uhcip->uhci_isoc_qh) | HC_QUEUE_HEAD); } /* * uhci_allocate_pools: * Allocate the system memory for the Queue Heads Descriptor and * for the Transfer Descriptor (TD) pools. Both QH and TD structures * must be aligned to a 16 byte boundary. */ int uhci_allocate_pools(uhci_state_t *uhcip) { dev_info_t *dip = uhcip->uhci_dip; size_t real_length; int i, result; uint_t ccount; ddi_device_acc_attr_t dev_attr; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools:"); /* The host controller will be little endian */ 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 TD pool DMA handle */ if (ddi_dma_alloc_handle(dip, &uhcip->uhci_dma_attr, DDI_DMA_SLEEP, 0, &uhcip->uhci_td_pool_dma_handle) != DDI_SUCCESS) { return (USB_FAILURE); } /* Allocate the memory for the TD pool */ if (ddi_dma_mem_alloc(uhcip->uhci_td_pool_dma_handle, uhci_td_pool_size * sizeof (uhci_td_t), &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, 0, (caddr_t *)&uhcip->uhci_td_pool_addr, &real_length, &uhcip->uhci_td_pool_mem_handle)) { return (USB_FAILURE); } /* Map the TD pool into the I/O address space */ result = ddi_dma_addr_bind_handle(uhcip->uhci_td_pool_dma_handle, NULL, (caddr_t)uhcip->uhci_td_pool_addr, real_length, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &uhcip->uhci_td_pool_cookie, &ccount); bzero((void *)uhcip->uhci_td_pool_addr, uhci_td_pool_size * sizeof (uhci_td_t)); /* Process the result */ if (result == DDI_DMA_MAPPED) { /* The cookie count should be 1 */ if (ccount != 1) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools: More than 1 cookie"); return (USB_FAILURE); } } else { USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools: Result = %d", result); uhci_decode_ddi_dma_addr_bind_handle_result(uhcip, result); return (USB_FAILURE); } uhcip->uhci_dma_addr_bind_flag |= UHCI_TD_POOL_BOUND; /* Initialize the TD pool */ for (i = 0; i < uhci_td_pool_size; i++) { uhcip->uhci_td_pool_addr[i].flag = TD_FLAG_FREE; } /* Allocate the TD pool DMA handle */ if (ddi_dma_alloc_handle(dip, &uhcip->uhci_dma_attr, DDI_DMA_SLEEP, 0, &uhcip->uhci_qh_pool_dma_handle) != DDI_SUCCESS) { return (USB_FAILURE); } /* Allocate the memory for the QH pool */ if (ddi_dma_mem_alloc(uhcip->uhci_qh_pool_dma_handle, uhci_qh_pool_size * sizeof (queue_head_t), &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, 0, (caddr_t *)&uhcip->uhci_qh_pool_addr, &real_length, &uhcip->uhci_qh_pool_mem_handle) != DDI_SUCCESS) { return (USB_FAILURE); } result = ddi_dma_addr_bind_handle(uhcip->uhci_qh_pool_dma_handle, NULL, (caddr_t)uhcip->uhci_qh_pool_addr, real_length, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &uhcip->uhci_qh_pool_cookie, &ccount); /* Process the result */ if (result == DDI_DMA_MAPPED) { /* The cookie count should be 1 */ if (ccount != 1) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools: More than 1 cookie"); return (USB_FAILURE); } } else { uhci_decode_ddi_dma_addr_bind_handle_result(uhcip, result); return (USB_FAILURE); } uhcip->uhci_dma_addr_bind_flag |= UHCI_QH_POOL_BOUND; bzero((void *)uhcip->uhci_qh_pool_addr, uhci_qh_pool_size * sizeof (queue_head_t)); /* Initialize the QH pool */ for (i = 0; i < uhci_qh_pool_size; i ++) { uhcip->uhci_qh_pool_addr[i].qh_flag = QUEUE_HEAD_FLAG_FREE; } USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools: Completed"); return (USB_SUCCESS); } /* * uhci_free_pools: * Cleanup on attach failure or detach */ void uhci_free_pools(uhci_state_t *uhcip) { int i, flag, rval; uhci_td_t *td; uhci_trans_wrapper_t *tw; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_free_pools:"); if (uhcip->uhci_td_pool_addr && uhcip->uhci_td_pool_mem_handle) { for (i = 0; i < uhci_td_pool_size; i ++) { td = &uhcip->uhci_td_pool_addr[i]; flag = uhcip->uhci_td_pool_addr[i].flag; if ((flag != TD_FLAG_FREE) && (flag != TD_FLAG_DUMMY) && (td->tw != NULL)) { tw = td->tw; uhci_free_tw(uhcip, tw); } } if (uhcip->uhci_dma_addr_bind_flag & UHCI_TD_POOL_BOUND) { rval = ddi_dma_unbind_handle( uhcip->uhci_td_pool_dma_handle); ASSERT(rval == DDI_SUCCESS); } ddi_dma_mem_free(&uhcip->uhci_td_pool_mem_handle); } /* Free the TD pool */ if (uhcip->uhci_td_pool_dma_handle) { ddi_dma_free_handle(&uhcip->uhci_td_pool_dma_handle); } if (uhcip->uhci_qh_pool_addr && uhcip->uhci_qh_pool_mem_handle) { if (uhcip->uhci_dma_addr_bind_flag & UHCI_QH_POOL_BOUND) { rval = ddi_dma_unbind_handle( uhcip->uhci_qh_pool_dma_handle); ASSERT(rval == DDI_SUCCESS); } ddi_dma_mem_free(&uhcip->uhci_qh_pool_mem_handle); } /* Free the QH pool */ if (uhcip->uhci_qh_pool_dma_handle) { ddi_dma_free_handle(&uhcip->uhci_qh_pool_dma_handle); } /* Free the Frame list Table area */ if (uhcip->uhci_frame_lst_tablep && uhcip->uhci_flt_mem_handle) { if (uhcip->uhci_dma_addr_bind_flag & UHCI_FLA_POOL_BOUND) { rval = ddi_dma_unbind_handle( uhcip->uhci_flt_dma_handle); ASSERT(rval == DDI_SUCCESS); } ddi_dma_mem_free(&uhcip->uhci_flt_mem_handle); } if (uhcip->uhci_flt_dma_handle) { ddi_dma_free_handle(&uhcip->uhci_flt_dma_handle); } } /* * uhci_decode_ddi_dma_addr_bind_handle_result: * Process the return values of ddi_dma_addr_bind_handle() */ void uhci_decode_ddi_dma_addr_bind_handle_result(uhci_state_t *uhcip, int result) { char *msg; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_decode_ddi_dma_addr_bind_handle_result:"); switch (result) { case DDI_DMA_PARTIAL_MAP: msg = "Partial transfers not allowed"; break; case DDI_DMA_INUSE: msg = "Handle is in use"; break; case DDI_DMA_NORESOURCES: msg = "No resources"; break; case DDI_DMA_NOMAPPING: msg = "No mapping"; break; case DDI_DMA_TOOBIG: msg = "Object is too big"; break; default: msg = "Unknown dma error"; } USB_DPRINTF_L4(PRINT_MASK_ALL, uhcip->uhci_log_hdl, "%s", msg); } /* * uhci_init_ctlr: * Initialize the Host Controller (HC). */ int uhci_init_ctlr(uhci_state_t *uhcip) { dev_info_t *dip = uhcip->uhci_dip; uint_t cmd_reg; uint_t frame_base_addr; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_init_ctlr:"); /* * When USB legacy mode is enabled, the BIOS manages the USB keyboard * attached to the UHCI controller. It has been observed that some * times the BIOS does not clear the interrupts in the legacy mode * register in the PCI configuration space. So, disable the SMI intrs * and route the intrs to PIRQD here. */ pci_config_put16(uhcip->uhci_config_handle, LEGACYMODE_REG_OFFSET, LEGACYMODE_REG_INIT_VALUE); /* * Disable all the interrupts. */ Set_OpReg16(USBINTR, DISABLE_ALL_INTRS); mutex_enter(&uhcip->uhci_int_mutex); cmd_reg = Get_OpReg16(USBCMD); cmd_reg &= (~USBCMD_REG_HC_RUN); /* Stop the controller */ Set_OpReg16(USBCMD, cmd_reg); /* Reset the host controller */ Set_OpReg16(USBCMD, USBCMD_REG_GBL_RESET); /* Wait 10ms for reset to complete */ mutex_exit(&uhcip->uhci_int_mutex); delay(drv_usectohz(UHCI_RESET_DELAY)); mutex_enter(&uhcip->uhci_int_mutex); Set_OpReg16(USBCMD, 0); /* Set the frame number to zero */ Set_OpReg16(FRNUM, 0); /* Initialize the Frame list base address area */ if (uhci_init_frame_lst_table(dip, uhcip) != USB_SUCCESS) { mutex_exit(&uhcip->uhci_int_mutex); return (USB_FAILURE); } /* Save the contents of the Frame Interval Registers */ uhcip->uhci_frame_interval = Get_OpReg8(SOFMOD); frame_base_addr = uhcip->uhci_flt_cookie.dmac_address; /* Set the Frame list base address */ Set_OpReg32(FRBASEADD, frame_base_addr); /* * Begin sending SOFs * Set the Host Controller Functional State to Operational */ cmd_reg = Get_OpReg16(USBCMD); cmd_reg |= (USBCMD_REG_HC_RUN | USBCMD_REG_MAXPKT_64 | USBCMD_REG_CONFIG_FLAG); Set_OpReg16(USBCMD, cmd_reg); mutex_exit(&uhcip->uhci_int_mutex); /* * Verify the Command and interrupt enable registers, * a sanity check whether actually initialized or not */ cmd_reg = Get_OpReg16(USBCMD); if (!(cmd_reg & (USBCMD_REG_HC_RUN | USBCMD_REG_MAXPKT_64 | USBCMD_REG_CONFIG_FLAG))) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_init_ctlr: Controller initialization failed"); return (USB_FAILURE); } /* * Set the ioc bit of the isoc intr td. This enables * the generation of an interrupt for every 1024 frames. */ SetTD_ioc(uhcip, uhcip->uhci_isoc_td, 1); /* Set the flag that uhci controller has been initialized. */ uhcip->uhci_ctlr_init_flag = B_TRUE; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_init_ctlr: Completed"); return (USB_SUCCESS); } /* * uhci_uninit_ctlr: * uninitialize the Host Controller (HC). */ void uhci_uninit_ctlr(uhci_state_t *uhcip) { if (uhcip->uhci_regs_handle) { /* Disable all the interrupts. */ Set_OpReg16(USBINTR, DISABLE_ALL_INTRS); /* Complete the current transaction and then halt. */ Set_OpReg16(USBCMD, 0); /* Wait for sometime */ mutex_exit(&uhcip->uhci_int_mutex); delay(drv_usectohz(UHCI_TIMEWAIT)); mutex_enter(&uhcip->uhci_int_mutex); } } /* * uhci_map_regs: * The Host Controller (HC) contains a set of on-chip operational * registers and which should be mapped into a non-cacheable * portion of the system addressable space. */ int uhci_map_regs(uhci_state_t *uhcip) { dev_info_t *dip = uhcip->uhci_dip; int index; uint32_t regs_prop_len; int32_t *regs_list; uint16_t command_reg; ddi_device_acc_attr_t attr; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_map_regs:"); /* The host controller will be little endian */ attr.devacc_attr_version = DDI_DEVICE_ATTR_V0; attr.devacc_attr_endian_flags = DDI_STRUCTURE_LE_ACC; attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC; if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, uhcip->uhci_dip, DDI_PROP_DONTPASS, "reg", ®s_list, ®s_prop_len) != DDI_PROP_SUCCESS) { return (USB_FAILURE); } for (index = 0; index * 5 < regs_prop_len; index++) { if (regs_list[index * 5] & UHCI_PROP_MASK) { break; } } /* * Deallocate the memory allocated by the ddi_prop_lookup_int_array */ ddi_prop_free(regs_list); if (index * 5 >= regs_prop_len) { return (USB_FAILURE); } /* Map in operational registers */ if (ddi_regs_map_setup(dip, index, (caddr_t *)&uhcip->uhci_regsp, 0, sizeof (hc_regs_t), &attr, &uhcip->uhci_regs_handle) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "ddi_regs_map_setup: failed"); return (USB_FAILURE); } if (pci_config_setup(dip, &uhcip->uhci_config_handle) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_map_regs: Config error"); return (USB_FAILURE); } /* Make sure Memory Access Enable and Master Enable are set */ command_reg = pci_config_get16(uhcip->uhci_config_handle, PCI_CONF_COMM); if (!(command_reg & (PCI_COMM_MAE | PCI_COMM_ME))) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_map_regs: No MAE/ME"); } command_reg |= PCI_COMM_MAE | PCI_COMM_ME; pci_config_put16(uhcip->uhci_config_handle, PCI_CONF_COMM, command_reg); /* * Check whether I/O base address is configured and enabled. */ if (!(command_reg & PCI_COMM_IO)) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "I/O Base address access disabled"); return (USB_FAILURE); } /* * Get the IO base address of the controller */ uhcip->uhci_iobase = (pci_config_get16(uhcip->uhci_config_handle, PCI_CONF_IOBASE) & PCI_CONF_IOBASE_MASK); USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_map_regs: Completed"); return (USB_SUCCESS); } void uhci_unmap_regs(uhci_state_t *uhcip) { /* Unmap the UHCI registers */ if (uhcip->uhci_regs_handle) { /* Reset the host controller */ Set_OpReg16(USBCMD, USBCMD_REG_GBL_RESET); ddi_regs_map_free(&uhcip->uhci_regs_handle); } if (uhcip->uhci_config_handle) { pci_config_teardown(&uhcip->uhci_config_handle); } } /* * uhci_set_dma_attributes: * Set the limits in the DMA attributes structure. Most of the values used * in the DMA limit structres are the default values as specified by the * Writing PCI device drivers document. */ void uhci_set_dma_attributes(uhci_state_t *uhcip) { USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_set_dma_attributes:"); /* Initialize the DMA attributes */ uhcip->uhci_dma_attr.dma_attr_version = DMA_ATTR_V0; uhcip->uhci_dma_attr.dma_attr_addr_lo = 0x00000000ull; uhcip->uhci_dma_attr.dma_attr_addr_hi = 0xfffffff0ull; /* 32 bit addressing */ uhcip->uhci_dma_attr.dma_attr_count_max = 0xffffffull; /* * Setting the dam_att_align to 512, some times fails the * binding handle. I dont know why ? But setting to 16 will * be right for our case (16 byte alignment required per * UHCI spec for TD descriptors). */ /* 16 byte alignment */ uhcip->uhci_dma_attr.dma_attr_align = 0x10; /* * Since PCI specification is byte alignment, the * burstsize field should be set to 1 for PCI devices. */ uhcip->uhci_dma_attr.dma_attr_burstsizes = 0x1; uhcip->uhci_dma_attr.dma_attr_minxfer = 0x1; uhcip->uhci_dma_attr.dma_attr_maxxfer = 0xffffffull; uhcip->uhci_dma_attr.dma_attr_seg = 0xffffffffull; uhcip->uhci_dma_attr.dma_attr_sgllen = 1; uhcip->uhci_dma_attr.dma_attr_granular = 1; uhcip->uhci_dma_attr.dma_attr_flags = 0; } uint_t pow_2(uint_t x) { return ((x == 0) ? 1 : (1 << x)); } uint_t log_2(uint_t x) { int ret_val = 0; while (x != 1) { ret_val++; x = x >> 1; } return (ret_val); } /* * uhci_obtain_state: */ uhci_state_t * uhci_obtain_state(dev_info_t *dip) { int instance = ddi_get_instance(dip); uhci_state_t *state = ddi_get_soft_state(uhci_statep, instance); ASSERT(state != NULL); return (state); } /* * uhci_alloc_hcdi_ops: * The HCDI interfaces or entry points are the software interfaces used by * the Universal Serial Bus Driver (USBA) to access the services of the * Host Controller Driver (HCD). During HCD initialization, inform USBA * about all available HCDI interfaces or entry points. */ usba_hcdi_ops_t * uhci_alloc_hcdi_ops(uhci_state_t *uhcip) { usba_hcdi_ops_t *hcdi_ops; USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_alloc_hcdi_ops:"); hcdi_ops = usba_alloc_hcdi_ops(); hcdi_ops->usba_hcdi_pipe_open = uhci_hcdi_pipe_open; hcdi_ops->usba_hcdi_pipe_close = uhci_hcdi_pipe_close; hcdi_ops->usba_hcdi_pipe_reset = uhci_hcdi_pipe_reset; hcdi_ops->usba_hcdi_pipe_ctrl_xfer = uhci_hcdi_pipe_ctrl_xfer; hcdi_ops->usba_hcdi_pipe_bulk_xfer = uhci_hcdi_pipe_bulk_xfer; hcdi_ops->usba_hcdi_pipe_intr_xfer = uhci_hcdi_pipe_intr_xfer; hcdi_ops->usba_hcdi_pipe_isoc_xfer = uhci_hcdi_pipe_isoc_xfer; hcdi_ops->usba_hcdi_bulk_transfer_size = uhci_hcdi_bulk_transfer_size; hcdi_ops->usba_hcdi_pipe_stop_intr_polling = uhci_hcdi_pipe_stop_intr_polling; hcdi_ops->usba_hcdi_pipe_stop_isoc_polling = uhci_hcdi_pipe_stop_isoc_polling; hcdi_ops->usba_hcdi_get_current_frame_number = uhci_hcdi_get_current_frame_number; hcdi_ops->usba_hcdi_get_max_isoc_pkts = uhci_hcdi_get_max_isoc_pkts; hcdi_ops->usba_hcdi_console_input_init = uhci_hcdi_polled_input_init; hcdi_ops->usba_hcdi_console_input_enter = uhci_hcdi_polled_input_enter; hcdi_ops->usba_hcdi_console_read = uhci_hcdi_polled_read; hcdi_ops->usba_hcdi_console_input_exit = uhci_hcdi_polled_input_exit; hcdi_ops->usba_hcdi_console_input_fini = uhci_hcdi_polled_input_fini; return (hcdi_ops); } /* * uhci_init_frame_lst_table : * Allocate the system memory and initialize Host Controller * Frame list table area The starting of the Frame list Table * area must be 4096 byte aligned. */ static int uhci_init_frame_lst_table(dev_info_t *dip, uhci_state_t *uhcip) { int result; uint_t ccount; size_t real_length; ddi_device_acc_attr_t dev_attr; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_init_frame_lst_table:"); /* The host controller will be little endian */ 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; /* 4K alignment required */ uhcip->uhci_dma_attr.dma_attr_align = 0x1000; /* Create space for the HCCA block */ if (ddi_dma_alloc_handle(dip, &uhcip->uhci_dma_attr, DDI_DMA_SLEEP, 0, &uhcip->uhci_flt_dma_handle) != DDI_SUCCESS) { return (USB_FAILURE); } /* Reset to default 16 bytes */ uhcip->uhci_dma_attr.dma_attr_align = 0x10; if (ddi_dma_mem_alloc(uhcip->uhci_flt_dma_handle, SIZE_OF_FRAME_LST_TABLE, &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, 0, (caddr_t *)&uhcip->uhci_frame_lst_tablep, &real_length, &uhcip->uhci_flt_mem_handle)) { return (USB_FAILURE); } /* Map the whole Frame list base area into the I/O address space */ result = ddi_dma_addr_bind_handle(uhcip->uhci_flt_dma_handle, NULL, (caddr_t)uhcip->uhci_frame_lst_tablep, real_length, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &uhcip->uhci_flt_cookie, &ccount); if (result == DDI_DMA_MAPPED) { /* The cookie count should be 1 */ if (ccount != 1) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_init_frame_list_table: More than 1 cookie"); return (USB_FAILURE); } } else { uhci_decode_ddi_dma_addr_bind_handle_result(uhcip, result); return (USB_FAILURE); } uhcip->uhci_dma_addr_bind_flag |= UHCI_FLA_POOL_BOUND; bzero((void *)uhcip->uhci_frame_lst_tablep, real_length); /* Initialize the interrupt lists */ uhci_build_interrupt_lattice(uhcip); return (USB_SUCCESS); } /* * uhci_alloc_queue_head: * Allocate a queue head */ queue_head_t * uhci_alloc_queue_head(uhci_state_t *uhcip) { int index; uhci_td_t *dummy_td; queue_head_t *queue_head; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_alloc_queue_head"); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Allocate a dummy td first. */ if ((dummy_td = uhci_allocate_td_from_pool(uhcip)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_alloc_queue_head: allocate td from pool failed"); return (NULL); } /* * The first 63 queue heads in the Queue Head (QH) * buffer pool are reserved for building interrupt lattice * tree. Search for a blank Queue head in the QH buffer pool. */ for (index = NUM_STATIC_NODES; index < uhci_qh_pool_size; index++) { if (uhcip->uhci_qh_pool_addr[index].qh_flag == QUEUE_HEAD_FLAG_FREE) { break; } } USB_DPRINTF_L3(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_alloc_queue_head: Allocated %d", index); if (index == uhci_qh_pool_size) { USB_DPRINTF_L2(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_alloc_queue_head: All QH exhausted"); /* Free the dummy td allocated for this qh. */ dummy_td->flag = TD_FLAG_FREE; return (NULL); } queue_head = &uhcip->uhci_qh_pool_addr[index]; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_alloc_queue_head: Allocated address 0x%p", queue_head); bzero((void *)queue_head, sizeof (queue_head_t)); SetQH32(uhcip, queue_head->link_ptr, HC_END_OF_LIST); SetQH32(uhcip, queue_head->element_ptr, HC_END_OF_LIST); queue_head->prev_qh = NULL; queue_head->qh_flag = QUEUE_HEAD_FLAG_BUSY; bzero((char *)dummy_td, sizeof (uhci_td_t)); queue_head->td_tailp = dummy_td; SetQH32(uhcip, queue_head->element_ptr, TD_PADDR(dummy_td)); return (queue_head); } /* * uhci_allocate_bandwidth: * Figure out whether or not this interval may be supported. Return * the index into the lattice if it can be supported. Return * allocation failure if it can not be supported. */ int uhci_allocate_bandwidth( uhci_state_t *uhcip, usba_pipe_handle_data_t *pipe_handle, uint_t *node) { int bandwidth; /* Requested bandwidth */ uint_t min, min_index; uint_t i; uint_t height; /* Bandwidth's height in the tree */ uint_t leftmost; uint_t length; uint32_t paddr; queue_head_t *tmp_qh; usb_ep_descr_t *endpoint = &pipe_handle->p_ep; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* * Calculate the length in bytes of a transaction on this * periodic endpoint. */ mutex_enter(&pipe_handle->p_usba_device->usb_mutex); length = uhci_compute_total_bandwidth(endpoint, pipe_handle->p_usba_device->usb_port_status); mutex_exit(&pipe_handle->p_usba_device->usb_mutex); /* * If the length in bytes plus the allocated bandwidth exceeds * the maximum, return bandwidth allocation failure. */ if ((length + uhcip->uhci_bandwidth_intr_min + uhcip->uhci_bandwidth_isoch_sum) > (MAX_PERIODIC_BANDWIDTH)) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_allocate_bandwidth: " "Reached maximum bandwidth value and cannot allocate " "bandwidth for a given Interrupt/Isoch endpoint"); return (USB_NO_BANDWIDTH); } /* * ISOC xfers are not supported at this point type */ if (UHCI_XFER_TYPE(endpoint) == USB_EP_ATTR_ISOCH) { uhcip->uhci_bandwidth_isoch_sum += length; return (USB_SUCCESS); } /* * This is an interrupt endpoint. * Adjust bandwidth to be a power of 2 */ mutex_enter(&pipe_handle->p_usba_device->usb_mutex); bandwidth = uhci_bandwidth_adjust(uhcip, endpoint, pipe_handle->p_usba_device->usb_port_status); mutex_exit(&pipe_handle->p_usba_device->usb_mutex); /* * If this bandwidth can't be supported, * return allocation failure. */ if (bandwidth == USB_FAILURE) { return (USB_FAILURE); } USB_DPRINTF_L3(PRINT_MASK_BW, uhcip->uhci_log_hdl, "The new bandwidth is %d", bandwidth); /* Find the leaf with the smallest allocated bandwidth */ min_index = 0; min = uhcip->uhci_bandwidth[0]; for (i = 1; i < NUM_FRAME_LST_ENTRIES; i++) { if (uhcip->uhci_bandwidth[i] < min) { min_index = i; min = uhcip->uhci_bandwidth[i]; } } USB_DPRINTF_L3(PRINT_MASK_BW, uhcip->uhci_log_hdl, "The leaf with minimal bandwidth %d, " "The smallest bandwidth %d", min_index, min); /* * Find the index into the lattice given the * leaf with the smallest allocated bandwidth. */ height = uhci_lattice_height(bandwidth); USB_DPRINTF_L3(PRINT_MASK_BW, uhcip->uhci_log_hdl, "The height is %d", height); *node = uhci_tree_bottom_nodes[min_index]; /* check if there are isocs TDs scheduled for this frame */ if (uhcip->uhci_isoc_q_tailp[*node]) { paddr = (uhcip->uhci_isoc_q_tailp[*node]->link_ptr & FRAME_LST_PTR_MASK); } else { paddr = (uhcip->uhci_frame_lst_tablep[*node] & FRAME_LST_PTR_MASK); } tmp_qh = QH_VADDR(paddr); *node = tmp_qh->node; for (i = 0; i < height; i++) { *node = uhci_lattice_parent(*node); } USB_DPRINTF_L3(PRINT_MASK_BW, uhcip->uhci_log_hdl, "The real node is %d", *node); /* * Find the leftmost leaf in the subtree specified by the node. */ leftmost = uhci_leftmost_leaf(*node, height); USB_DPRINTF_L3(PRINT_MASK_BW, uhcip->uhci_log_hdl, "Leftmost %d", leftmost); for (i = leftmost; i < leftmost + (NUM_FRAME_LST_ENTRIES/bandwidth); i ++) { if ((length + uhcip->uhci_bandwidth_isoch_sum + uhcip->uhci_bandwidth[i]) > MAX_PERIODIC_BANDWIDTH) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_allocate_bandwidth: " "Reached maximum bandwidth value and cannot " "allocate bandwidth for Interrupt endpoint"); return (USB_NO_BANDWIDTH); } } /* * All the leaves for this node must be updated with the bandwidth. */ for (i = leftmost; i < leftmost + (NUM_FRAME_LST_ENTRIES/bandwidth); i ++) { uhcip->uhci_bandwidth[i] += length; } /* Find the leaf with the smallest allocated bandwidth */ min_index = 0; min = uhcip->uhci_bandwidth[0]; for (i = 1; i < NUM_FRAME_LST_ENTRIES; i++) { if (uhcip->uhci_bandwidth[i] < min) { min_index = i; min = uhcip->uhci_bandwidth[i]; } } /* Save the minimum for later use */ uhcip->uhci_bandwidth_intr_min = min; return (USB_SUCCESS); } /* * uhci_deallocate_bandwidth: * Deallocate bandwidth for the given node in the lattice * and the length of transfer. */ void uhci_deallocate_bandwidth(uhci_state_t *uhcip, usba_pipe_handle_data_t *pipe_handle) { uint_t bandwidth; uint_t height; uint_t leftmost; uint_t i; uint_t min; usb_ep_descr_t *endpoint = &pipe_handle->p_ep; uint_t node, length; uhci_pipe_private_t *pp = (uhci_pipe_private_t *)pipe_handle->p_hcd_private; /* This routine is protected by the uhci_int_mutex */ ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Obtain the length */ mutex_enter(&pipe_handle->p_usba_device->usb_mutex); length = uhci_compute_total_bandwidth(endpoint, pipe_handle->p_usba_device->usb_port_status); mutex_exit(&pipe_handle->p_usba_device->usb_mutex); /* * If this is an isochronous endpoint, just delete endpoint's * bandwidth from the total allocated isochronous bandwidth. */ if (UHCI_XFER_TYPE(endpoint) == USB_EP_ATTR_ISOCH) { uhcip->uhci_bandwidth_isoch_sum -= length; return; } /* Obtain the node */ node = pp->pp_node; /* Adjust bandwidth to be a power of 2 */ mutex_enter(&pipe_handle->p_usba_device->usb_mutex); bandwidth = uhci_bandwidth_adjust(uhcip, endpoint, pipe_handle->p_usba_device->usb_port_status); mutex_exit(&pipe_handle->p_usba_device->usb_mutex); /* Find the height in the tree */ height = uhci_lattice_height(bandwidth); /* * Find the leftmost leaf in the subtree specified by the node */ leftmost = uhci_leftmost_leaf(node, height); /* Delete the bandwith from the appropriate lists */ for (i = leftmost; i < leftmost + (NUM_FRAME_LST_ENTRIES/bandwidth); i ++) { uhcip->uhci_bandwidth[i] -= length; } min = uhcip->uhci_bandwidth[0]; /* Recompute the minimum */ for (i = 1; i < NUM_FRAME_LST_ENTRIES; i++) { if (uhcip->uhci_bandwidth[i] < min) { min = uhcip->uhci_bandwidth[i]; } } /* Save the minimum for later use */ uhcip->uhci_bandwidth_intr_min = min; } /* * uhci_compute_total_bandwidth: * * Given a periodic endpoint (interrupt or isochronous) determine the total * bandwidth for one transaction. The UHCI host controller traverses the * endpoint descriptor lists on a first-come-first-serve basis. When the HC * services an endpoint, only a single transaction attempt is made. The HC * moves to the next Endpoint Descriptor after the first transaction attempt * rather than finishing the entire Transfer Descriptor. Therefore, when a * Transfer Descriptor is inserted into the lattice, we will only count the * number of bytes for one transaction. * * The following are the formulas used for calculating bandwidth in terms * bytes and it is for the single USB full speed and low speed transaction * respectively. The protocol overheads will be different for each of type * of USB transfer and all these formulas & protocol overheads are derived * from the 5.9.3 section of USB Specification & with the help of Bandwidth * Analysis white paper which is posted on the USB developer forum. * * Full-Speed: * Protocol overhead + ((MaxPacketSize * 7)/6 ) + Host_Delay * * Low-Speed: * Protocol overhead + Hub LS overhead + * (Low-Speed clock * ((MaxPacketSize * 7)/6 )) + Host_Delay */ static uint_t uhci_compute_total_bandwidth(usb_ep_descr_t *endpoint, usb_port_status_t port_status) { uint_t bandwidth; ushort_t MaxPacketSize = endpoint->wMaxPacketSize; /* Add Host Controller specific delay to required bandwidth */ bandwidth = HOST_CONTROLLER_DELAY; /* Add bit-stuffing overhead */ MaxPacketSize = (ushort_t)((MaxPacketSize * 7) / 6); /* Low Speed interrupt transaction */ if (port_status == USBA_LOW_SPEED_DEV) { /* Low Speed interrupt transaction */ bandwidth += (LOW_SPEED_PROTO_OVERHEAD + HUB_LOW_SPEED_PROTO_OVERHEAD + (LOW_SPEED_CLOCK * MaxPacketSize)); } else { /* Full Speed transaction */ bandwidth += MaxPacketSize; if (UHCI_XFER_TYPE(endpoint) == USB_EP_ATTR_INTR) { /* Full Speed interrupt transaction */ bandwidth += FS_NON_ISOC_PROTO_OVERHEAD; } else { /* Isochronus and input transaction */ if (UHCI_XFER_DIR(endpoint) == USB_EP_DIR_IN) { bandwidth += FS_ISOC_INPUT_PROTO_OVERHEAD; } else { /* Isochronus and output transaction */ bandwidth += FS_ISOC_OUTPUT_PROTO_OVERHEAD; } } } return (bandwidth); } /* * uhci_bandwidth_adjust: */ static int uhci_bandwidth_adjust( uhci_state_t *uhcip, usb_ep_descr_t *endpoint, usb_port_status_t port_status) { int i = 0; uint_t interval; /* * Get the polling interval from the endpoint descriptor */ interval = endpoint->bInterval; /* * The bInterval value in the endpoint descriptor can range * from 1 to 255ms. The interrupt lattice has 32 leaf nodes, * and the host controller cycles through these nodes every * 32ms. The longest polling interval that the controller * supports is 32ms. */ /* * Return an error if the polling interval is less than 1ms * and greater than 255ms */ if ((interval < MIN_POLL_INTERVAL) || (interval > MAX_POLL_INTERVAL)) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_bandwidth_adjust: Endpoint's poll interval must be " "between %d and %d ms", MIN_POLL_INTERVAL, MAX_POLL_INTERVAL); return (USB_FAILURE); } /* * According USB Specifications, a full-speed endpoint can * specify a desired polling interval 1ms to 255ms and a low * speed endpoints are limited to specifying only 10ms to * 255ms. But some old keyboards & mice uses polling interval * of 8ms. For compatibility purpose, we are using polling * interval between 8ms & 255ms for low speed endpoints. */ if ((port_status == USBA_LOW_SPEED_DEV) && (interval < MIN_LOW_SPEED_POLL_INTERVAL)) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_bandwidth_adjust: Low speed endpoint's poll interval " "must be >= %d ms, adjusted", MIN_LOW_SPEED_POLL_INTERVAL); interval = MIN_LOW_SPEED_POLL_INTERVAL; } /* * If polling interval is greater than 32ms, * adjust polling interval equal to 32ms. */ if (interval > 32) { interval = 32; } /* * Find the nearest power of 2 that's less * than interval. */ while ((pow_2(i)) <= interval) { i++; } return (pow_2((i - 1))); } /* * uhci_lattice_height: * Given the requested bandwidth, find the height in the tree at * which the nodes for this bandwidth fall. The height is measured * as the number of nodes from the leaf to the level specified by * bandwidth The root of the tree is at height TREE_HEIGHT. */ static uint_t uhci_lattice_height(uint_t bandwidth) { return (TREE_HEIGHT - (log_2(bandwidth))); } static uint_t uhci_lattice_parent(uint_t node) { return (((node % 2) == 0) ? ((node/2) - 1) : (node/2)); } /* * uhci_leftmost_leaf: * Find the leftmost leaf in the subtree specified by the node. * Height refers to number of nodes from the bottom of the tree * to the node, including the node. */ static uint_t uhci_leftmost_leaf(uint_t node, uint_t height) { node = pow_2(height + VIRTUAL_TREE_HEIGHT) * (node+1) - NUM_FRAME_LST_ENTRIES; return (node); } /* * uhci_insert_qh: * Add the Queue Head (QH) into the Host Controller's (HC) * appropriate queue head list. */ void uhci_insert_qh(uhci_state_t *uhcip, usba_pipe_handle_data_t *ph) { uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_qh:"); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); switch (UHCI_XFER_TYPE(&ph->p_ep)) { case USB_EP_ATTR_CONTROL: uhci_insert_ctrl_qh(uhcip, pp); break; case USB_EP_ATTR_BULK: uhci_insert_bulk_qh(uhcip, pp); break; case USB_EP_ATTR_INTR: uhci_insert_intr_qh(uhcip, pp); break; case USB_EP_ATTR_ISOCH: USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_insert_qh: Illegal request"); break; } } /* * uhci_insert_ctrl_qh: * Insert a control QH into the Host Controller's (HC) control QH list. */ static void uhci_insert_ctrl_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { queue_head_t *qh = pp->pp_qh; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_ctrl_qh:"); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); if (uhcip->uhci_ctrl_xfers_q_head == uhcip->uhci_ctrl_xfers_q_tail) { uhcip->uhci_ctrl_xfers_q_head->prev_qh = UHCI_INVALID_PTR; } SetQH32(uhcip, qh->link_ptr, GetQH32(uhcip, uhcip->uhci_ctrl_xfers_q_tail->link_ptr)); qh->prev_qh = uhcip->uhci_ctrl_xfers_q_tail; SetQH32(uhcip, uhcip->uhci_ctrl_xfers_q_tail->link_ptr, QH_PADDR(qh) | HC_QUEUE_HEAD); uhcip->uhci_ctrl_xfers_q_tail = qh; } /* * uhci_insert_bulk_qh: * Insert a bulk QH into the Host Controller's (HC) bulk QH list. */ static void uhci_insert_bulk_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { queue_head_t *qh = pp->pp_qh; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_bulk_qh:"); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); if (uhcip->uhci_bulk_xfers_q_head == uhcip->uhci_bulk_xfers_q_tail) { uhcip->uhci_bulk_xfers_q_head->prev_qh = UHCI_INVALID_PTR; } else if (uhcip->uhci_bulk_xfers_q_head->link_ptr == uhcip->uhci_bulk_xfers_q_tail->link_ptr) { /* If there is already a loop, we should keep the loop. */ qh->link_ptr = uhcip->uhci_bulk_xfers_q_tail->link_ptr; } qh->prev_qh = uhcip->uhci_bulk_xfers_q_tail; SetQH32(uhcip, uhcip->uhci_bulk_xfers_q_tail->link_ptr, QH_PADDR(qh) | HC_QUEUE_HEAD); uhcip->uhci_bulk_xfers_q_tail = qh; } /* * uhci_insert_intr_qh: * Insert a periodic Queue head i.e Interrupt queue head into the * Host Controller's (HC) interrupt lattice tree. */ static void uhci_insert_intr_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { uint_t node = pp->pp_node; /* The appropriate node was */ /* found during the opening */ /* of the pipe. */ queue_head_t *qh = pp->pp_qh; queue_head_t *next_lattice_qh, *lattice_qh; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_qh:"); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Find the lattice queue head */ lattice_qh = &uhcip->uhci_qh_pool_addr[node]; next_lattice_qh = QH_VADDR(GetQH32(uhcip, lattice_qh->link_ptr) & QH_LINK_PTR_MASK); next_lattice_qh->prev_qh = qh; qh->link_ptr = lattice_qh->link_ptr; qh->prev_qh = lattice_qh; SetQH32(uhcip, lattice_qh->link_ptr, QH_PADDR(qh) | HC_QUEUE_HEAD); pp->pp_data_toggle = 0; } /* * uhci_insert_intr_td: * Create a TD and a data buffer for an interrupt endpoint. */ int uhci_insert_intr_td( uhci_state_t *uhcip, usba_pipe_handle_data_t *ph, usb_intr_req_t *req, usb_flags_t flags) { int error, pipe_dir; uint_t length, mps; uint32_t buf_addr; uhci_td_t *tmp_td; usb_intr_req_t *intr_reqp; uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; uhci_trans_wrapper_t *tw; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_td: req: 0x%p", req); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Get the interrupt pipe direction */ pipe_dir = UHCI_XFER_DIR(&ph->p_ep); /* Get the current interrupt request pointer */ if (req) { length = req->intr_len; } else { ASSERT(pipe_dir == USB_EP_DIR_IN); length = (pp->pp_client_periodic_in_reqp) ? (((usb_intr_req_t *)pp-> pp_client_periodic_in_reqp)->intr_len) : ph->p_ep.wMaxPacketSize; } /* Check the size of interrupt request */ if (length > UHCI_MAX_TD_XFER_SIZE) { /* the length shouldn't exceed 8K */ USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_td: Intr request size 0x%lx is " "more than 0x%x", length, UHCI_MAX_TD_XFER_SIZE); return (USB_INVALID_REQUEST); } USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_td: length: 0x%lx", length); /* Allocate a transaction wrapper */ if ((tw = uhci_create_transfer_wrapper(uhcip, pp, length, flags)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_td: TW allocation failed"); return (USB_NO_RESOURCES); } /* * Initialize the callback and any callback * data for when the td completes. */ tw->tw_handle_td = uhci_handle_intr_td; tw->tw_handle_callback_value = NULL; tw->tw_direction = (pipe_dir == USB_EP_DIR_OUT) ? PID_OUT : PID_IN; tw->tw_curr_xfer_reqp = (usb_opaque_t)req; /* * If it is an Interrupt IN request and interrupt request is NULL, * allocate the usb interrupt request structure for the current * interrupt polling request. */ if (tw->tw_direction == PID_IN) { if ((error = uhci_allocate_periodic_in_resource(uhcip, pp, tw, flags)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_td: Interrupt request structure " "allocation failed"); return (error); } } intr_reqp = (usb_intr_req_t *)tw->tw_curr_xfer_reqp; ASSERT(tw->tw_curr_xfer_reqp != NULL); tw->tw_timeout_cnt = (intr_reqp->intr_attributes & USB_ATTRS_ONE_XFER) ? intr_reqp->intr_timeout : 0; /* DATA IN */ if (tw->tw_direction == PID_IN) { /* Insert the td onto the queue head */ error = uhci_insert_hc_td(uhcip, tw->tw_cookie.dmac_address, length, pp, tw, PID_IN, intr_reqp->intr_attributes); if (error != USB_SUCCESS) { uhci_deallocate_periodic_in_resource(uhcip, pp, tw); /* free the transfer wrapper */ uhci_deallocate_tw(uhcip, pp, tw); return (USB_NO_RESOURCES); } tw->tw_bytes_xfered = 0; return (USB_SUCCESS); } /* DATA OUT */ ASSERT(req->intr_data != NULL); /* Copy the data into the message */ ddi_rep_put8(tw->tw_accesshandle, req->intr_data->b_rptr, (uint8_t *)tw->tw_buf, req->intr_len, DDI_DEV_AUTOINCR); /* set tw->tw_claim flag, so that nobody else works on this tw. */ tw->tw_claim = UHCI_INTR_HDLR_CLAIMED; mps = ph->p_ep.wMaxPacketSize; buf_addr = tw->tw_cookie.dmac_address; /* Insert tds onto the queue head */ while (length > 0) { error = uhci_insert_hc_td(uhcip, buf_addr, (length > mps) ? mps : length, pp, tw, PID_OUT, intr_reqp->intr_attributes); if (error != USB_SUCCESS) { /* no resource. */ break; } if (length <= mps) { /* inserted all data. */ length = 0; } else { buf_addr += mps; length -= mps; } } if (error != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_insert_intr_td: allocate td failed, free resource"); /* remove all the tds */ while (tw->tw_hctd_head != NULL) { uhci_delete_td(uhcip, tw->tw_hctd_head); } tw->tw_claim = UHCI_NOT_CLAIMED; uhci_deallocate_tw(uhcip, pp, tw); return (error); } /* allow HC to xfer the tds of this tw */ tmp_td = tw->tw_hctd_head; while (tmp_td != NULL) { SetTD_status(uhcip, tmp_td, UHCI_TD_ACTIVE); tmp_td = tmp_td->tw_td_next; } tw->tw_bytes_xfered = 0; tw->tw_claim = UHCI_NOT_CLAIMED; return (error); } /* * uhci_create_transfer_wrapper: * Create a Transaction Wrapper (TW). * This involves the allocating of DMA resources. */ static uhci_trans_wrapper_t * uhci_create_transfer_wrapper( uhci_state_t *uhcip, uhci_pipe_private_t *pp, size_t length, usb_flags_t usb_flags) { int result; size_t real_length; uint_t ccount; uhci_trans_wrapper_t *tw; ddi_device_acc_attr_t dev_attr; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_create_transfer_wrapper: length = 0x%lx flags = 0x%x", length, usb_flags); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Allocate space for the transfer wrapper */ if ((tw = kmem_zalloc(sizeof (uhci_trans_wrapper_t), KM_NOSLEEP)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_create_transfer_wrapper: kmem_alloc failed"); return (NULL); } /* Store the transfer length */ tw->tw_length = length; /* 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) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_create_transfer_wrapper: Alloc handle failed"); 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, tw->tw_length, &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, (caddr_t *)&tw->tw_buf, &real_length, &tw->tw_accesshandle)) != DDI_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_create_transfer_wrapper: dma_mem_alloc fail"); ddi_dma_free_handle(&tw->tw_dmahandle); kmem_free(tw, sizeof (uhci_trans_wrapper_t)); return (NULL); } ASSERT(real_length >= length); /* Bind the handle */ if ((result = ddi_dma_addr_bind_handle(tw->tw_dmahandle, NULL, (caddr_t)tw->tw_buf, real_length, DDI_DMA_RDWR|DDI_DMA_CONSISTENT, DDI_DMA_DONTWAIT, NULL, &tw->tw_cookie, &ccount)) != DDI_DMA_MAPPED) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_create_transfer_wrapper: Bind handle failed"); 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) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "create_transfer_wrapper: More than 1 cookie"); 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); } /* * 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; ASSERT(tw->tw_next == NULL); } /* 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; return (tw); } /* * uhci_insert_hc_td: * Insert a Transfer Descriptor (TD) on an QH. */ int uhci_insert_hc_td( uhci_state_t *uhcip, uint32_t buffer_address, size_t hcgtd_length, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw, uchar_t PID, usb_req_attrs_t attrs) { uhci_td_t *td, *current_dummy; queue_head_t *qh = pp->pp_qh; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); if ((td = uhci_allocate_td_from_pool(uhcip)) == NULL) { return (USB_NO_RESOURCES); } current_dummy = qh->td_tailp; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_insert_hc_td: td %p, attrs = 0x%x", td, attrs); /* * Fill in the current dummy td and * add the new dummy to the end. */ uhci_fill_in_td(uhcip, td, current_dummy, buffer_address, hcgtd_length, pp, PID, attrs); /* * Allow HC hardware xfer the td, except interrupt out td. */ if ((tw->tw_handle_td != uhci_handle_intr_td) || (PID != PID_OUT)) { SetTD_status(uhcip, current_dummy, UHCI_TD_ACTIVE); } /* Insert this td onto the tw */ if (tw->tw_hctd_head == NULL) { ASSERT(tw->tw_hctd_tail == NULL); tw->tw_hctd_head = current_dummy; tw->tw_hctd_tail = current_dummy; } else { /* Add the td to the end of the list */ tw->tw_hctd_tail->tw_td_next = current_dummy; tw->tw_hctd_tail = current_dummy; } /* * Insert the TD on to the QH. When this occurs, * the Host Controller will see the newly filled in TD */ current_dummy->outst_td_next = NULL; current_dummy->outst_td_prev = uhcip->uhci_outst_tds_tail; if (uhcip->uhci_outst_tds_head == NULL) { uhcip->uhci_outst_tds_head = current_dummy; } else { uhcip->uhci_outst_tds_tail->outst_td_next = current_dummy; } uhcip->uhci_outst_tds_tail = current_dummy; current_dummy->tw = tw; return (USB_SUCCESS); } /* * uhci_fill_in_td: * Fill in the fields of a Transfer Descriptor (TD). */ static void uhci_fill_in_td( uhci_state_t *uhcip, uhci_td_t *td, uhci_td_t *current_dummy, uint32_t buffer_address, size_t length, uhci_pipe_private_t *pp, uchar_t PID, usb_req_attrs_t attrs) { usba_pipe_handle_data_t *ph = pp->pp_pipe_handle; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_fill_in_td: attrs = 0x%x", attrs); /* * If this is an isochronous TD, just return */ if (UHCI_XFER_TYPE(&ph->p_ep) == USB_EP_ATTR_ISOCH) { return; } bzero((char *)td, sizeof (uhci_td_t)); /* Clear the TD */ SetTD32(uhcip, current_dummy->link_ptr, TD_PADDR(td)); if (attrs & USB_ATTRS_SHORT_XFER_OK) { SetTD_spd(uhcip, current_dummy, 1); } mutex_enter(&ph->p_usba_device->usb_mutex); if (ph->p_usba_device->usb_port_status == USBA_LOW_SPEED_DEV) { SetTD_ls(uhcip, current_dummy, LOW_SPEED_DEVICE); } SetTD_c_err(uhcip, current_dummy, UHCI_MAX_ERR_COUNT); SetTD_mlen(uhcip, current_dummy, (length == 0) ? 0x7ff: (length - 1)); SetTD_dtogg(uhcip, current_dummy, pp->pp_data_toggle); /* Adjust the data toggle bit */ ADJ_DATA_TOGGLE(pp); SetTD_devaddr(uhcip, current_dummy, ph->p_usba_device->usb_addr); SetTD_endpt(uhcip, current_dummy, ph->p_ep.bEndpointAddress & END_POINT_ADDRESS_MASK); SetTD_PID(uhcip, current_dummy, PID); SetTD32(uhcip, current_dummy->buffer_address, buffer_address); SetTD_ioc(uhcip, current_dummy, INTERRUPT_ON_COMPLETION); td->qh_td_prev = current_dummy; current_dummy->qh_td_prev = NULL; pp->pp_qh->td_tailp = td; mutex_exit(&ph->p_usba_device->usb_mutex); } /* * uhci_modify_td_active_bits: * Sets active bit in all the tds of QH to INACTIVE so that * the HC stops processing the TD's related to the QH. */ void uhci_modify_td_active_bits( uhci_state_t *uhcip, uhci_pipe_private_t *pp) { uhci_td_t *td_head; usb_ep_descr_t *ept = &pp->pp_pipe_handle->p_ep; uhci_trans_wrapper_t *tw_head = pp->pp_tw_head; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_modify_td_active_bits: tw head %p", (void *)tw_head); while (tw_head != NULL) { tw_head->tw_claim = UHCI_MODIFY_TD_BITS_CLAIMED; td_head = tw_head->tw_hctd_head; while (td_head) { if (UHCI_XFER_TYPE(ept) == USB_EP_ATTR_ISOCH) { SetTD_status(uhcip, td_head, GetTD_status(uhcip, td_head) & TD_INACTIVE); } else { SetTD32(uhcip, td_head->link_ptr, GetTD32(uhcip, td_head->link_ptr) | HC_END_OF_LIST); } td_head = td_head->tw_td_next; } tw_head = tw_head->tw_next; } } /* * uhci_insert_ctrl_td: * Create a TD and a data buffer for a control Queue Head. */ int uhci_insert_ctrl_td( uhci_state_t *uhcip, usba_pipe_handle_data_t *ph, usb_ctrl_req_t *ctrl_reqp, usb_flags_t flags) { uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; uhci_trans_wrapper_t *tw; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_ctrl_td: timeout: 0x%x", ctrl_reqp->ctrl_timeout); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Allocate a transaction wrapper */ if ((tw = uhci_create_transfer_wrapper(uhcip, pp, ctrl_reqp->ctrl_wLength + SETUP_SIZE, flags)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_ctrl_td: TW allocation failed"); return (USB_NO_RESOURCES); } pp->pp_data_toggle = 0; tw->tw_curr_xfer_reqp = (usb_opaque_t)ctrl_reqp; tw->tw_bytes_xfered = 0; tw->tw_bytes_pending = ctrl_reqp->ctrl_wLength; tw->tw_timeout_cnt = max(UHCI_CTRL_TIMEOUT, ctrl_reqp->ctrl_timeout); /* * Initialize the callback and any callback * data for when the td completes. */ tw->tw_handle_td = uhci_handle_ctrl_td; tw->tw_handle_callback_value = NULL; if ((uhci_create_setup_pkt(uhcip, pp, tw)) != USB_SUCCESS) { tw->tw_ctrl_state = 0; /* free the transfer wrapper */ uhci_deallocate_tw(uhcip, pp, tw); return (USB_NO_RESOURCES); } tw->tw_ctrl_state = SETUP; return (USB_SUCCESS); } /* * uhci_create_setup_pkt: * create a setup packet to initiate a control transfer. * * OHCI driver has seen the case where devices fail if there is * more than one control transfer to the device within a frame. * So, the UHCI ensures that only one TD will be put on the control * pipe to one device (to be consistent with OHCI driver). */ static int uhci_create_setup_pkt( uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw) { int sdata; usb_ctrl_req_t *req = (usb_ctrl_req_t *)tw->tw_curr_xfer_reqp; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_create_setup_pkt: 0x%x 0x%x 0x%x 0x%x 0x%x 0x%p", req->ctrl_bmRequestType, req->ctrl_bRequest, req->ctrl_wValue, req->ctrl_wIndex, req->ctrl_wLength, (void *)req->ctrl_data); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); ASSERT(tw != NULL); /* Create the first four bytes of the setup packet */ sdata = (req->ctrl_bmRequestType | (req->ctrl_bRequest << 8) | (req->ctrl_wValue << 16)); ddi_put32(tw->tw_accesshandle, (uint_t *)tw->tw_buf, sdata); /* Create the second four bytes */ sdata = (uint32_t)(req->ctrl_wIndex | (req->ctrl_wLength << 16)); ddi_put32(tw->tw_accesshandle, (uint_t *)(tw->tw_buf + sizeof (uint_t)), sdata); /* * The TD's are placed on the QH one at a time. * Once this TD is placed on the done list, the * data or status phase TD will be enqueued. */ if ((uhci_insert_hc_td(uhcip, tw->tw_cookie.dmac_address, SETUP_SIZE, pp, tw, PID_SETUP, req->ctrl_attributes)) != USB_SUCCESS) { return (USB_NO_RESOURCES); } USB_DPRINTF_L3(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "Create_setup: pp = 0x%p, attrs = 0x%x", pp, req->ctrl_attributes); /* * If this control transfer has a data phase, record the * direction. If the data phase is an OUT transaction , * copy the data into the buffer of the transfer wrapper. */ if (req->ctrl_wLength != 0) { /* There is a data stage. Find the direction */ if (req->ctrl_bmRequestType & USB_DEV_REQ_DEV_TO_HOST) { tw->tw_direction = PID_IN; } else { tw->tw_direction = PID_OUT; /* Copy the data into the buffer */ ddi_rep_put8(tw->tw_accesshandle, req->ctrl_data->b_rptr, (uint8_t *)(tw->tw_buf + SETUP_SIZE), req->ctrl_wLength, DDI_DEV_AUTOINCR); } } return (USB_SUCCESS); } /* * uhci_create_stats: * Allocate and initialize the uhci kstat structures */ void uhci_create_stats(uhci_state_t *uhcip) { int i; char kstatname[KSTAT_STRLEN]; char *usbtypes[USB_N_COUNT_KSTATS] = {"ctrl", "isoch", "bulk", "intr"}; uint_t instance = uhcip->uhci_instance; const char *dname = ddi_driver_name(uhcip->uhci_dip); uhci_intrs_stats_t *isp; if (UHCI_INTRS_STATS(uhcip) == NULL) { (void) snprintf(kstatname, KSTAT_STRLEN, "%s%d,intrs", dname, instance); UHCI_INTRS_STATS(uhcip) = kstat_create("usba", instance, kstatname, "usb_interrupts", KSTAT_TYPE_NAMED, sizeof (uhci_intrs_stats_t) / sizeof (kstat_named_t), KSTAT_FLAG_PERSISTENT); if (UHCI_INTRS_STATS(uhcip) != NULL) { isp = UHCI_INTRS_STATS_DATA(uhcip); kstat_named_init(&isp->uhci_intrs_hc_halted, "HC Halted", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_hc_process_err, "HC Process Errors", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_host_sys_err, "Host Sys Errors", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_resume_detected, "Resume Detected", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_usb_err_intr, "USB Error", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_usb_intr, "USB Interrupts", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_total, "Total Interrupts", KSTAT_DATA_UINT64); kstat_named_init(&isp->uhci_intrs_not_claimed, "Not Claimed", KSTAT_DATA_UINT64); UHCI_INTRS_STATS(uhcip)->ks_private = uhcip; UHCI_INTRS_STATS(uhcip)->ks_update = nulldev; kstat_install(UHCI_INTRS_STATS(uhcip)); } } if (UHCI_TOTAL_STATS(uhcip) == NULL) { (void) snprintf(kstatname, KSTAT_STRLEN, "%s%d,total", dname, instance); UHCI_TOTAL_STATS(uhcip) = kstat_create("usba", instance, kstatname, "usb_byte_count", KSTAT_TYPE_IO, 1, KSTAT_FLAG_PERSISTENT); if (UHCI_TOTAL_STATS(uhcip) != NULL) { kstat_install(UHCI_TOTAL_STATS(uhcip)); } } for (i = 0; i < USB_N_COUNT_KSTATS; i++) { if (uhcip->uhci_count_stats[i] == NULL) { (void) snprintf(kstatname, KSTAT_STRLEN, "%s%d,%s", dname, instance, usbtypes[i]); uhcip->uhci_count_stats[i] = kstat_create("usba", instance, kstatname, "usb_byte_count", KSTAT_TYPE_IO, 1, KSTAT_FLAG_PERSISTENT); if (uhcip->uhci_count_stats[i] != NULL) { kstat_install(uhcip->uhci_count_stats[i]); } } } } /* * uhci_destroy_stats: * Clean up uhci kstat structures */ void uhci_destroy_stats(uhci_state_t *uhcip) { int i; if (UHCI_INTRS_STATS(uhcip)) { kstat_delete(UHCI_INTRS_STATS(uhcip)); UHCI_INTRS_STATS(uhcip) = NULL; } if (UHCI_TOTAL_STATS(uhcip)) { kstat_delete(UHCI_TOTAL_STATS(uhcip)); UHCI_TOTAL_STATS(uhcip) = NULL; } for (i = 0; i < USB_N_COUNT_KSTATS; i++) { if (uhcip->uhci_count_stats[i]) { kstat_delete(uhcip->uhci_count_stats[i]); uhcip->uhci_count_stats[i] = NULL; } } } void uhci_do_intrs_stats(uhci_state_t *uhcip, int val) { if (UHCI_INTRS_STATS(uhcip) == NULL) { return; } UHCI_INTRS_STATS_DATA(uhcip)->uhci_intrs_total.value.ui64++; switch (val) { case USBSTS_REG_HC_HALTED: UHCI_INTRS_STATS_DATA(uhcip)->uhci_intrs_hc_halted.value.ui64++; break; case USBSTS_REG_HC_PROCESS_ERR: UHCI_INTRS_STATS_DATA(uhcip)-> uhci_intrs_hc_process_err.value.ui64++; break; case USBSTS_REG_HOST_SYS_ERR: UHCI_INTRS_STATS_DATA(uhcip)-> uhci_intrs_host_sys_err.value.ui64++; break; case USBSTS_REG_RESUME_DETECT: UHCI_INTRS_STATS_DATA(uhcip)-> uhci_intrs_resume_detected.value.ui64++; break; case USBSTS_REG_USB_ERR_INTR: UHCI_INTRS_STATS_DATA(uhcip)-> uhci_intrs_usb_err_intr.value.ui64++; break; case USBSTS_REG_USB_INTR: UHCI_INTRS_STATS_DATA(uhcip)->uhci_intrs_usb_intr.value.ui64++; break; default: UHCI_INTRS_STATS_DATA(uhcip)-> uhci_intrs_not_claimed.value.ui64++; break; } } void uhci_do_byte_stats(uhci_state_t *uhcip, size_t len, uint8_t attr, uint8_t addr) { uint8_t type = attr & USB_EP_ATTR_MASK; uint8_t dir = addr & USB_EP_DIR_MASK; switch (dir) { case USB_EP_DIR_IN: UHCI_TOTAL_STATS_DATA(uhcip)->reads++; UHCI_TOTAL_STATS_DATA(uhcip)->nread += len; switch (type) { case USB_EP_ATTR_CONTROL: UHCI_CTRL_STATS(uhcip)->reads++; UHCI_CTRL_STATS(uhcip)->nread += len; break; case USB_EP_ATTR_BULK: UHCI_BULK_STATS(uhcip)->reads++; UHCI_BULK_STATS(uhcip)->nread += len; break; case USB_EP_ATTR_INTR: UHCI_INTR_STATS(uhcip)->reads++; UHCI_INTR_STATS(uhcip)->nread += len; break; case USB_EP_ATTR_ISOCH: UHCI_ISOC_STATS(uhcip)->reads++; UHCI_ISOC_STATS(uhcip)->nread += len; break; } break; case USB_EP_DIR_OUT: UHCI_TOTAL_STATS_DATA(uhcip)->writes++; UHCI_TOTAL_STATS_DATA(uhcip)->nwritten += len; switch (type) { case USB_EP_ATTR_CONTROL: UHCI_CTRL_STATS(uhcip)->writes++; UHCI_CTRL_STATS(uhcip)->nwritten += len; break; case USB_EP_ATTR_BULK: UHCI_BULK_STATS(uhcip)->writes++; UHCI_BULK_STATS(uhcip)->nwritten += len; break; case USB_EP_ATTR_INTR: UHCI_INTR_STATS(uhcip)->writes++; UHCI_INTR_STATS(uhcip)->nwritten += len; break; case USB_EP_ATTR_ISOCH: UHCI_ISOC_STATS(uhcip)->writes++; UHCI_ISOC_STATS(uhcip)->nwritten += len; break; } break; } } /* * uhci_free_tw: * Free the Transfer Wrapper (TW). */ void uhci_free_tw(uhci_state_t *uhcip, uhci_trans_wrapper_t *tw) { int rval; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_free_tw:"); ASSERT(tw != NULL); rval = ddi_dma_unbind_handle(tw->tw_dmahandle); ASSERT(rval == DDI_SUCCESS); ddi_dma_mem_free(&tw->tw_accesshandle); ddi_dma_free_handle(&tw->tw_dmahandle); kmem_free(tw, sizeof (uhci_trans_wrapper_t)); } /* * uhci_deallocate_tw: * Deallocate of a Transaction Wrapper (TW) and this involves * the freeing of DMA resources. */ void uhci_deallocate_tw(uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw) { uhci_trans_wrapper_t *head; USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_deallocate_tw:"); /* * If the transfer wrapper has no Host Controller (HC) * Transfer Descriptors (TD) associated with it, then * remove the transfer wrapper. The transfers are done * in FIFO order, so this should be the first transfer * wrapper on the list. */ if (tw->tw_hctd_head != NULL) { ASSERT(tw->tw_hctd_tail != NULL); return; } ASSERT(tw->tw_hctd_tail == NULL); ASSERT(pp->pp_tw_head != NULL); /* * If pp->pp_tw_head is NULL, set the tail also to NULL. */ head = pp->pp_tw_head; if (head == tw) { pp->pp_tw_head = head->tw_next; if (pp->pp_tw_head == NULL) { pp->pp_tw_tail = NULL; } } else { while (head->tw_next != tw) head = head->tw_next; head->tw_next = tw->tw_next; if (tw->tw_next == NULL) { pp->pp_tw_tail = head; } } uhci_free_tw(uhcip, tw); } void uhci_delete_td(uhci_state_t *uhcip, uhci_td_t *td) { uhci_td_t *tmp_td; uhci_trans_wrapper_t *tw = td->tw; if ((td->outst_td_next == NULL) && (td->outst_td_prev == NULL)) { uhcip->uhci_outst_tds_head = NULL; uhcip->uhci_outst_tds_tail = NULL; } else if (td->outst_td_next == NULL) { td->outst_td_prev->outst_td_next = NULL; uhcip->uhci_outst_tds_tail = td->outst_td_prev; } else if (td->outst_td_prev == NULL) { td->outst_td_next->outst_td_prev = NULL; uhcip->uhci_outst_tds_head = td->outst_td_next; } else { td->outst_td_prev->outst_td_next = td->outst_td_next; td->outst_td_next->outst_td_prev = td->outst_td_prev; } tmp_td = tw->tw_hctd_head; if (tmp_td != td) { while (tmp_td->tw_td_next != td) { tmp_td = tmp_td->tw_td_next; } ASSERT(tmp_td); tmp_td->tw_td_next = td->tw_td_next; if (td->tw_td_next == NULL) { tw->tw_hctd_tail = tmp_td; } } else { tw->tw_hctd_head = tw->tw_hctd_head->tw_td_next; if (tw->tw_hctd_head == NULL) { tw->tw_hctd_tail = NULL; } } td->flag = TD_FLAG_FREE; } void uhci_remove_tds_tws( uhci_state_t *uhcip, usba_pipe_handle_data_t *ph) { usb_opaque_t curr_reqp; uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; usb_ep_descr_t *ept = &pp->pp_pipe_handle->p_ep; uhci_trans_wrapper_t *tw_tmp; uhci_trans_wrapper_t *tw_head = pp->pp_tw_head; while (tw_head != NULL) { tw_tmp = tw_head; tw_head = tw_head->tw_next; curr_reqp = tw_tmp->tw_curr_xfer_reqp; if (curr_reqp) { /* do this for control/bulk/intr */ if ((tw_tmp->tw_direction == PID_IN) && (UHCI_XFER_TYPE(ept) == USB_EP_ATTR_INTR)) { uhci_deallocate_periodic_in_resource(uhcip, pp, tw_tmp); } else { uhci_hcdi_callback(uhcip, pp, pp->pp_pipe_handle, tw_tmp, USB_CR_FLUSHED); } } /* end of curr_reqp */ if (tw_tmp->tw_claim != UHCI_MODIFY_TD_BITS_CLAIMED) { continue; } while (tw_tmp->tw_hctd_head != NULL) { uhci_delete_td(uhcip, tw_tmp->tw_hctd_head); } uhci_deallocate_tw(uhcip, pp, tw_tmp); } } /* * uhci_remove_qh: * Remove the Queue Head from the Host Controller's * appropriate QH list. */ void uhci_remove_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { uhci_td_t *dummy_td; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_remove_qh:"); dummy_td = pp->pp_qh->td_tailp; dummy_td->flag = TD_FLAG_FREE; switch (UHCI_XFER_TYPE(&pp->pp_pipe_handle->p_ep)) { case USB_EP_ATTR_CONTROL: uhci_remove_ctrl_qh(uhcip, pp); break; case USB_EP_ATTR_BULK: uhci_remove_bulk_qh(uhcip, pp); break; case USB_EP_ATTR_INTR: uhci_remove_intr_qh(uhcip, pp); break; } } static void uhci_remove_intr_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { queue_head_t *qh = pp->pp_qh; queue_head_t *next_lattice_qh = QH_VADDR(GetQH32(uhcip, qh->link_ptr) & QH_LINK_PTR_MASK); qh->prev_qh->link_ptr = qh->link_ptr; next_lattice_qh->prev_qh = qh->prev_qh; qh->qh_flag = QUEUE_HEAD_FLAG_FREE; } /* * uhci_remove_bulk_qh: * Remove a bulk QH from the Host Controller's QH list. There may be a * loop for bulk QHs, we must care about this while removing a bulk QH. */ static void uhci_remove_bulk_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { queue_head_t *qh = pp->pp_qh; queue_head_t *next_lattice_qh; uint32_t paddr; paddr = (GetQH32(uhcip, qh->link_ptr) & QH_LINK_PTR_MASK); next_lattice_qh = (qh == uhcip->uhci_bulk_xfers_q_tail) ? 0 : QH_VADDR(paddr); if ((qh == uhcip->uhci_bulk_xfers_q_tail) && (qh->prev_qh == uhcip->uhci_bulk_xfers_q_head)) { SetQH32(uhcip, qh->prev_qh->link_ptr, HC_END_OF_LIST); } else { qh->prev_qh->link_ptr = qh->link_ptr; } if (next_lattice_qh == NULL) { uhcip->uhci_bulk_xfers_q_tail = qh->prev_qh; } else { next_lattice_qh->prev_qh = qh->prev_qh; } qh->qh_flag = QUEUE_HEAD_FLAG_FREE; } static void uhci_remove_ctrl_qh(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { queue_head_t *qh = pp->pp_qh; queue_head_t *next_lattice_qh = QH_VADDR(GetQH32(uhcip, qh->link_ptr) & QH_LINK_PTR_MASK); qh->prev_qh->link_ptr = qh->link_ptr; if (next_lattice_qh->prev_qh != NULL) { next_lattice_qh->prev_qh = qh->prev_qh; } else { uhcip->uhci_ctrl_xfers_q_tail = qh->prev_qh; } qh->qh_flag = QUEUE_HEAD_FLAG_FREE; } /* * uhci_allocate_td_from_pool: * Allocate a Transfer Descriptor (TD) from the TD buffer pool. */ static uhci_td_t * uhci_allocate_td_from_pool(uhci_state_t *uhcip) { int index; uhci_td_t *td; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* * Search for a blank Transfer Descriptor (TD) * in the TD buffer pool. */ for (index = 0; index < uhci_td_pool_size; index ++) { if (uhcip->uhci_td_pool_addr[index].flag == TD_FLAG_FREE) { break; } } if (index == uhci_td_pool_size) { USB_DPRINTF_L2(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_allocate_td_from_pool: TD exhausted"); return (NULL); } USB_DPRINTF_L4(PRINT_MASK_ALLOC, uhcip->uhci_log_hdl, "uhci_allocate_td_from_pool: Allocated %d", index); /* Create a new dummy for the end of the TD list */ td = &uhcip->uhci_td_pool_addr[index]; /* Mark the newly allocated TD as a dummy */ td->flag = TD_FLAG_DUMMY; td->qh_td_prev = NULL; return (td); } /* * uhci_insert_bulk_td: */ int uhci_insert_bulk_td( uhci_state_t *uhcip, usba_pipe_handle_data_t *ph, usb_bulk_req_t *req, usb_flags_t flags) { size_t length; uint_t mps; /* MaxPacketSize */ uint_t num_bulk_tds, i; uint32_t buf_addr; uhci_td_t *bulk_td_ptr; uhci_td_t *current_dummy; uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; uhci_trans_wrapper_t *tw; uhci_bulk_isoc_xfer_t *bulk_xfer_info; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_bulk_td: req: 0x%p, flags = 0x%x", req, flags); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* * Create transfer wrapper */ if ((tw = uhci_create_transfer_wrapper(uhcip, pp, req->bulk_len, flags)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_bulk_td: TW allocation failed"); return (USB_NO_RESOURCES); } tw->tw_bytes_xfered = 0; tw->tw_bytes_pending = req->bulk_len; tw->tw_handle_td = uhci_handle_bulk_td; tw->tw_handle_callback_value = (usb_opaque_t)req->bulk_data; tw->tw_timeout_cnt = req->bulk_timeout; tw->tw_data = req->bulk_data; tw->tw_curr_xfer_reqp = (usb_opaque_t)req; /* Get the bulk pipe direction */ tw->tw_direction = (UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_OUT) ? PID_OUT : PID_IN; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_bulk_td: direction: 0x%x", tw->tw_direction); /* If the DATA OUT, copy the data into transfer buffer. */ if (tw->tw_direction == PID_OUT) { ASSERT(req->bulk_data != NULL); /* Copy the data into the message */ ddi_rep_put8(tw->tw_accesshandle, req->bulk_data->b_rptr, (uint8_t *)tw->tw_buf, req->bulk_len, DDI_DEV_AUTOINCR); } /* Get the max packet size. */ length = mps = pp->pp_pipe_handle->p_ep.wMaxPacketSize; /* * Calculate number of TD's to insert in the current frame interval. * Max number TD's allowed (driver implementation) is 128 * in one frame interval. Once all the TD's are completed * then the remaining TD's will be inserted into the lattice * in the uhci_handle_bulk_td(). */ if ((tw->tw_bytes_pending / mps) >= MAX_NUM_BULK_TDS_PER_XFER) { num_bulk_tds = MAX_NUM_BULK_TDS_PER_XFER; } else { num_bulk_tds = (tw->tw_bytes_pending / mps); if (tw->tw_bytes_pending % mps) { num_bulk_tds++; length = (tw->tw_bytes_pending % mps); } } /* * Allocate memory for the bulk xfer information structure */ if ((bulk_xfer_info = kmem_zalloc( sizeof (uhci_bulk_isoc_xfer_t), KM_NOSLEEP)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_bulk_td: kmem_zalloc failed"); /* Free the transfer wrapper */ uhci_deallocate_tw(uhcip, pp, tw); return (USB_FAILURE); } /* Allocate memory for the bulk TD's */ if (uhci_alloc_memory_for_tds(uhcip, num_bulk_tds, bulk_xfer_info) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_bulk_td: alloc_memory_for_tds failed"); kmem_free(bulk_xfer_info, sizeof (uhci_bulk_isoc_xfer_t)); /* Free the transfer wrapper */ uhci_deallocate_tw(uhcip, pp, tw); return (USB_FAILURE); } bulk_td_ptr = (uhci_td_t *)bulk_xfer_info->pool_addr; bulk_td_ptr[0].qh_td_prev = NULL; current_dummy = pp->pp_qh->td_tailp; buf_addr = tw->tw_cookie.dmac_address; pp->pp_qh->bulk_xfer_info = bulk_xfer_info; /* Fill up all the bulk TD's */ for (i = 0; i < (num_bulk_tds - 1); i++) { uhci_fill_in_bulk_isoc_td(uhcip, &bulk_td_ptr[i], &bulk_td_ptr[i+1], BULKTD_PADDR(bulk_xfer_info, &bulk_td_ptr[i+1]), ph, buf_addr, mps, tw); buf_addr += mps; } uhci_fill_in_bulk_isoc_td(uhcip, &bulk_td_ptr[i], current_dummy, TD_PADDR(current_dummy), ph, buf_addr, length, tw); bulk_xfer_info->num_tds = num_bulk_tds; /* * Point the end of the lattice tree to the start of the bulk xfers * queue head. This allows the HC to execute the same Queue Head/TD * in the same frame. There are some bulk devices, which NAKs after * completing each TD. As a result, the performance on such devices * is very bad. This loop will provide a chance to execute NAk'ed * bulk TDs again in the same frame. */ if (uhcip->uhci_pending_bulk_cmds++ == 0) { uhcip->uhci_bulk_xfers_q_tail->link_ptr = uhcip->uhci_bulk_xfers_q_head->link_ptr; USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_insert_bulk_td: count = %d no tds %d", uhcip->uhci_pending_bulk_cmds, num_bulk_tds); } /* Insert on the bulk queue head for the execution by HC */ SetQH32(uhcip, pp->pp_qh->element_ptr, bulk_xfer_info->cookie.dmac_address); return (USB_SUCCESS); } /* * uhci_fill_in_bulk_isoc_td * Fills the bulk TD */ void uhci_fill_in_bulk_isoc_td(uhci_state_t *uhcip, uhci_td_t *current_td, uhci_td_t *next_td, uint32_t next_td_paddr, usba_pipe_handle_data_t *ph, uint_t buffer_address, uint_t length, uhci_trans_wrapper_t *tw) { uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; usb_ep_descr_t *ept = &pp->pp_pipe_handle->p_ep; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_fill_in_bulk_isoc_td: tw = 0x%p", tw); bzero((char *)current_td, sizeof (uhci_td_t)); SetTD32(uhcip, current_td->link_ptr, next_td_paddr | HC_DEPTH_FIRST); switch (UHCI_XFER_TYPE(ept)) { case USB_EP_ATTR_ISOCH: if (((usb_isoc_req_t *)tw->tw_curr_xfer_reqp)->isoc_attributes & USB_ATTRS_SHORT_XFER_OK) { SetTD_spd(uhcip, current_td, 1); } break; case USB_EP_ATTR_BULK: if (((usb_bulk_req_t *)tw->tw_curr_xfer_reqp)->bulk_attributes & USB_ATTRS_SHORT_XFER_OK) { SetTD_spd(uhcip, current_td, 1); } break; } mutex_enter(&ph->p_usba_device->usb_mutex); SetTD_c_err(uhcip, current_td, UHCI_MAX_ERR_COUNT); SetTD_status(uhcip, current_td, UHCI_TD_ACTIVE); SetTD_ioc(uhcip, current_td, INTERRUPT_ON_COMPLETION); SetTD_mlen(uhcip, current_td, (length - 1)); SetTD_dtogg(uhcip, current_td, pp->pp_data_toggle); SetTD_devaddr(uhcip, current_td, ph->p_usba_device->usb_addr); SetTD_endpt(uhcip, current_td, ph->p_ep.bEndpointAddress & END_POINT_ADDRESS_MASK); SetTD_PID(uhcip, current_td, tw->tw_direction); SetTD32(uhcip, current_td->buffer_address, buffer_address); /* * Adjust the data toggle. * The data toggle bit must always be 0 for isoc transfers. * And set the "iso" bit in the TD for isoc transfers. */ if (UHCI_XFER_TYPE(ept) == USB_EP_ATTR_ISOCH) { pp->pp_data_toggle = 0; SetTD_iso(uhcip, current_td, 1); } else { ADJ_DATA_TOGGLE(pp); next_td->qh_td_prev = current_td; pp->pp_qh->td_tailp = next_td; } current_td->outst_td_next = NULL; current_td->outst_td_prev = uhcip->uhci_outst_tds_tail; if (uhcip->uhci_outst_tds_head == NULL) { uhcip->uhci_outst_tds_head = current_td; } else { uhcip->uhci_outst_tds_tail->outst_td_next = current_td; } uhcip->uhci_outst_tds_tail = current_td; current_td->tw = tw; if (tw->tw_hctd_head == NULL) { ASSERT(tw->tw_hctd_tail == NULL); tw->tw_hctd_head = current_td; tw->tw_hctd_tail = current_td; } else { /* Add the td to the end of the list */ tw->tw_hctd_tail->tw_td_next = current_td; tw->tw_hctd_tail = current_td; } mutex_exit(&ph->p_usba_device->usb_mutex); } /* * uhci_alloc_memory_for_tds: * - Allocates memory for the isoc/bulk td pools. */ static int uhci_alloc_memory_for_tds( uhci_state_t *uhcip, uint_t num_tds, uhci_bulk_isoc_xfer_t *info) { int result; size_t real_length; uint_t ccount; ddi_device_acc_attr_t dev_attr; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_alloc_memory_for_tds: num_tds: 0x%x info: 0x%p", num_tds, info); /* The host controller will be little endian */ 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 bulk TD pool DMA handle */ if (ddi_dma_alloc_handle(uhcip->uhci_dip, &uhcip->uhci_dma_attr, DDI_DMA_SLEEP, 0, &info->dma_handle) != DDI_SUCCESS) { return (USB_FAILURE); } /* Allocate the memory for the bulk TD pool */ if (ddi_dma_mem_alloc(info->dma_handle, num_tds * sizeof (uhci_td_t), &dev_attr, DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, 0, &info->pool_addr, &real_length, &info->mem_handle)) { return (USB_FAILURE); } /* Map the bulk TD pool into the I/O address space */ result = ddi_dma_addr_bind_handle(info->dma_handle, NULL, (caddr_t)info->pool_addr, real_length, DDI_DMA_RDWR | DDI_DMA_CONSISTENT, DDI_DMA_SLEEP, NULL, &info->cookie, &ccount); /* Process the result */ if (result == DDI_DMA_MAPPED) { /* The cookie count should be 1 */ if (ccount != 1) { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools: More than 1 cookie"); return (USB_FAILURE); } } else { USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_allocate_pools: Result = %d", result); uhci_decode_ddi_dma_addr_bind_handle_result(uhcip, result); return (USB_FAILURE); } bzero((void *)info->pool_addr, num_tds * sizeof (uhci_td_t)); return (USB_SUCCESS); } /* * uhci_handle_bulk_td: * * Handles the completed bulk transfer descriptors */ void uhci_handle_bulk_td(uhci_state_t *uhcip, uhci_td_t *td) { uint_t num_bulk_tds, i; usb_cr_t error; uint_t length, bytes_xfered; ushort_t MaxPacketSize; uint32_t buf_addr, paddr; uhci_td_t *bulk_td_ptr, *current_dummy, *td_head; queue_head_t *qh, *next_qh; uhci_trans_wrapper_t *tw = td->tw; uhci_pipe_private_t *pp = tw->tw_pipe_private; uhci_bulk_isoc_xfer_t *bulk_xfer_info; usba_pipe_handle_data_t *ph; USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_handle_bulk_td: td = 0x%p tw = 0x%p", td, tw); /* * Update the tw_bytes_pending, and tw_bytes_xfered */ bytes_xfered = ZERO_LENGTH; /* * Check whether there are any errors occurred in the xfer. * If so, update the data_toggle for the queue head and * return error to the upper layer. */ if (GetTD_status(uhcip, td) & TD_STATUS_MASK) { uhci_handle_bulk_td_errors(uhcip, td); USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_bulk_td: error; data toggle: 0x%x", pp->pp_data_toggle); return; } /* * Update the tw_bytes_pending, and tw_bytes_xfered */ bytes_xfered = GetTD_alen(uhcip, td); if (bytes_xfered != ZERO_LENGTH) { tw->tw_bytes_pending -= (bytes_xfered + 1); tw->tw_bytes_xfered += (bytes_xfered + 1); } /* * Get Bulk pipe information and pipe handle */ bulk_xfer_info = pp->pp_qh->bulk_xfer_info; ph = tw->tw_pipe_private->pp_pipe_handle; /* * Check whether data underrun occurred. * If so, complete the transfer * Update the data toggle bit */ if (bytes_xfered != GetTD_mlen(uhcip, td)) { bulk_xfer_info->num_tds = 1; USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_handle_bulk_td: Data underrun occured"); pp->pp_data_toggle = GetTD_dtogg(uhcip, td) == 0 ? 1 : 0; } /* * If the TD's in the current frame are completed, then check * whether we have any more bytes to xfer. If so, insert TD's. * If no more bytes needs to be transferred, then do callback to the * upper layer. * If the TD's in the current frame are not completed, then * just delete the TD from the linked lists. */ USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_bulk_td: completed TD data toggle: 0x%x", GetTD_dtogg(uhcip, td)); if (--bulk_xfer_info->num_tds == 0) { uhci_delete_td(uhcip, td); if ((tw->tw_bytes_pending) && (GetTD_mlen(uhcip, td) - GetTD_alen(uhcip, td) == 0)) { MaxPacketSize = pp->pp_pipe_handle->p_ep.wMaxPacketSize; length = MaxPacketSize; qh = pp->pp_qh; paddr = GetQH32(uhcip, qh->link_ptr) & QH_LINK_PTR_MASK; if (GetQH32(uhcip, qh->link_ptr) != GetQH32(uhcip, uhcip->uhci_bulk_xfers_q_head->link_ptr)) { next_qh = QH_VADDR(paddr); SetQH32(uhcip, qh->prev_qh->link_ptr, paddr|(0x2)); next_qh->prev_qh = qh->prev_qh; SetQH32(uhcip, qh->link_ptr, GetQH32(uhcip, uhcip->uhci_bulk_xfers_q_head->link_ptr)); qh->prev_qh = uhcip->uhci_bulk_xfers_q_tail; SetQH32(uhcip, uhcip->uhci_bulk_xfers_q_tail->link_ptr, QH_PADDR(qh) | 0x2); uhcip->uhci_bulk_xfers_q_tail = qh; } if ((tw->tw_bytes_pending / MaxPacketSize) >= MAX_NUM_BULK_TDS_PER_XFER) { num_bulk_tds = MAX_NUM_BULK_TDS_PER_XFER; } else { num_bulk_tds = (tw->tw_bytes_pending / MaxPacketSize); if (tw->tw_bytes_pending % MaxPacketSize) { num_bulk_tds++; length = (tw->tw_bytes_pending % MaxPacketSize); } } current_dummy = pp->pp_qh->td_tailp; bulk_td_ptr = (uhci_td_t *)bulk_xfer_info->pool_addr; buf_addr = tw->tw_cookie.dmac_address + tw->tw_bytes_xfered; for (i = 0; i < (num_bulk_tds - 1); i++) { uhci_fill_in_bulk_isoc_td(uhcip, &bulk_td_ptr[i], &bulk_td_ptr[i + 1], BULKTD_PADDR(bulk_xfer_info, &bulk_td_ptr[i+1]), ph, buf_addr, MaxPacketSize, tw); buf_addr += MaxPacketSize; } uhci_fill_in_bulk_isoc_td(uhcip, &bulk_td_ptr[i], current_dummy, TD_PADDR(current_dummy), ph, buf_addr, length, tw); pp->pp_qh->bulk_xfer_info = bulk_xfer_info; bulk_xfer_info->num_tds = num_bulk_tds; SetQH32(uhcip, pp->pp_qh->element_ptr, bulk_xfer_info->cookie.dmac_address); } else { usba_pipe_handle_data_t *usb_pp = pp->pp_pipe_handle; pp->pp_qh->bulk_xfer_info = NULL; if (tw->tw_bytes_pending) { /* Update the element pointer */ SetQH32(uhcip, pp->pp_qh->element_ptr, TD_PADDR(pp->pp_qh->td_tailp)); /* Remove all the tds */ td_head = tw->tw_hctd_head; while (td_head != NULL) { uhci_delete_td(uhcip, td_head); td_head = tw->tw_hctd_head; } } if (tw->tw_direction == PID_IN) { usb_req_attrs_t attrs = ((usb_bulk_req_t *) tw->tw_curr_xfer_reqp)->bulk_attributes; error = USB_CR_OK; /* Data run occurred */ if (tw->tw_bytes_pending && (!(attrs & USB_ATTRS_SHORT_XFER_OK))) { error = USB_CR_DATA_UNDERRUN; } uhci_sendup_td_message(uhcip, error, tw); } else { uhci_do_byte_stats(uhcip, tw->tw_length, usb_pp->p_ep.bmAttributes, usb_pp->p_ep.bEndpointAddress); /* Data underrun occurred */ if (tw->tw_bytes_pending) { tw->tw_data->b_rptr += tw->tw_bytes_xfered; USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_handle_bulk_td: " "data underrun occurred"); uhci_hcdi_callback(uhcip, pp, tw->tw_pipe_private->pp_pipe_handle, tw, USB_CR_DATA_UNDERRUN); } else { uhci_hcdi_callback(uhcip, pp, tw->tw_pipe_private->pp_pipe_handle, tw, USB_CR_OK); } } /* direction */ /* Deallocate DMA memory */ uhci_deallocate_tw(uhcip, pp, tw); (void) ddi_dma_unbind_handle( bulk_xfer_info->dma_handle); ddi_dma_mem_free(&bulk_xfer_info->mem_handle); ddi_dma_free_handle(&bulk_xfer_info->dma_handle); kmem_free(bulk_xfer_info, sizeof (uhci_bulk_isoc_xfer_t)); /* * When there are no pending bulk commands, point the * end of the lattice tree to NULL. This will make sure * that the HC control does not loop anymore and PCI * bus is not affected. */ if (--uhcip->uhci_pending_bulk_cmds == 0) { uhcip->uhci_bulk_xfers_q_tail->link_ptr = HC_END_OF_LIST; USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_handle_bulk_td: count = %d", uhcip->uhci_pending_bulk_cmds); } } } else { uhci_delete_td(uhcip, td); } } void uhci_handle_bulk_td_errors(uhci_state_t *uhcip, uhci_td_t *td) { usb_cr_t usb_err; uint32_t paddr_tail, element_ptr; uhci_td_t *next_td; uhci_pipe_private_t *pp; uhci_trans_wrapper_t *tw = td->tw; usba_pipe_handle_data_t *ph; USB_DPRINTF_L2(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_handle_bulk_td_errors: td = %p", (void *)td); #ifdef DEBUG uhci_print_td(uhcip, td); #endif tw = td->tw; ph = tw->tw_pipe_private->pp_pipe_handle; pp = (uhci_pipe_private_t *)ph->p_hcd_private; /* * Find the type of error occurred and return the error * to the upper layer. And adjust the data toggle. */ element_ptr = GetQH32(uhcip, pp->pp_qh->element_ptr) & QH_ELEMENT_PTR_MASK; paddr_tail = TD_PADDR(pp->pp_qh->td_tailp); /* * If a timeout occurs before a transfer has completed, * the timeout handler sets the CRC/Timeout bit and clears the Active * bit in the link_ptr for each td in the transfer. * It then waits (at least) 1 ms so that any tds the controller might * have been executing will have completed. * So at this point element_ptr will point to either: * 1) the next td for the transfer (which has not been executed, * and has the CRC/Timeout status bit set and Active bit cleared), * 2) the dummy td for this qh. * So if the element_ptr does not point to the dummy td, we know * it points to the next td that would have been executed. * That td has the data toggle we want to save. * All outstanding tds have been marked as CRC/Timeout, * so it doesn't matter which td we pass to uhci_parse_td_error * for the error status. */ if (element_ptr != paddr_tail) { next_td = BULKTD_VADDR(pp->pp_qh->bulk_xfer_info, (element_ptr & QH_ELEMENT_PTR_MASK)); USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_bulk_td_errors: next td = %p", (void *)next_td); usb_err = uhci_parse_td_error(uhcip, pp, next_td); } else { usb_err = uhci_parse_td_error(uhcip, pp, td); } /* * Update the link pointer. */ SetQH32(uhcip, pp->pp_qh->element_ptr, TD_PADDR(pp->pp_qh->td_tailp)); /* * Send up number of bytes transferred before the error condition. */ if ((tw->tw_direction == PID_OUT) && tw->tw_data) { tw->tw_data->b_rptr += tw->tw_bytes_xfered; } uhci_remove_bulk_tds_tws(uhcip, tw->tw_pipe_private, UHCI_IN_ERROR); /* * When there are no pending bulk commands, point the end of the * lattice tree to NULL. This will make sure that the HC control * does not loop anymore and PCI bus is not affected. */ if (--uhcip->uhci_pending_bulk_cmds == 0) { uhcip->uhci_bulk_xfers_q_tail->link_ptr = HC_END_OF_LIST; USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_handle_bulk_td_errors: count = %d", uhcip->uhci_pending_bulk_cmds); } uhci_hcdi_callback(uhcip, pp, ph, tw, usb_err); uhci_deallocate_tw(uhcip, pp, tw); } void uhci_remove_bulk_tds_tws( uhci_state_t *uhcip, uhci_pipe_private_t *pp, int what) { uint_t rval; uhci_td_t *head; uhci_td_t *head_next; usb_opaque_t curr_reqp; uhci_bulk_isoc_xfer_t *info; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); if ((info = pp->pp_qh->bulk_xfer_info) == NULL) { return; } head = uhcip->uhci_outst_tds_head; while (head) { uhci_trans_wrapper_t *tw_tmp = head->tw; head_next = head->outst_td_next; if (pp->pp_qh == tw_tmp->tw_pipe_private->pp_qh) { curr_reqp = tw_tmp->tw_curr_xfer_reqp; if (curr_reqp && ((what == UHCI_IN_CLOSE) || (what == UHCI_IN_RESET))) { uhci_hcdi_callback(uhcip, pp, pp->pp_pipe_handle, tw_tmp, USB_CR_FLUSHED); } /* end of curr_reqp */ uhci_delete_td(uhcip, head); if (what == UHCI_IN_CLOSE || what == UHCI_IN_RESET) { ASSERT(info->num_tds > 0); if (--info->num_tds == 0) { uhci_deallocate_tw(uhcip, pp, tw_tmp); /* * This will make sure that the HC * does not loop anymore when there * are no pending bulk commands. */ if (--uhcip->uhci_pending_bulk_cmds == 0) { uhcip->uhci_bulk_xfers_q_tail-> link_ptr = HC_END_OF_LIST; USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_remove_bulk_tds_tws:" " count = %d", uhcip-> uhci_pending_bulk_cmds); } } } } head = head_next; } if (what == UHCI_IN_CLOSE || what == UHCI_IN_RESET) { ASSERT(info->num_tds == 0); } rval = ddi_dma_unbind_handle(info->dma_handle); ASSERT(rval == DDI_SUCCESS); ddi_dma_mem_free(&info->mem_handle); ddi_dma_free_handle(&info->dma_handle); kmem_free(info, sizeof (uhci_bulk_isoc_xfer_t)); pp->pp_qh->bulk_xfer_info = NULL; } /* * uhci_save_data_toggle () * Save the data toggle in the usba_device structure */ void uhci_save_data_toggle(uhci_pipe_private_t *pp) { usba_pipe_handle_data_t *ph = pp->pp_pipe_handle; /* Save the data toggle in the usb devices structure. */ mutex_enter(&ph->p_mutex); usba_hcdi_set_data_toggle(ph->p_usba_device, ph->p_ep.bEndpointAddress, pp->pp_data_toggle); mutex_exit(&ph->p_mutex); } /* * uhci_insert_isoc_td: * - Create transfer wrapper * - Allocate memory for the isoc td's * - Fill up all the TD's and submit to the HC * - Update all the linked lists */ int uhci_insert_isoc_td( uhci_state_t *uhcip, usba_pipe_handle_data_t *ph, usb_isoc_req_t *isoc_req, size_t length, usb_flags_t flags) { int rval = USB_SUCCESS; int error; uint_t ddic; uint32_t i, buffer_address; uint32_t bytes_to_xfer; uint32_t expired_frames = 0; usb_frame_number_t start_frame, end_frame, current_frame; uhci_td_t *td_ptr; uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; uhci_trans_wrapper_t *tw; uhci_bulk_isoc_xfer_t *isoc_xfer_info; USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td: ph = 0x%p, isoc req = %p length = %lu", ph, (void *)isoc_req, length); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Allocate a transfer wrapper */ if ((tw = uhci_create_transfer_wrapper(uhcip, pp, length, flags)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td: TW allocation failed"); return (USB_NO_RESOURCES); } /* Save current isochronous request pointer */ tw->tw_curr_xfer_reqp = (usb_opaque_t)isoc_req; /* * Initialize the transfer wrapper. These values are useful * for sending back the reply. */ tw->tw_handle_td = uhci_handle_isoc_td; tw->tw_handle_callback_value = NULL; tw->tw_direction = (UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_OUT) ? PID_OUT : PID_IN; /* * If the transfer isoc send, then copy the data from the request * to the transfer wrapper. */ if ((tw->tw_direction == PID_OUT) && length) { ASSERT(isoc_req->isoc_data != NULL); /* Copy the data into the message */ ddi_rep_put8(tw->tw_accesshandle, isoc_req->isoc_data->b_rptr, (uint8_t *)tw->tw_buf, length, DDI_DEV_AUTOINCR); } if (tw->tw_direction == PID_IN) { if ((rval = uhci_allocate_periodic_in_resource(uhcip, pp, tw, flags)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td: isoc_req_t alloc failed"); return (rval); } isoc_req = (usb_isoc_req_t *)tw->tw_curr_xfer_reqp; } tw->tw_isoc_req = (usb_isoc_req_t *)tw->tw_curr_xfer_reqp; /* Get the pointer to the isoc_xfer_info structure */ isoc_xfer_info = (uhci_bulk_isoc_xfer_t *)&tw->tw_xfer_info; isoc_xfer_info->num_tds = isoc_req->isoc_pkts_count; /* * Allocate memory for isoc tds */ if ((rval = uhci_alloc_memory_for_tds(uhcip, isoc_req->isoc_pkts_count, isoc_xfer_info)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td: Memory allocation failure"); uhci_deallocate_tw(uhcip, pp, tw); return (rval); } /* * Get the isoc td pool address, buffer address and * max packet size that the device supports. */ td_ptr = (uhci_td_t *)isoc_xfer_info->pool_addr; buffer_address = tw->tw_cookie.dmac_address; /* * Fill up the isoc tds */ USB_DPRINTF_L3(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td : isoc pkts %d", isoc_req->isoc_pkts_count); for (i = 0; i < isoc_req->isoc_pkts_count; i++) { td_ptr[i].isoc_pkt_index = i; bytes_to_xfer = isoc_req->isoc_pkt_descr[i].isoc_pkt_length; uhci_fill_in_bulk_isoc_td(uhcip, &td_ptr[i], (uhci_td_t *)NULL, HC_END_OF_LIST, ph, buffer_address, bytes_to_xfer, tw); buffer_address += isoc_req->isoc_pkt_descr[i].isoc_pkt_length; } /* * Get the starting frame number. * The client drivers sets the flag USB_ATTRS_ISOC_XFER_ASAP to inform * the HCD to care of starting frame number. * * Following code is very time critical. So, perform atomic execution. */ ddic = ddi_enter_critical(); current_frame = uhci_get_sw_frame_number(uhcip); if (isoc_req->isoc_attributes & USB_ATTRS_ISOC_START_FRAME) { start_frame = isoc_req->isoc_frame_no; end_frame = start_frame + isoc_req->isoc_pkts_count; /* Check available frames */ if ((end_frame - current_frame) < UHCI_MAX_ISOC_FRAMES) { if (current_frame > start_frame) { if ((current_frame + FRNUM_OFFSET) < end_frame) { expired_frames = current_frame + FRNUM_OFFSET - start_frame; start_frame = current_frame + FRNUM_OFFSET; } else { rval = USB_INVALID_START_FRAME; } } } else { rval = USB_INVALID_START_FRAME; } } else if (isoc_req->isoc_attributes & USB_ATTRS_ISOC_XFER_ASAP) { start_frame = pp->pp_frame_num; if (start_frame == INVALID_FRNUM) { start_frame = current_frame + FRNUM_OFFSET; } else if (current_frame > start_frame) { start_frame = current_frame + FRNUM_OFFSET; } end_frame = start_frame + isoc_req->isoc_pkts_count; isoc_req->isoc_frame_no = start_frame; } if (rval != USB_SUCCESS) { /* Exit the critical */ ddi_exit_critical(ddic); USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td: Invalid starting frame number"); while (tw->tw_hctd_head) { uhci_delete_td(uhcip, tw->tw_hctd_head); } error = ddi_dma_unbind_handle(isoc_xfer_info->dma_handle); ASSERT(error == DDI_SUCCESS); ddi_dma_mem_free(&isoc_xfer_info->mem_handle); ddi_dma_free_handle(&isoc_xfer_info->dma_handle); uhci_deallocate_tw(uhcip, pp, tw); return (rval); } for (i = 0; i < expired_frames; i++) { isoc_req->isoc_pkt_descr[i].isoc_pkt_status = USB_CR_NOT_ACCESSED; isoc_req->isoc_pkt_descr[i].isoc_pkt_actual_length = isoc_req->isoc_pkt_descr[i].isoc_pkt_length; uhci_delete_td(uhcip, &td_ptr[i]); --isoc_xfer_info->num_tds; } /* * Add the TD's to the HC list */ start_frame = (start_frame & 0x3ff); for (; i < isoc_req->isoc_pkts_count; i++) { if (uhcip->uhci_isoc_q_tailp[start_frame]) { td_ptr[i].isoc_prev = uhcip->uhci_isoc_q_tailp[start_frame]; td_ptr[i].isoc_next = NULL; td_ptr[i].link_ptr = uhcip->uhci_isoc_q_tailp[start_frame]->link_ptr; uhcip->uhci_isoc_q_tailp[start_frame]->isoc_next = &td_ptr[i]; SetTD32(uhcip, uhcip->uhci_isoc_q_tailp[start_frame]->link_ptr, ISOCTD_PADDR(isoc_xfer_info, &td_ptr[i])); uhcip->uhci_isoc_q_tailp[start_frame] = &td_ptr[i]; } else { uhcip->uhci_isoc_q_tailp[start_frame] = &td_ptr[i]; td_ptr[i].isoc_next = NULL; td_ptr[i].isoc_prev = NULL; SetTD32(uhcip, td_ptr[i].link_ptr, GetFL32(uhcip, uhcip->uhci_frame_lst_tablep[start_frame])); SetFL32(uhcip, uhcip->uhci_frame_lst_tablep[start_frame], ISOCTD_PADDR(isoc_xfer_info, &td_ptr[i])); } td_ptr[i].starting_frame = start_frame; if (++start_frame == NUM_FRAME_LST_ENTRIES) start_frame = 0; } ddi_exit_critical(ddic); pp->pp_frame_num = end_frame; USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_insert_isoc_td: current frame number 0x%llx, pipe frame num" " 0x%llx", current_frame, pp->pp_frame_num); return (rval); } /* * uhci_handle_isoc_td: * Handles the completed isoc tds */ void uhci_handle_isoc_td(uhci_state_t *uhcip, uhci_td_t *td) { uint_t rval; uint32_t pkt_index = td->isoc_pkt_index; usb_cr_t cr; uhci_trans_wrapper_t *tw = td->tw; usb_isoc_req_t *isoc_req = (usb_isoc_req_t *)tw->tw_isoc_req; uhci_pipe_private_t *pp = tw->tw_pipe_private; uhci_bulk_isoc_xfer_t *isoc_xfer_info = &tw->tw_xfer_info; usba_pipe_handle_data_t *usb_pp; USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_handle_isoc_td: td = 0x%p, pp = 0x%p, tw = 0x%p, req = 0x%p, " "index = %x", td, pp, tw, isoc_req, pkt_index); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); usb_pp = pp->pp_pipe_handle; /* * Check whether there are any errors occurred. If so, update error * count and return it to the upper.But never return a non zero * completion reason. */ cr = USB_CR_OK; if (GetTD_status(uhcip, td) & TD_STATUS_MASK) { USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_handle_isoc_td: Error Occurred: TD Status = %x", GetTD_status(uhcip, td)); isoc_req->isoc_error_count++; } if (isoc_req != NULL) { isoc_req->isoc_pkt_descr[pkt_index].isoc_pkt_status = cr; isoc_req->isoc_pkt_descr[pkt_index].isoc_pkt_actual_length = (GetTD_alen(uhcip, td) == ZERO_LENGTH) ? 0 : GetTD_alen(uhcip, td) + 1; } uhci_delete_isoc_td(uhcip, td); if (--isoc_xfer_info->num_tds != 0) { USB_DPRINTF_L3(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_handle_isoc_td: Number of TDs %d", isoc_xfer_info->num_tds); return; } tw->tw_claim = UHCI_INTR_HDLR_CLAIMED; if (tw->tw_direction == PID_IN) { uhci_sendup_td_message(uhcip, cr, tw); if ((uhci_handle_isoc_receive(uhcip, pp, tw)) != USB_SUCCESS) { USB_DPRINTF_L3(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_handle_isoc_td: Drop message"); } } else { /* update kstats only for OUT. sendup_td_msg() does it for IN */ uhci_do_byte_stats(uhcip, tw->tw_length, usb_pp->p_ep.bmAttributes, usb_pp->p_ep.bEndpointAddress); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_CR_OK); } rval = ddi_dma_unbind_handle(isoc_xfer_info->dma_handle); ASSERT(rval == DDI_SUCCESS); ddi_dma_mem_free(&isoc_xfer_info->mem_handle); ddi_dma_free_handle(&isoc_xfer_info->dma_handle); uhci_deallocate_tw(uhcip, pp, tw); } /* * uhci_handle_isoc_receive: * - Sends the isoc data to the client * - Inserts another isoc receive request */ static int uhci_handle_isoc_receive( uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw) { USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_handle_isoc_receive: tw = 0x%p", tw); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* * -- check for pipe state being polling before * inserting a new request. Check when is TD * de-allocation being done? (so we can reuse the same TD) */ if (uhci_start_isoc_receive_polling(uhcip, pp->pp_pipe_handle, (usb_isoc_req_t *)tw->tw_curr_xfer_reqp, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_handle_isoc_receive: receive polling failed"); return (USB_FAILURE); } return (USB_SUCCESS); } /* * uhci_delete_isoc_td: * - Delete from the outstanding command queue * - Delete from the tw queue * - Delete from the isoc queue * - Delete from the HOST CONTROLLER list */ static void uhci_delete_isoc_td(uhci_state_t *uhcip, uhci_td_t *td) { uint32_t starting_frame = td->starting_frame; if ((td->isoc_next == NULL) && (td->isoc_prev == NULL)) { SetFL32(uhcip, uhcip->uhci_frame_lst_tablep[starting_frame], GetTD32(uhcip, td->link_ptr)); uhcip->uhci_isoc_q_tailp[starting_frame] = 0; } else if (td->isoc_next == NULL) { td->isoc_prev->link_ptr = td->link_ptr; td->isoc_prev->isoc_next = NULL; uhcip->uhci_isoc_q_tailp[starting_frame] = td->isoc_prev; } else if (td->isoc_prev == NULL) { td->isoc_next->isoc_prev = NULL; SetFL32(uhcip, uhcip->uhci_frame_lst_tablep[starting_frame], GetTD32(uhcip, td->link_ptr)); } else { td->isoc_prev->isoc_next = td->isoc_next; td->isoc_next->isoc_prev = td->isoc_prev; td->isoc_prev->link_ptr = td->link_ptr; } uhci_delete_td(uhcip, td); } /* * uhci_send_isoc_receive * - Allocates usb_isoc_request * - Updates the isoc request * - Inserts the isoc td's into the HC processing list. */ int uhci_start_isoc_receive_polling( uhci_state_t *uhcip, usba_pipe_handle_data_t *ph, usb_isoc_req_t *isoc_req, usb_flags_t usb_flags) { int ii, error; size_t max_isoc_xfer_size, length; ushort_t isoc_pkt_count; uhci_pipe_private_t *pp = (uhci_pipe_private_t *)ph->p_hcd_private; usb_isoc_pkt_descr_t *isoc_pkt_descr; USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_start_isoc_receive_polling: usb_flags = %x", usb_flags); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); max_isoc_xfer_size = ph->p_ep.wMaxPacketSize * UHCI_MAX_ISOC_PKTS; if (isoc_req) { isoc_pkt_descr = isoc_req->isoc_pkt_descr; isoc_pkt_count = isoc_req->isoc_pkts_count; } else { isoc_pkt_descr = ((usb_isoc_req_t *) pp->pp_client_periodic_in_reqp)->isoc_pkt_descr; isoc_pkt_count = ((usb_isoc_req_t *) pp->pp_client_periodic_in_reqp)->isoc_pkts_count; } for (ii = 0, length = 0; ii < isoc_pkt_count; ii++) { length += isoc_pkt_descr->isoc_pkt_length; isoc_pkt_descr++; } /* Check the size of isochronous request */ if (length > max_isoc_xfer_size) { USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_start_isoc_receive_polling: " "Max isoc request size = %lx, Given isoc req size = %lx", max_isoc_xfer_size, length); return (USB_FAILURE); } /* Add the TD into the Host Controller's isoc list */ if ((error = uhci_insert_isoc_td(uhcip, ph, isoc_req, length, usb_flags)) != USB_SUCCESS) { usb_free_isoc_req(isoc_req); } return (error); } /* * uhci_remove_isoc_tds_tws * This routine scans the pipe and removes all the td's * and transfer wrappers and deallocates the memory * associated with those td's and tw's. */ void uhci_remove_isoc_tds_tws(uhci_state_t *uhcip, uhci_pipe_private_t *pp) { uint_t rval; uhci_td_t *tmp_td, *td_head; usb_isoc_req_t *isoc_req; uhci_trans_wrapper_t *tmp_tw, *tw_head; USB_DPRINTF_L4(PRINT_MASK_ISOC, uhcip->uhci_log_hdl, "uhci_remove_isoc_tds_tws: pp = %p", (void *)pp); tw_head = pp->pp_tw_head; while (tw_head) { tmp_tw = tw_head; tw_head = tw_head->tw_next; td_head = tmp_tw->tw_hctd_head; if (tmp_tw->tw_direction == PID_IN) { uhci_deallocate_periodic_in_resource(uhcip, pp, tmp_tw); } else if (tmp_tw->tw_direction == PID_OUT) { uhci_hcdi_callback(uhcip, pp, pp->pp_pipe_handle, tmp_tw, USB_CR_FLUSHED); } while (td_head) { tmp_td = td_head; td_head = td_head->tw_td_next; uhci_delete_isoc_td(uhcip, tmp_td); } isoc_req = (usb_isoc_req_t *)tmp_tw->tw_isoc_req; if (isoc_req) { usb_free_isoc_req(isoc_req); } ASSERT(tmp_tw->tw_hctd_head == NULL); if (tmp_tw->tw_xfer_info.dma_handle) { rval = ddi_dma_unbind_handle(tmp_tw->tw_xfer_info. dma_handle); ASSERT(rval == DDI_SUCCESS); ddi_dma_mem_free(&tmp_tw->tw_xfer_info.mem_handle); ddi_dma_free_handle(&tmp_tw->tw_xfer_info.dma_handle); } uhci_deallocate_tw(uhcip, pp, tmp_tw); } } /* * uhci_isoc_update_sw_frame_number() * to avoid code duplication, call uhci_get_sw_frame_number() */ void uhci_isoc_update_sw_frame_number(uhci_state_t *uhcip) { (void) uhci_get_sw_frame_number(uhcip); } /* * uhci_get_sw_frame_number: * Hold the uhci_int_mutex before calling this routine. */ uint64_t uhci_get_sw_frame_number(uhci_state_t *uhcip) { uint64_t sw_frnum, hw_frnum, current_frnum; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); sw_frnum = uhcip->uhci_sw_frnum; hw_frnum = Get_OpReg16(FRNUM); /* * Check bit 10 in the software counter and hardware frame counter. * If both are same, then don't increment the software frame counter * (Bit 10 of hw frame counter toggle for every 1024 frames) * The lower 11 bits of software counter contains the hardware frame * counter value. The MSB (bit 10) of software counter is incremented * for every 1024 frames either here or in get frame number routine. */ if ((sw_frnum & UHCI_BIT_10_MASK) == (hw_frnum & UHCI_BIT_10_MASK)) { /* The MSB of hw counter did not toggle */ current_frnum = ((sw_frnum & (SW_FRNUM_MASK)) | hw_frnum); } else { /* * The hw counter wrapped around. And the interrupt handler * did not get a chance to update the sw frame counter. * So, update the sw frame counter and return correct frame no. */ sw_frnum >>= UHCI_SIZE_OF_HW_FRNUM - 1; current_frnum = ((++sw_frnum << (UHCI_SIZE_OF_HW_FRNUM - 1)) | hw_frnum); } uhcip->uhci_sw_frnum = current_frnum; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_get_sw_frame_number: sw=%ld hd=%ld", uhcip->uhci_sw_frnum, hw_frnum); return (current_frnum); } /* * uhci_cmd_timeout_hdlr: * This routine will get called for every second. It checks for * timed out control commands/bulk commands. Timeout any commands * that exceeds the time out period specified by the pipe policy. */ void uhci_cmd_timeout_hdlr(void *arg) { uint_t flag = B_FALSE; uhci_td_t *head, *tmp_td; uhci_state_t *uhcip = (uhci_state_t *)arg; uhci_pipe_private_t *pp; /* * Check whether any of the control xfers are timed out. * If so, complete those commands with time out as reason. */ mutex_enter(&uhcip->uhci_int_mutex); head = uhcip->uhci_outst_tds_head; while (head) { /* * If timeout out is zero, then dont timeout command. */ if (head->tw->tw_timeout_cnt == 0) { head = head->outst_td_next; continue; } if (!(head->tw->tw_flags & TW_TIMEOUT_FLAG)) { head->tw->tw_flags |= TW_TIMEOUT_FLAG; --head->tw->tw_timeout_cnt; } /* only do it for bulk and control TDs */ if ((head->tw->tw_timeout_cnt == 0) && (head->tw->tw_handle_td != uhci_handle_isoc_td)) { USB_DPRINTF_L3(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "Command timed out: td = %p", (void *)head); head->tw->tw_claim = UHCI_TIMEOUT_HDLR_CLAIMED; /* * Check finally whether the command completed */ if (GetTD_status(uhcip, head) & UHCI_TD_ACTIVE) { SetTD32(uhcip, head->link_ptr, GetTD32(uhcip, head->link_ptr) | HC_END_OF_LIST); pp = head->tw->tw_pipe_private; SetQH32(uhcip, pp->pp_qh->element_ptr, GetQH32(uhcip, pp->pp_qh->element_ptr) | HC_END_OF_LIST); } flag = B_TRUE; } head = head->outst_td_next; } if (flag) { (void) uhci_wait_for_sof(uhcip); } head = uhcip->uhci_outst_tds_head; while (head) { if (head->tw->tw_flags & TW_TIMEOUT_FLAG) { head->tw->tw_flags &= ~TW_TIMEOUT_FLAG; } if (head->tw->tw_claim == UHCI_TIMEOUT_HDLR_CLAIMED) { head->tw->tw_claim = UHCI_NOT_CLAIMED; tmp_td = head->tw->tw_hctd_head; while (tmp_td) { SetTD_status(uhcip, tmp_td, UHCI_TD_CRC_TIMEOUT); tmp_td = tmp_td->tw_td_next; } } head = head->outst_td_next; } /* * Process the td which was completed before shifting from normal * mode to polled mode */ if (uhcip->uhci_polled_flag == UHCI_POLLED_FLAG_TRUE) { uhci_process_submitted_td_queue(uhcip); uhcip->uhci_polled_flag = UHCI_POLLED_FLAG_FALSE; } else if (flag) { /* Process the completed/timed out commands */ uhci_process_submitted_td_queue(uhcip); } /* Re-register the control/bulk/intr commands' timeout handler */ if (uhcip->uhci_cmd_timeout_id) { uhcip->uhci_cmd_timeout_id = timeout(uhci_cmd_timeout_hdlr, (void *)uhcip, UHCI_ONE_SECOND); } mutex_exit(&uhcip->uhci_int_mutex); } /* * uhci_wait_for_sof: * Wait for the start of the next frame (implying any changes made in the * lattice have now taken effect). * To be sure this is the case, we wait for the completion of the current * frame (which might have already been pending), then another complete * frame to ensure everything has taken effect. */ int uhci_wait_for_sof(uhci_state_t *uhcip) { int n; ushort_t cmd_reg; usb_frame_number_t before_frame_number, after_frame_number; clock_t time, rval; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_wait_for_sof: uhcip = %p", uhcip); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); before_frame_number = uhci_get_sw_frame_number(uhcip); for (n = 0; n < MAX_SOF_WAIT_COUNT; n++) { SetTD_ioc(uhcip, uhcip->uhci_sof_td, 1); uhcip->uhci_cv_signal = B_TRUE; time = ddi_get_lbolt() + UHCI_ONE_SECOND; rval = cv_timedwait(&uhcip->uhci_cv_SOF, &uhcip->uhci_int_mutex, time); after_frame_number = uhci_get_sw_frame_number(uhcip); if ((rval == -1) && (after_frame_number <= before_frame_number)) { cmd_reg = Get_OpReg16(USBCMD); Set_OpReg16(USBCMD, (cmd_reg | USBCMD_REG_HC_RUN)); Set_OpReg16(USBINTR, ENABLE_ALL_INTRS); after_frame_number = uhci_get_sw_frame_number(uhcip); } before_frame_number = after_frame_number; } SetTD_ioc(uhcip, uhcip->uhci_sof_td, 0); return (uhcip->uhci_cv_signal ? USB_FAILURE : USB_SUCCESS); } /* * uhci_allocate_periodic_in_resource: * Allocate interrupt/isochronous request structure for the * interrupt/isochronous IN transfer. */ int uhci_allocate_periodic_in_resource( uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw, usb_flags_t flags) { size_t length = 0; usb_opaque_t client_periodic_in_reqp; usb_intr_req_t *cur_intr_req; usb_isoc_req_t *curr_isoc_reqp; usba_pipe_handle_data_t *ph = pp->pp_pipe_handle; USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_allocate_periodic_in_resource:\n\t" "ph = 0x%p, pp = 0x%p, tw = 0x%p, flags = 0x%x", ph, pp, tw, flags); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* Check the current periodic in request pointer */ if (tw->tw_curr_xfer_reqp) { USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_allocate_periodic_in_resource: Interrupt " "request structure already exists: " "allocation failed"); return (USB_SUCCESS); } /* Get the client periodic in request pointer */ client_periodic_in_reqp = pp->pp_client_periodic_in_reqp; /* * If it a periodic IN request and periodic request is NULL, * allocate corresponding usb periodic IN request for the * current periodic polling request and copy the information * from the saved periodic request structure. */ if (UHCI_XFER_TYPE(&ph->p_ep) == USB_EP_ATTR_INTR) { /* Get the interrupt transfer length */ length = ((usb_intr_req_t *)client_periodic_in_reqp)-> intr_len; cur_intr_req = usba_hcdi_dup_intr_req(ph->p_dip, (usb_intr_req_t *)client_periodic_in_reqp, length, flags); if (cur_intr_req == NULL) { USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_allocate_periodic_in_resource: Interrupt " "request structure allocation failed"); return (USB_NO_RESOURCES); } /* Check and save the timeout value */ tw->tw_timeout_cnt = (cur_intr_req->intr_attributes & USB_ATTRS_ONE_XFER) ? cur_intr_req->intr_timeout: 0; tw->tw_curr_xfer_reqp = (usb_opaque_t)cur_intr_req; tw->tw_length = cur_intr_req->intr_len; } else { ASSERT(client_periodic_in_reqp != NULL); if ((curr_isoc_reqp = usba_hcdi_dup_isoc_req(ph->p_dip, (usb_isoc_req_t *)client_periodic_in_reqp, flags)) == NULL) { USB_DPRINTF_L2(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_allocate_periodic_in_resource: Isochronous " "request structure allocation failed"); return (USB_NO_RESOURCES); } /* * Save the client's isochronous request pointer and * length of isochronous transfer in transfer wrapper. * The dup'ed request is saved in pp_client_periodic_in_reqp */ tw->tw_curr_xfer_reqp = (usb_opaque_t)pp->pp_client_periodic_in_reqp; pp->pp_client_periodic_in_reqp = (usb_opaque_t)curr_isoc_reqp; tw->tw_length = curr_isoc_reqp->isoc_pkts_length; } mutex_enter(&ph->p_mutex); ph->p_req_count++; mutex_exit(&ph->p_mutex); return (USB_SUCCESS); } /* * uhci_deallocate_periodic_in_resource: * Deallocate interrupt/isochronous request structure for the * interrupt/isochronous IN transfer. */ void uhci_deallocate_periodic_in_resource( uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_trans_wrapper_t *tw) { usb_opaque_t curr_xfer_reqp; usba_pipe_handle_data_t *ph = pp->pp_pipe_handle; USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_deallocate_periodic_in_resource: " "pp = 0x%p tw = 0x%p", pp, tw); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); curr_xfer_reqp = tw->tw_curr_xfer_reqp; if (curr_xfer_reqp) { /* * Reset periodic in request usb isoch * packet request pointers to null. */ tw->tw_curr_xfer_reqp = NULL; tw->tw_isoc_req = NULL; mutex_enter(&ph->p_mutex); ph->p_req_count--; mutex_exit(&ph->p_mutex); /* * Free pre-allocated interrupt or isochronous requests. */ switch (UHCI_XFER_TYPE(&ph->p_ep)) { case USB_EP_ATTR_INTR: usb_free_intr_req((usb_intr_req_t *)curr_xfer_reqp); break; case USB_EP_ATTR_ISOCH: usb_free_isoc_req((usb_isoc_req_t *)curr_xfer_reqp); break; } } } /* * uhci_hcdi_callback() * convenience wrapper around usba_hcdi_callback() */ void uhci_hcdi_callback(uhci_state_t *uhcip, uhci_pipe_private_t *pp, usba_pipe_handle_data_t *ph, uhci_trans_wrapper_t *tw, usb_cr_t cr) { usb_opaque_t curr_xfer_reqp; USB_DPRINTF_L4(PRINT_MASK_HCDI, uhcip->uhci_log_hdl, "uhci_hcdi_callback: ph = 0x%p, tw = 0x%p, cr = 0x%x", ph, tw, cr); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); if (tw && tw->tw_curr_xfer_reqp) { curr_xfer_reqp = tw->tw_curr_xfer_reqp; tw->tw_curr_xfer_reqp = NULL; tw->tw_isoc_req = NULL; } else { ASSERT(pp->pp_client_periodic_in_reqp != NULL); curr_xfer_reqp = pp->pp_client_periodic_in_reqp; pp->pp_client_periodic_in_reqp = NULL; } ASSERT(curr_xfer_reqp != NULL); mutex_exit(&uhcip->uhci_int_mutex); usba_hcdi_cb(ph, curr_xfer_reqp, cr); mutex_enter(&uhcip->uhci_int_mutex); } #ifdef DEBUG static void uhci_print_td(uhci_state_t *uhcip, uhci_td_t *td) { uint_t *ptr = (uint_t *)td; #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif USB_DPRINTF_L3(PRINT_MASK_DUMPING, uhcip->uhci_log_hdl, "\tDWORD 1 0x%x\t DWORD 2 0x%x", ptr[0], ptr[1]); USB_DPRINTF_L3(PRINT_MASK_DUMPING, uhcip->uhci_log_hdl, "\tDWORD 3 0x%x\t DWORD 4 0x%x", ptr[2], ptr[3]); USB_DPRINTF_L3(PRINT_MASK_DUMPING, uhcip->uhci_log_hdl, "\tBytes xfered = %d", td->tw->tw_bytes_xfered); USB_DPRINTF_L3(PRINT_MASK_DUMPING, uhcip->uhci_log_hdl, "\tBytes Pending = %d", td->tw->tw_bytes_pending); USB_DPRINTF_L3(PRINT_MASK_DUMPING, uhcip->uhci_log_hdl, "Queue Head Details:"); uhci_print_qh(uhcip, td->tw->tw_pipe_private->pp_qh); #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif } static void uhci_print_qh(uhci_state_t *uhcip, queue_head_t *qh) { uint_t *ptr = (uint_t *)qh; USB_DPRINTF_L3(PRINT_MASK_DUMPING, uhcip->uhci_log_hdl, "\tLink Ptr = %x Element Ptr = %x", ptr[0], ptr[1]); } #endif