/* * 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 Architecture (USBA) and the Host Controller (HC). The interface to * the Host Controller is defined by the Universal Host Controller Interface. * This file contains code for auto-configuration entry points and interrupt * handling. */ #include #include #include /* * Prototype Declarations for cb_ops and dev_ops */ static int uhci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int uhci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int uhci_open(dev_t *devp, int flags, int otyp, cred_t *credp); static int uhci_close(dev_t dev, int flag, int otyp, cred_t *credp); static int uhci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp); static int uhci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd); static int uhci_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static struct cb_ops uhci_cb_ops = { uhci_open, /* Open */ uhci_close, /* Close */ nodev, /* Strategy */ nodev, /* Print */ nodev, /* Dump */ nodev, /* Read */ nodev, /* Write */ uhci_ioctl, /* Ioctl */ nodev, /* Devmap */ nodev, /* Mmap */ nodev, /* Segmap */ nochpoll, /* Poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* Streamtab */ D_MP /* Driver compatibility flag */ }; static struct dev_ops uhci_ops = { DEVO_REV, /* Devo_rev */ 0, /* Refcnt */ uhci_info, /* Info */ nulldev, /* Identify */ nulldev, /* Probe */ uhci_attach, /* Attach */ uhci_detach, /* Detach */ uhci_reset, /* Reset */ &uhci_cb_ops, /* Driver operations */ &usba_hubdi_busops, /* Bus operations */ NULL /* Power */ }; static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ "USB UHCI Controller Driver %I%", /* Name of the module. */ &uhci_ops, /* Driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; /* * Globals */ void *uhci_statep; static uint_t uhci_errlevel = USB_LOG_L2; static uint_t uhci_errmask = PRINT_MASK_ALL; static uint_t uhci_instance_debug = (uint_t)-1; uint_t uhci_td_pool_size = 256; /* Num TDs */ uint_t uhci_qh_pool_size = 130; /* Num QHs */ ushort_t uhci_tree_bottom_nodes[NUM_FRAME_LST_ENTRIES]; /* * tunable, delay during attach in seconds */ static int uhci_attach_wait = 1; /* function prototypes */ static void uhci_handle_intr_td_errors(uhci_state_t *uhcip, uhci_td_t *td, uhci_trans_wrapper_t *tw, uhci_pipe_private_t *pp); static void uhci_handle_one_xfer_completion(uhci_state_t *uhcip, usb_cr_t usb_err, uhci_td_t *td); static uint_t uhci_intr(caddr_t arg); static int uhci_cleanup(uhci_state_t *uhcip); int _init(void) { int error; ushort_t i, j, k, *temp, num_of_nodes; /* Initialize the soft state structures */ if ((error = ddi_soft_state_init(&uhci_statep, sizeof (uhci_state_t), UHCI_MAX_INSTS)) != 0) { return (error); } /* Install the loadable module */ if ((error = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&uhci_statep); return (error); } /* * Build the tree bottom shared by all instances */ temp = kmem_zalloc(NUM_FRAME_LST_ENTRIES * 2, KM_SLEEP); num_of_nodes = 1; for (i = 0; i < log_2(NUM_FRAME_LST_ENTRIES); i++) { for (j = 0, k = 0; k < num_of_nodes; k++, j++) { uhci_tree_bottom_nodes[j++] = temp[k]; uhci_tree_bottom_nodes[j] = temp[k] + pow_2(i); } num_of_nodes *= 2; for (k = 0; k < num_of_nodes; k++) temp[k] = uhci_tree_bottom_nodes[k]; } kmem_free(temp, (NUM_FRAME_LST_ENTRIES*2)); return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; error = mod_remove(&modlinkage); if (error == 0) { /* Release per module resources */ ddi_soft_state_fini(&uhci_statep); } return (error); } /* * Host Controller Driver (HCD) Auto configuration entry points */ /* * Function Name : uhci_attach: * Description : Attach entry point - called by the Kernel. * Allocates of per controller data structure. * Initializes the controller. * Output : DDI_SUCCESS / DDI_FAILURE */ static int uhci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance; int type, count, actual, ret; uint_t intr_pri; uhci_state_t *uhcip = NULL; usba_hcdi_register_args_t hcdi_args; USB_DPRINTF_L4(PRINT_MASK_ATTA, NULL, "uhci_attach:"); switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: default: return (DDI_FAILURE); } /* Get the instance and create soft state */ instance = ddi_get_instance(dip); /* Allocate the soft state structure for this instance of the driver */ if (ddi_soft_state_zalloc(uhci_statep, instance) != 0) { return (DDI_FAILURE); } if ((uhcip = ddi_get_soft_state(uhci_statep, instance)) == NULL) { return (DDI_FAILURE); } uhcip->uhci_log_hdl = usb_alloc_log_hdl(dip, "uhci", &uhci_errlevel, &uhci_errmask, &uhci_instance_debug, 0); /* Save the dip and instance */ uhcip->uhci_dip = dip; uhcip->uhci_instance = instance; /* Semaphore to serialize opens and closes */ sema_init(&uhcip->uhci_ocsem, 1, NULL, SEMA_DRIVER, NULL); ret = ddi_intr_get_supported_types(dip, &type); if ((ret != DDI_SUCCESS) || (!(type & DDI_INTR_TYPE_FIXED))) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "Fixed type interrupts not supported"); usb_free_log_hdl(uhcip->uhci_log_hdl); ddi_soft_state_free(uhci_statep, instance); return (DDI_FAILURE); } ret = ddi_intr_get_nintrs(dip, DDI_INTR_TYPE_FIXED, &count); /* * Fixed interrupts can only have one interrupt. Check to make * sure that number of supported interrupts and number of * available interrupts are both equal to 1. */ if ((ret != DDI_SUCCESS) || (count != 1)) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "No Fixed interrupts found"); usb_free_log_hdl(uhcip->uhci_log_hdl); ddi_soft_state_free(uhci_statep, instance); return (DDI_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "Supported Intr types = 0x%x, #Intrs = 0x%x", type, count); uhcip->uhci_htable = kmem_zalloc(count * sizeof (ddi_intr_handle_t), KM_SLEEP); ret = ddi_intr_alloc(dip, uhcip->uhci_htable, DDI_INTR_TYPE_FIXED, 0, count, &actual, 0); if ((ret != DDI_SUCCESS) || (actual != 1)) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "ddi_intr_alloc failed 0x%x", ret); kmem_free(uhcip->uhci_htable, sizeof (ddi_intr_handle_t)); usb_free_log_hdl(uhcip->uhci_log_hdl); ddi_soft_state_free(uhci_statep, instance); return (DDI_FAILURE); } ret = ddi_intr_get_pri(uhcip->uhci_htable[0], &intr_pri); if (ret != DDI_SUCCESS) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "ddi_intr_get_pri failed 0x%x", ret); (void) ddi_intr_free(uhcip->uhci_htable[0]); kmem_free(uhcip->uhci_htable, sizeof (ddi_intr_handle_t)); usb_free_log_hdl(uhcip->uhci_log_hdl); ddi_soft_state_free(uhci_statep, instance); return (DDI_FAILURE); } USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "Supported Interrupt priority = 0x%x", intr_pri); if (intr_pri >= ddi_intr_get_hilevel_pri()) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "Hi level interrupt not supported"); (void) ddi_intr_free(uhcip->uhci_htable[0]); kmem_free(uhcip->uhci_htable, sizeof (ddi_intr_handle_t)); usb_free_log_hdl(uhcip->uhci_log_hdl); ddi_soft_state_free(uhci_statep, instance); return (DDI_FAILURE); } /* Initialize the mutex */ mutex_init(&uhcip->uhci_int_mutex, NULL, MUTEX_DRIVER, (void *)(uintptr_t)intr_pri); /* Create prototype condition variable */ cv_init(&uhcip->uhci_cv_SOF, NULL, CV_DRIVER, NULL); ret = ddi_intr_add_handler(uhcip->uhci_htable[0], (ddi_intr_handler_t *)uhci_intr, (caddr_t)uhcip, NULL); if (ret != DDI_SUCCESS) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "ddi_intr_add_handler failed 0x%x", ret); goto fail; } /* Initialize the DMA attributes */ uhci_set_dma_attributes(uhcip); /* Initialize the kstat structures */ uhci_create_stats(uhcip); /* Create the td and ed pools */ if (uhci_allocate_pools(uhcip) != USB_SUCCESS) { goto fail; } /* Map the registers */ if (uhci_map_regs(uhcip) != USB_SUCCESS) { goto fail; } /* Set the flag that uhci controller has not been initialized. */ uhcip->uhci_ctlr_init_flag = B_FALSE; /* finally enable the interrupt */ ret = ddi_intr_enable(uhcip->uhci_htable[0]); if (ret != DDI_SUCCESS) { USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "ddi_intr_enable failed 0x%x", ret); goto fail; } /* Initialize the controller */ if (uhci_init_ctlr(uhcip) != USB_SUCCESS) { goto fail; } /* * At this point, the hardware will be okay. * Initialize the usba_hcdi structure */ uhcip->uhci_hcdi_ops = uhci_alloc_hcdi_ops(uhcip); /* * Make this HCD instance known to USBA * (dma_attr must be passed for USBA busctl's) */ hcdi_args.usba_hcdi_register_version = HCDI_REGISTER_VERSION; hcdi_args.usba_hcdi_register_dip = dip; hcdi_args.usba_hcdi_register_ops = uhcip->uhci_hcdi_ops; hcdi_args.usba_hcdi_register_dma_attr = &uhcip->uhci_dma_attr; hcdi_args.usba_hcdi_register_iblock_cookie = (ddi_iblock_cookie_t)(uintptr_t)intr_pri; if (usba_hcdi_register(&hcdi_args, 0) != USB_SUCCESS) { goto fail; } #ifndef __sparc /* * On NCR system, the driver seen failure of some commands * while booting. This delay mysteriously solved the problem. */ delay(drv_usectohz(uhci_attach_wait*1000000)); #endif /* * Create another timeout handler to check whether any * control/bulk/interrupt commands failed. * This gets called every second. */ uhcip->uhci_cmd_timeout_id = timeout(uhci_cmd_timeout_hdlr, (void *)uhcip, UHCI_ONE_SECOND); mutex_enter(&uhcip->uhci_int_mutex); /* * Set HcInterruptEnable to enable all interrupts except Root * Hub Status change and SOF interrupts. */ Set_OpReg16(USBINTR, ENABLE_ALL_INTRS); /* Test the SOF interrupt */ if (uhci_wait_for_sof(uhcip) != USB_SUCCESS) { USB_DPRINTF_L0(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "No SOF interrupts have been received, this USB UHCI host" " controller is unusable"); mutex_exit(&uhcip->uhci_int_mutex); goto fail; } mutex_exit(&uhcip->uhci_int_mutex); /* This should be the last step which might fail during attaching */ if (uhci_init_root_hub(uhcip) != USB_SUCCESS) { goto fail; } /* Display information in the banner */ ddi_report_dev(dip); USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_attach successful"); return (DDI_SUCCESS); fail: USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "failed to attach"); (void) uhci_cleanup(uhcip); return (DDI_FAILURE); } /* * Function Name: uhci_detach * Description: Detach entry point - called by the Kernel. * Deallocates all the memory * Unregisters the interrupt handle and other resources. * Output: DDI_SUCCESS / DDI_FAILURE */ static int uhci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { uhci_state_t *uhcip = uhci_obtain_state(dip); USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_detach:"); switch (cmd) { case DDI_DETACH: return (uhci_cleanup(uhcip) == USB_SUCCESS ? DDI_SUCCESS : DDI_FAILURE); case DDI_SUSPEND: USB_DPRINTF_L1(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_detach: Suspend not supported"); return (DDI_FAILURE); default: return (DDI_FAILURE); } } /* * Function Name: uhci_reset * Description: Reset entry point - called by the Kernel * on the way down. * The Toshiba laptop has been observed to hang * on reboot when BIOS is set to suspend/resume. * The resetting uhci on the way down solves the * problem. * Output: DDI_SUCCESS / DDI_FAILURE */ /* ARGSUSED */ static int uhci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) { uhci_state_t *uhcip = uhci_obtain_state(dip); /* Disable all HC ED list processing */ Set_OpReg16(USBINTR, DISABLE_ALL_INTRS); Set_OpReg16(USBCMD, 0); return (DDI_SUCCESS); } /* * uhci_info: */ /* ARGSUSED */ static int uhci_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { dev_t dev; int instance; int error = DDI_FAILURE; uhci_state_t *uhcip; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: dev = (dev_t)arg; instance = UHCI_UNIT(dev); uhcip = ddi_get_soft_state(uhci_statep, instance); if (uhcip != NULL) { *result = (void *)uhcip->uhci_dip; if (*result != NULL) { error = DDI_SUCCESS; } } else { *result = NULL; } break; case DDI_INFO_DEVT2INSTANCE: dev = (dev_t)arg; instance = UHCI_UNIT(dev); *result = (void *)(uintptr_t)instance; error = DDI_SUCCESS; break; default: break; } return (error); } /* * uhci_cleanup: * Cleanup on attach failure or detach */ static int uhci_cleanup(uhci_state_t *uhcip) { USB_DPRINTF_L4(PRINT_MASK_ATTA, uhcip->uhci_log_hdl, "uhci_cleanup:"); if (usba_hubdi_unbind_root_hub(uhcip->uhci_dip) != USB_SUCCESS) { return (USB_FAILURE); } mutex_enter(&uhcip->uhci_int_mutex); if (uhcip->uhci_cmd_timeout_id) { timeout_id_t timeout_id = uhcip->uhci_cmd_timeout_id; uhcip->uhci_cmd_timeout_id = 0; mutex_exit(&uhcip->uhci_int_mutex); (void) untimeout(timeout_id); mutex_enter(&uhcip->uhci_int_mutex); } uhci_uninit_ctlr(uhcip); mutex_exit(&uhcip->uhci_int_mutex); /* do interrupt cleanup */ if (uhcip->uhci_htable[0]) { /* disable interrupt */ (void) ddi_intr_disable(uhcip->uhci_htable[0]); /* remove interrupt handler */ (void) ddi_intr_remove_handler(uhcip->uhci_htable[0]); /* free interrupt handle */ (void) ddi_intr_free(uhcip->uhci_htable[0]); /* free memory */ kmem_free(uhcip->uhci_htable, sizeof (ddi_intr_handle_t)); } mutex_enter(&uhcip->uhci_int_mutex); usba_hcdi_unregister(uhcip->uhci_dip); uhci_unmap_regs(uhcip); uhci_free_pools(uhcip); mutex_exit(&uhcip->uhci_int_mutex); mutex_destroy(&uhcip->uhci_int_mutex); cv_destroy(&uhcip->uhci_cv_SOF); sema_destroy(&uhcip->uhci_ocsem); /* cleanup kstat structures */ uhci_destroy_stats(uhcip); usba_free_hcdi_ops(uhcip->uhci_hcdi_ops); usb_free_log_hdl(uhcip->uhci_log_hdl); ddi_prop_remove_all(uhcip->uhci_dip); ddi_soft_state_free(uhci_statep, uhcip->uhci_instance); return (USB_SUCCESS); } /* * uhci_intr: * uhci interrupt handling routine. */ static uint_t uhci_intr(caddr_t arg) { ushort_t intr_status, cmd_reg; uhci_state_t *uhcip = (uhci_state_t *)arg; mutex_enter(&uhcip->uhci_int_mutex); /* Get the status of the interrupts */ intr_status = Get_OpReg16(USBSTS); USB_DPRINTF_L4(PRINT_MASK_INTR, uhcip->uhci_log_hdl, "uhci_intr: intr_status = %x", intr_status); /* * If the intr is not from our controller, just return unclaimed */ if (!(intr_status & UHCI_INTR_MASK)) { USB_DPRINTF_L3(PRINT_MASK_INTR, uhcip->uhci_log_hdl, "uhci_intr: unclaimed interrupt"); mutex_exit(&uhcip->uhci_int_mutex); return (DDI_INTR_UNCLAIMED); } /* Update kstat values */ uhci_do_intrs_stats(uhcip, intr_status); /* Acknowledge the interrupt */ Set_OpReg16(USBSTS, intr_status); /* * If uhci controller has not been initialized, just clear the * interrupter status and return claimed. */ if (uhcip->uhci_ctlr_init_flag != B_TRUE) { USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl, "uhci_intr: uhci controller has not been fully" "initialized"); mutex_exit(&uhcip->uhci_int_mutex); return (DDI_INTR_CLAIMED); } /* * We configured the hw incorrectly, disable future interrupts. */ if ((intr_status & USBSTS_REG_HOST_SYS_ERR)) { USB_DPRINTF_L1(PRINT_MASK_INTR, uhcip->uhci_log_hdl, "uhci_intr: Sys Err Disabling Interrupt"); Set_OpReg16(USBINTR, DISABLE_ALL_INTRS); mutex_exit(&uhcip->uhci_int_mutex); return (DDI_INTR_CLAIMED); } /* * Check whether a frame number overflow occurred. * if so, update the sw frame number. */ uhci_isoc_update_sw_frame_number(uhcip); /* * Check whether any commands got completed. If so, process them. */ uhci_process_submitted_td_queue(uhcip); /* * This should not occur. It occurs only if a HC controller * experiences internal problem. */ if (intr_status & USBSTS_REG_HC_HALTED) { USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl, "uhci_intr: Controller halted"); cmd_reg = Get_OpReg16(USBCMD); Set_OpReg16(USBCMD, (cmd_reg | USBCMD_REG_HC_RUN)); } /* * Wake up all the threads which are waiting for the Start of Frame */ if (uhcip->uhci_cv_signal == B_TRUE) { cv_broadcast(&uhcip->uhci_cv_SOF); uhcip->uhci_cv_signal = B_FALSE; } mutex_exit(&uhcip->uhci_int_mutex); return (DDI_INTR_CLAIMED); } /* * uhci_process_submitted_td_queue: * Traverse thru the submitted queue and process the completed ones. */ void uhci_process_submitted_td_queue(uhci_state_t *uhcip) { uhci_td_t *head = uhcip->uhci_outst_tds_head; uhci_trans_wrapper_t *tw; while (head != NULL) { if ((!(GetTD_status(uhcip, head) & UHCI_TD_ACTIVE)) && (head->tw->tw_claim == UHCI_NOT_CLAIMED)) { tw = head->tw; /* * Call the corresponding handle_td routine */ (*tw->tw_handle_td)(uhcip, head); /* restart at the beginning again */ head = uhcip->uhci_outst_tds_head; } else { head = head->outst_td_next; } } } /* * uhci_handle_intr_td: * handles the completed interrupt transfer TD's. */ void uhci_handle_intr_td(uhci_state_t *uhcip, uhci_td_t *td) { usb_req_attrs_t attrs; uint_t bytes_xfered; usb_cr_t usb_err; uhci_trans_wrapper_t *tw = td->tw; uhci_pipe_private_t *pp = tw->tw_pipe_private; usb_intr_req_t *intr_reqp = (usb_intr_req_t *)tw->tw_curr_xfer_reqp; usba_pipe_handle_data_t *ph = pp->pp_pipe_handle; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_intr_td: intr_reqp = 0x%p", (void *)intr_reqp); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); /* set tw->tw_claim flag, so that nobody else works on this td. */ tw->tw_claim = UHCI_INTR_HDLR_CLAIMED; /* Interrupt OUT */ if (UHCI_XFER_DIR(&ph->p_ep) == USB_EP_DIR_OUT) { /* process errors first */ usb_err = uhci_parse_td_error(uhcip, pp, td); /* get the actual xfered data size */ bytes_xfered = GetTD_alen(uhcip, td); /* check data underrun error */ if ((usb_err == USB_CR_OK) && (bytes_xfered != GetTD_mlen(uhcip, td))) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_intr_td:" " Intr out pipe, data underrun occurred"); usb_err = USB_CR_DATA_UNDERRUN; } bytes_xfered = (bytes_xfered == ZERO_LENGTH) ? 0 : bytes_xfered+1; tw->tw_bytes_xfered += bytes_xfered; uhci_do_byte_stats(uhcip, tw->tw_bytes_xfered, ph->p_ep.bmAttributes, ph->p_ep.bEndpointAddress); /* * If error occurred or all data xfered, delete the current td, * free tw, do the callback. Otherwise wait for the next td. */ if (usb_err != USB_CR_OK) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_intr_td: Intr out pipe error"); /* update the element pointer */ SetQH32(uhcip, pp->pp_qh->element_ptr, GetTD32( uhcip, tw->tw_hctd_tail->link_ptr)); } else if (tw->tw_bytes_xfered == tw->tw_length) { /* all data xfered */ USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_intr_td: Intr out pipe," " all data xfered"); } else { /* remove the current td and wait for the next one. */ uhci_delete_td(uhcip, td); tw->tw_claim = UHCI_NOT_CLAIMED; return; } uhci_delete_td(uhcip, td); uhci_hcdi_callback(uhcip, pp, ph, tw, usb_err); uhci_deallocate_tw(uhcip, tw->tw_pipe_private, tw); return; } /* Interrupt IN */ /* Get the actual received data size */ tw->tw_bytes_xfered = GetTD_alen(uhcip, td); if (tw->tw_bytes_xfered == ZERO_LENGTH) { tw->tw_bytes_xfered = 0; } else { tw->tw_bytes_xfered++; } /* process errors first */ if (GetTD_status(uhcip, td) & TD_STATUS_MASK) { SetQH32(uhcip, pp->pp_qh->element_ptr, GetTD32(uhcip, td->link_ptr)); uhci_handle_intr_td_errors(uhcip, td, tw, pp); return; } /* * Check for data underruns. * For data underrun case, the host controller does not update * element pointer. So, we update here. */ if (GetTD_alen(uhcip, td) != GetTD_mlen(uhcip, td)) { SetQH32(uhcip, pp->pp_qh->element_ptr, GetTD32(uhcip, td->link_ptr)); } /* * Call uhci_sendup_td_message to send message upstream. * The function uhci_sendup_td_message returns USB_NO_RESOURCES * if allocb fails and also sends error message to upstream by * calling USBA callback function. Under error conditions just * drop the current message. */ /* Get the interrupt xfer attributes */ attrs = intr_reqp->intr_attributes; /* * Check usb flag whether USB_FLAGS_ONE_XFER flag is set * and if so, free duplicate request. */ if (attrs & USB_ATTRS_ONE_XFER) { uhci_handle_one_xfer_completion(uhcip, USB_CR_OK, td); return; } /* save it temporarily */ if (tw->tw_bytes_xfered != 0) { uhci_sendup_td_message(uhcip, USB_CR_OK, tw); } /* Clear the tw->tw_claim flag */ tw->tw_claim = UHCI_NOT_CLAIMED; uhci_delete_td(uhcip, td); /* allocate another interrupt periodic resource */ if (pp->pp_state == UHCI_PIPE_STATE_ACTIVE) { if (uhci_allocate_periodic_in_resource(uhcip, pp, tw, 0) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_insert_intr_req: Interrupt request structure" "allocation failed"); uhci_hcdi_callback(uhcip, pp, ph, tw, USB_CR_NO_RESOURCES); return; } /* Insert another interrupt TD */ if (uhci_insert_hc_td(uhcip, tw->tw_cookie.dmac_address, tw->tw_length, pp, tw, PID_IN, attrs) != USB_SUCCESS) { uhci_deallocate_periodic_in_resource(uhcip, pp, tw); USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_intr_td: TD exhausted"); uhci_hcdi_callback(uhcip, pp, ph, tw, USB_CR_NO_RESOURCES); } } } /* * uhci_sendup_td_message: * * Get a message block and send the received message upstream. */ void uhci_sendup_td_message( uhci_state_t *uhcip, usb_cr_t usb_err, uhci_trans_wrapper_t *tw) { mblk_t *mp = NULL; size_t length = 0; size_t skip_len = 0; uchar_t *buf; usb_opaque_t curr_xfer_reqp = tw->tw_curr_xfer_reqp; uhci_pipe_private_t *pp = tw->tw_pipe_private; usb_ep_descr_t *ept = &pp->pp_pipe_handle->p_ep; ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_sendup_td_message: bytes transferred=0x%x, " "bytes pending=0x%x", tw->tw_bytes_xfered, tw->tw_bytes_pending); length = tw->tw_bytes_xfered; switch (UHCI_XFER_TYPE(ept)) { case USB_EP_ATTR_CONTROL: skip_len = SETUP_SIZE; /* length of the buffer to skip */ mp = ((usb_ctrl_req_t *)curr_xfer_reqp)->ctrl_data; break; case USB_EP_ATTR_INTR: mp = ((usb_intr_req_t *)curr_xfer_reqp)->intr_data; break; case USB_EP_ATTR_BULK: mp = ((usb_bulk_req_t *)curr_xfer_reqp)->bulk_data; break; case USB_EP_ATTR_ISOCH: length = tw->tw_length; mp = ((usb_isoc_req_t *)curr_xfer_reqp)->isoc_data; break; default: break; } /* Copy the data into the mblk_t */ buf = (uchar_t *)tw->tw_buf + skip_len; ASSERT(mp != NULL); /* * Update kstat byte counts * The control endpoints don't have direction bits so in * order for control stats to be counted correctly an IN * bit must be faked on a control read. */ uhci_do_byte_stats(uhcip, length, ept->bmAttributes, (UHCI_XFER_TYPE(ept) == USB_EP_ATTR_CONTROL) ? USB_EP_DIR_IN : ept->bEndpointAddress); if (length) { int rval; /* Sync the streaming buffer */ rval = ddi_dma_sync(tw->tw_dmahandle, 0, length, DDI_DMA_SYNC_FORCPU); ASSERT(rval == DDI_SUCCESS); /* Copy the data into the message */ ddi_rep_get8(tw->tw_accesshandle, mp->b_rptr, buf, length, DDI_DEV_AUTOINCR); /* Increment the write pointer */ mp->b_wptr += length; } else { USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_sendup_td_message: Zero length packet"); } /* Do the callback */ uhci_hcdi_callback(uhcip, pp, pp->pp_pipe_handle, tw, usb_err); } /* * uhci_handle_ctrl_td: * Handle a control Transfer Descriptor (TD). */ void uhci_handle_ctrl_td(uhci_state_t *uhcip, uhci_td_t *td) { ushort_t direction; ushort_t bytes_for_xfer; ushort_t bytes_xfered; ushort_t MaxPacketSize; usb_cr_t error; uhci_trans_wrapper_t *tw = td->tw; uhci_pipe_private_t *pp = tw->tw_pipe_private; usba_pipe_handle_data_t *usb_pp = pp->pp_pipe_handle; usb_ep_descr_t *eptd = &usb_pp->p_ep; usb_ctrl_req_t *reqp = (usb_ctrl_req_t *)tw->tw_curr_xfer_reqp; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: pp = 0x%p tw = 0x%p td = 0x%p " "state = 0x%x len = 0x%lx", (void *)pp, (void *)tw, (void *)td, tw->tw_ctrl_state, tw->tw_length); ASSERT(mutex_owned(&uhcip->uhci_int_mutex)); error = uhci_parse_td_error(uhcip, pp, td); /* * In case of control transfers, the device can send NAK when it * is busy. If a NAK is received, then send the status TD again. */ if (error != USB_CR_OK) { USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: Ctrl cmd failed, error = %x", error); SetQH32(uhcip, pp->pp_qh->element_ptr, GetTD32(uhcip, td->link_ptr)); uhci_delete_td(uhcip, td); /* Return number of bytes xfered */ if (GetTD_alen(uhcip, td) != ZERO_LENGTH) { tw->tw_bytes_xfered = GetTD_alen(uhcip, td) + 1; } USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: Bytes transferred = %x", tw->tw_bytes_xfered); if ((tw->tw_ctrl_state == DATA) && (tw->tw_direction == PID_IN)) { uhci_sendup_td_message(uhcip, error, tw); } else { uhci_hcdi_callback(uhcip, pp, usb_pp, tw, error); uhci_deallocate_tw(uhcip, pp, tw); } return; } /* * A control transfer consists of three phases: * - Setup * - Data (optional) * - Status * * There is a TD per phase. A TD for a given phase isn't * enqueued until the previous phase is finished. */ switch (tw->tw_ctrl_state) { case SETUP: /* * Enqueue either the data or the status * phase depending on the length. */ pp->pp_data_toggle = 1; uhci_delete_td(uhcip, td); /* * If the length is 0, move to the status. * If length is not 0, then we have some data * to move on the bus to device either IN or OUT. */ if ((tw->tw_length - SETUP_SIZE) == 0) { /* * There is no data stage, then * initiate status phase from the host. */ if ((uhci_insert_hc_td(uhcip, NULL, 0, pp, tw, PID_IN, reqp->ctrl_attributes)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: No resources"); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_CR_NO_RESOURCES); return; } tw->tw_ctrl_state = STATUS; } else { uint_t xx; /* * Each USB device can send/receive 8/16/32/64 * depending on wMaxPacketSize's implementation. * We need to insert 'N = Number of byte/ * MaxpktSize" TD's in the lattice to send/ * receive the data. Though the USB protocol * allows to insert more than one TD in the same * frame, we are inserting only one TD in one * frame. This is bcos OHCI has seen some problem * when multiple TD's are inserted at the same time. */ tw->tw_length -= 8; MaxPacketSize = eptd->wMaxPacketSize; /* * We dont know the maximum packet size that * the device can handle(MaxPAcketSize=0). * In that case insert a data phase with * eight bytes or less. */ if (MaxPacketSize == 0) { xx = (tw->tw_length > 8) ? 8 : tw->tw_length; } else { xx = (tw->tw_length > MaxPacketSize) ? MaxPacketSize : tw->tw_length; } tw->tw_tmp = xx; /* * Create the TD. If this is an OUT * transaction, the data is already * in the buffer of the TW. * Get first 8 bytes of the command only. */ if ((uhci_insert_hc_td(uhcip, tw->tw_cookie.dmac_address + SETUP_SIZE, xx, pp, tw, tw->tw_direction, reqp->ctrl_attributes)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: No resources"); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_CR_NO_RESOURCES); return; } tw->tw_ctrl_state = DATA; } USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "Setup complete: pp 0x%p td 0x%p", (void *)pp, (void *)td); break; case DATA: uhci_delete_td(uhcip, td); MaxPacketSize = eptd->wMaxPacketSize; /* * Decrement pending bytes and increment the total * number bytes transferred by the actual number of bytes * transferred in this TD. If the number of bytes transferred * is less than requested, that means an underrun has * occurred. Set the tw_tmp varible to indicate UNDER run. */ bytes_xfered = GetTD_alen(uhcip, td); if (bytes_xfered == ZERO_LENGTH) { bytes_xfered = 0; } else { bytes_xfered++; } tw->tw_bytes_pending -= bytes_xfered; tw->tw_bytes_xfered += bytes_xfered; if (bytes_xfered < tw->tw_tmp) { tw->tw_bytes_pending = 0; tw->tw_tmp = UHCI_UNDERRUN_OCCURRED; /* * Controller does not update the queue head * element pointer when a data underrun occurs. */ SetQH32(uhcip, pp->pp_qh->element_ptr, GetTD32(uhcip, td->link_ptr)); } if (bytes_xfered > tw->tw_tmp) { tw->tw_bytes_pending = 0; tw->tw_tmp = UHCI_OVERRUN_OCCURRED; } /* * If no more bytes are pending, insert status * phase. Otherwise insert data phase. */ if (tw->tw_bytes_pending) { bytes_for_xfer = (tw->tw_bytes_pending > MaxPacketSize) ? MaxPacketSize : tw->tw_bytes_pending; tw->tw_tmp = bytes_for_xfer; if ((uhci_insert_hc_td(uhcip, tw->tw_cookie.dmac_address + SETUP_SIZE + tw->tw_bytes_xfered, bytes_for_xfer, pp, tw, tw->tw_direction, reqp->ctrl_attributes)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: No TD"); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_NO_RESOURCES); return; } tw->tw_ctrl_state = DATA; break; } pp->pp_data_toggle = 1; direction = (tw->tw_direction == PID_IN) ? PID_OUT : PID_IN; if ((uhci_insert_hc_td(uhcip, NULL, 0, pp, tw, direction, reqp->ctrl_attributes)) != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: TD exhausted"); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_NO_RESOURCES); return; } tw->tw_ctrl_state = STATUS; USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "Data complete: pp 0x%p td 0x%p", (void *)pp, (void *)td); break; case STATUS: /* * Send the data to the client if it is a DATA IN, * else send just return status for DATA OUT commnads. * And set the tw_claim flag. */ tw->tw_claim = UHCI_INTR_HDLR_CLAIMED; if ((tw->tw_length != 0) && (tw->tw_direction == PID_IN)) { usb_req_attrs_t attrs = ((usb_ctrl_req_t *) tw->tw_curr_xfer_reqp)->ctrl_attributes; /* * Call uhci_sendup_td_message to send message * upstream. The function uhci_sendup_td_message * returns USB_NO_RESOURCES if allocb fails and * also sends error message to upstream by calling * USBA callback function. * * Under error conditions just drop the current msg. */ if ((tw->tw_tmp == UHCI_UNDERRUN_OCCURRED) && (!(attrs & USB_ATTRS_SHORT_XFER_OK))) { error = USB_CR_DATA_UNDERRUN; } else if (tw->tw_tmp == UHCI_OVERRUN_OCCURRED) { error = USB_CR_DATA_OVERRUN; } uhci_sendup_td_message(uhcip, error, tw); } else { uhci_do_byte_stats(uhcip, tw->tw_length, eptd->bmAttributes, eptd->bEndpointAddress); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_CR_OK); } USB_DPRINTF_L3(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "Status complete: pp 0x%p td 0x%p", pp, td); uhci_delete_td(uhcip, td); uhci_deallocate_tw(uhcip, pp, tw); break; default: USB_DPRINTF_L2(PRINT_MASK_INTR, uhcip->uhci_log_hdl, "uhci_handle_ctrl_td: Bad control state"); uhci_hcdi_callback(uhcip, pp, usb_pp, tw, USB_CR_UNSPECIFIED_ERR); } } /* * uhci_handle_intr_td_errors: * Handles the errors encountered for the interrupt transfers. */ static void uhci_handle_intr_td_errors(uhci_state_t *uhcip, uhci_td_t *td, uhci_trans_wrapper_t *tw, uhci_pipe_private_t *pp) { usb_cr_t usb_err; usb_intr_req_t *intr_reqp = (usb_intr_req_t *)tw->tw_curr_xfer_reqp; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_intr_td_errors: td = 0x%p tw = 0x%p", td, tw); usb_err = uhci_parse_td_error(uhcip, pp, td); if (intr_reqp->intr_attributes & USB_ATTRS_ONE_XFER) { uhci_handle_one_xfer_completion(uhcip, usb_err, td); return; } uhci_delete_td(uhcip, td); uhci_sendup_td_message(uhcip, usb_err, tw); uhci_deallocate_tw(uhcip, tw->tw_pipe_private, tw); } /* * uhci_handle_one_xfer_completion: */ static void uhci_handle_one_xfer_completion( uhci_state_t *uhcip, usb_cr_t usb_err, uhci_td_t *td) { uhci_trans_wrapper_t *tw = td->tw; uhci_pipe_private_t *pp = tw->tw_pipe_private; usba_pipe_handle_data_t *ph = pp->pp_pipe_handle; usb_intr_req_t *intr_reqp = (usb_intr_req_t *)tw->tw_curr_xfer_reqp; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_handle_one_xfer_completion: td = 0x%p", td); ASSERT(intr_reqp->intr_attributes & USB_ATTRS_ONE_XFER); /* set state to idle */ pp->pp_state = UHCI_PIPE_STATE_IDLE; ((usb_intr_req_t *)(pp->pp_client_periodic_in_reqp))-> intr_data = ((usb_intr_req_t *)(tw->tw_curr_xfer_reqp))->intr_data; ((usb_intr_req_t *)tw->tw_curr_xfer_reqp)->intr_data = NULL; /* now free duplicate current request */ usb_free_intr_req((usb_intr_req_t *)tw->tw_curr_xfer_reqp); mutex_enter(&ph->p_mutex); ph->p_req_count--; mutex_exit(&ph->p_mutex); /* make client's request the current request */ tw->tw_curr_xfer_reqp = pp->pp_client_periodic_in_reqp; pp->pp_client_periodic_in_reqp = NULL; uhci_sendup_td_message(uhcip, usb_err, tw); /* Clear the tw->tw_claim flag */ tw->tw_claim = UHCI_NOT_CLAIMED; uhci_delete_td(uhcip, td); uhci_deallocate_tw(uhcip, pp, tw); } /* * uhci_parse_td_error * Parses the Transfer Descriptors error */ usb_cr_t uhci_parse_td_error(uhci_state_t *uhcip, uhci_pipe_private_t *pp, uhci_td_t *td) { uint_t status; status = GetTD_status(uhcip, td) & TD_STATUS_MASK; USB_DPRINTF_L4(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_parse_td_error: status_bits=0x%x", status); if (UHCI_XFER_TYPE(&pp->pp_pipe_handle->p_ep) == USB_EP_ATTR_ISOCH) { return (USB_CR_OK); } if (!status) { return (USB_CR_OK); } USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_parse_td_error: status_bits=0x%x", status); if (status & UHCI_TD_BITSTUFF_ERR) { return (USB_CR_BITSTUFFING); } if (status & UHCI_TD_CRC_TIMEOUT) { pp->pp_data_toggle = GetTD_dtogg(uhcip, td); USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_parse_td_error: timeout & data toggle reset; " "data toggle: %x", pp->pp_data_toggle); return ((GetTD_PID(uhcip, td) == PID_IN) ? USB_CR_DEV_NOT_RESP : USB_CR_TIMEOUT); } if (status & UHCI_TD_BABBLE_ERR) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "babble error"); return (USB_CR_UNSPECIFIED_ERR); } if (status & UHCI_TD_DATA_BUFFER_ERR) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "buffer error"); return ((GetTD_PID(uhcip, td) == PID_IN) ? USB_CR_BUFFER_OVERRUN : USB_CR_BUFFER_UNDERRUN); } if (status & UHCI_TD_STALLED) { pp->pp_data_toggle = 0; USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "uhci_parse_td_error: stall; data toggle reset; " "data toggle: %x", pp->pp_data_toggle); return (USB_CR_STALL); } if (status) { USB_DPRINTF_L2(PRINT_MASK_LISTS, uhcip->uhci_log_hdl, "unspecified error=0x%x", status); } return (USB_CR_OK); } static dev_info_t * uhci_get_dip(dev_t dev) { int instance = UHCI_UNIT(dev); uhci_state_t *uhcip = ddi_get_soft_state(uhci_statep, instance); return (uhcip ? uhcip->uhci_dip : NULL); } /* * cb_ops entry points */ static int uhci_open(dev_t *devp, int flags, int otyp, cred_t *credp) { dev_info_t *dip = uhci_get_dip(*devp); return (usba_hubdi_open(dip, devp, flags, otyp, credp)); } static int uhci_close(dev_t dev, int flag, int otyp, cred_t *credp) { dev_info_t *dip = uhci_get_dip(dev); return (usba_hubdi_close(dip, dev, flag, otyp, credp)); } static int uhci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { dev_info_t *dip = uhci_get_dip(dev); return (usba_hubdi_ioctl(dip, dev, cmd, arg, mode, credp, rvalp)); }