/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * EHCI Host Controller Driver (EHCI) * * The EHCI driver is a software driver which interfaces to the Universal * Serial Bus layer (USBA) and the Host Controller (HC). The interface to * the Host Controller is defined by the EHCI Host Controller Interface. * * This file contains code for Auto-configuration and HCDI entry points. * * NOTE: * * Currently EHCI driver does not support the following features * * - Alternate QTD for short xfer condition is only used in Bulk xfers. * - Frame Span Traversal Nodes (FSTN). * - Bandwidth allocation scheme needs to be updated for FSTN and USB2.0 * or High speed hub with multiple TT implementation. Currently bandwidth * allocation scheme assumes one TT per USB2.0 or High speed hub. * - 64 bit addressing capability. * - Programmable periodic frame list size like 256, 512, 1024. * It supports only 1024 periodic frame list size. */ #include #include #include #include #include /* Pointer to the state structure */ void *ehci_statep; /* Number of instances */ #define EHCI_INSTS 1 /* Debugging information */ uint_t ehci_errmask = (uint_t)PRINT_MASK_ALL; uint_t ehci_errlevel = USB_LOG_L2; uint_t ehci_instance_debug = (uint_t)-1; /* * Tunable to ensure host controller goes off even if a keyboard is attached. */ int force_ehci_off = 1; /* Enable all workarounds for VIA VT62x2 */ uint_t ehci_vt62x2_workaround = EHCI_VIA_WORKAROUNDS; /* * EHCI Auto-configuration entry points. * * Device operations (dev_ops) entries function prototypes. * * We use the hub cbops since all nexus ioctl operations defined so far will * be executed by the root hub. The following are the Host Controller Driver * (HCD) entry points. * * the open/close/ioctl functions call the corresponding usba_hubdi_* * calls after looking up the dip thru the dev_t. */ static int ehci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); static int ehci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); static int ehci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd); static int ehci_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result); static int ehci_open(dev_t *devp, int flags, int otyp, cred_t *credp); static int ehci_close(dev_t dev, int flag, int otyp, cred_t *credp); static int ehci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp); int usba_hubdi_root_hub_power(dev_info_t *dip, int comp, int level); static int ehci_quiesce(dev_info_t *dip); static struct cb_ops ehci_cb_ops = { ehci_open, /* EHCI */ ehci_close, /* Close */ nodev, /* Strategy */ nodev, /* Print */ nodev, /* Dump */ nodev, /* Read */ nodev, /* Write */ ehci_ioctl, /* Ioctl */ nodev, /* Devmap */ nodev, /* Mmap */ nodev, /* Segmap */ nochpoll, /* Poll */ ddi_prop_op, /* cb_prop_op */ NULL, /* Streamtab */ D_NEW | D_MP | D_HOTPLUG /* Driver compatibility flag */ }; static struct dev_ops ehci_ops = { DEVO_REV, /* Devo_rev */ 0, /* Refcnt */ ehci_info, /* Info */ nulldev, /* Identify */ nulldev, /* Probe */ ehci_attach, /* Attach */ ehci_detach, /* Detach */ ehci_reset, /* Reset */ &ehci_cb_ops, /* Driver operations */ &usba_hubdi_busops, /* Bus operations */ usba_hubdi_root_hub_power, /* Power */ ehci_quiesce /* Quiesce */ }; /* * The USBA library must be loaded for this driver. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module. This one is a driver */ "USB EHCI Driver", /* Name of the module. */ &ehci_ops, /* Driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, (void *)&modldrv, NULL }; int _init(void) { int error; /* Initialize the soft state structures */ if ((error = ddi_soft_state_init(&ehci_statep, sizeof (ehci_state_t), EHCI_INSTS)) != 0) { return (error); } /* Install the loadable module */ if ((error = mod_install(&modlinkage)) != 0) { ddi_soft_state_fini(&ehci_statep); } return (error); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int _fini(void) { int error; if ((error = mod_remove(&modlinkage)) == 0) { /* Release per module resources */ ddi_soft_state_fini(&ehci_statep); } return (error); } /* * EHCI Auto configuration entry points. */ /* * ehci_attach: * * Description: Attach entry point is called by the Kernel. * Allocates resources for each EHCI host controller instance. * Initializes the EHCI Host Controller. * * Return : DDI_SUCCESS / DDI_FAILURE. */ static int ehci_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { int instance; ehci_state_t *ehcip = NULL; usba_hcdi_register_args_t hcdi_args; switch (cmd) { case DDI_ATTACH: break; case DDI_RESUME: ehcip = ehci_obtain_state(dip); return (ehci_cpr_resume(ehcip)); default: return (DDI_FAILURE); } /* Get the instance and create soft state */ instance = ddi_get_instance(dip); if (ddi_soft_state_zalloc(ehci_statep, instance) != 0) { return (DDI_FAILURE); } ehcip = ddi_get_soft_state(ehci_statep, instance); if (ehcip == NULL) { return (DDI_FAILURE); } ehcip->ehci_flags = EHCI_ATTACH; ehcip->ehci_log_hdl = usb_alloc_log_hdl(dip, "ehci", &ehci_errlevel, &ehci_errmask, &ehci_instance_debug, 0); ehcip->ehci_flags |= EHCI_ZALLOC; /* Set host controller soft state to initialization */ ehcip->ehci_hc_soft_state = EHCI_CTLR_INIT_STATE; USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl, "ehcip = 0x%p", (void *)ehcip); /* Save the dip and instance */ ehcip->ehci_dip = dip; ehcip->ehci_instance = instance; /* Map the registers */ if (ehci_map_regs(ehcip) != DDI_SUCCESS) { (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } /* Get the ehci chip vendor and device id */ ehcip->ehci_vendor_id = pci_config_get16( ehcip->ehci_config_handle, PCI_CONF_VENID); ehcip->ehci_device_id = pci_config_get16( ehcip->ehci_config_handle, PCI_CONF_DEVID); ehcip->ehci_rev_id = pci_config_get8( ehcip->ehci_config_handle, PCI_CONF_REVID); /* Initialize the DMA attributes */ ehci_set_dma_attributes(ehcip); /* Initialize kstat structures */ ehci_create_stats(ehcip); /* Create the qtd and qh pools */ if (ehci_allocate_pools(ehcip) != DDI_SUCCESS) { (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } /* Initialize the isochronous resources */ if (ehci_isoc_init(ehcip) != DDI_SUCCESS) { (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } /* Register interrupts */ if (ehci_register_intrs_and_init_mutex(ehcip) != DDI_SUCCESS) { (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } mutex_enter(&ehcip->ehci_int_mutex); /* Initialize the controller */ if (ehci_init_ctlr(ehcip, EHCI_NORMAL_INITIALIZATION) != DDI_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } /* * At this point, the hardware will be okay. * Initialize the usba_hcdi structure */ ehcip->ehci_hcdi_ops = ehci_alloc_hcdi_ops(ehcip); mutex_exit(&ehcip->ehci_int_mutex); /* * 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 = ehcip->ehci_hcdi_ops; hcdi_args.usba_hcdi_register_dma_attr = &ehcip->ehci_dma_attr; /* * Priority and iblock_cookie are one and the same * (However, retaining hcdi_soft_iblock_cookie for now * assigning it w/ priority. In future all iblock_cookie * could just go) */ hcdi_args.usba_hcdi_register_iblock_cookie = (ddi_iblock_cookie_t)(uintptr_t)ehcip->ehci_intr_pri; if (usba_hcdi_register(&hcdi_args, 0) != DDI_SUCCESS) { (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } ehcip->ehci_flags |= EHCI_USBAREG; mutex_enter(&ehcip->ehci_int_mutex); if ((ehci_init_root_hub(ehcip)) != USB_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } mutex_exit(&ehcip->ehci_int_mutex); /* Finally load the root hub driver */ if (ehci_load_root_hub_driver(ehcip) != USB_SUCCESS) { (void) ehci_cleanup(ehcip); return (DDI_FAILURE); } ehcip->ehci_flags |= EHCI_RHREG; /* Display information in the banner */ ddi_report_dev(dip); mutex_enter(&ehcip->ehci_int_mutex); /* Reset the ehci initialization flag */ ehcip->ehci_flags &= ~EHCI_ATTACH; /* Print the Host Control's Operational registers */ ehci_print_caps(ehcip); ehci_print_regs(ehcip); (void) pci_report_pmcap(dip, PCI_PM_IDLESPEED, (void *)4000); mutex_exit(&ehcip->ehci_int_mutex); USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl, "ehci_attach: dip = 0x%p done", (void *)dip); return (DDI_SUCCESS); } /* * ehci_detach: * * Description: Detach entry point is called by the Kernel. * Deallocates all resource allocated. * Unregisters the interrupt handler. * * Return : DDI_SUCCESS / DDI_FAILURE */ int ehci_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { ehci_state_t *ehcip = ehci_obtain_state(dip); USB_DPRINTF_L4(PRINT_MASK_ATTA, ehcip->ehci_log_hdl, "ehci_detach:"); switch (cmd) { case DDI_DETACH: return (ehci_cleanup(ehcip)); case DDI_SUSPEND: return (ehci_cpr_suspend(ehcip)); default: return (DDI_FAILURE); } } /* * ehci_reset: * * Description: Reset entry point - called by the Kernel * on the way down. * Toshiba Tecra laptop has been observed to hang * on soft reboot. The resetting ehci on the way * down solves the problem. * * Return : DDI_SUCCESS / DDI_FAILURE */ /* ARGSUSED */ static int ehci_reset(dev_info_t *dip, ddi_reset_cmd_t cmd) { #if defined(__sparc) /* * Don't reset the host controller on SPARC, for OBP needs Solaris * to continue to provide keyboard support after shutdown of SPARC, * or the keyboard connected to a USB 2.0 port will not work after * that. The incomplete reset problem on Toshiba Tecra laptop is * specific to Tecra laptop or BIOS, not present on SPARC. The SPARC * OBP guarantees good reset behavior during startup. */ return (DDI_SUCCESS); #else ehci_state_t *ehcip = ehci_obtain_state(dip); mutex_enter(&ehcip->ehci_int_mutex); /* * To reset the host controller, the HCRESET bit should be set to one. * Software should not set this bit to a one when the HCHalted bit in * the USBSTS register is a zero. Attempting to reset an actively * running host controller will result in undefined behavior. * see EHCI SPEC. for more information. */ if (!(Get_OpReg(ehci_status) & EHCI_STS_HOST_CTRL_HALTED)) { /* Stop the EHCI host controller */ Set_OpReg(ehci_command, Get_OpReg(ehci_command) & ~EHCI_CMD_HOST_CTRL_RUN); /* * When this bit is set to 0, the Host Controller completes the * current and any actively pipelined transactions on the USB * and then halts. The Host Controller must halt within 16 * micro-frames after software clears the Run bit. * The HC Halted bit in the status register indicates when the * Host Controller has finished its pending pipelined * transactions and has entered the stopped state. */ drv_usecwait(EHCI_RESET_TIMEWAIT); } /* Reset the EHCI host controller */ Set_OpReg(ehci_command, Get_OpReg(ehci_command) | EHCI_CMD_HOST_CTRL_RESET); mutex_exit(&ehcip->ehci_int_mutex); return (DDI_SUCCESS); #endif } /* * quiesce(9E) entry point. * * This function is called when the system is single-threaded at high * PIL with preemption disabled. Therefore, this function must not be * blocked. * * This function returns DDI_SUCCESS on success, or DDI_FAILURE on failure. * DDI_FAILURE indicates an error condition and should almost never happen. */ static int ehci_quiesce(dev_info_t *dip) { ehci_state_t *ehcip = ehci_obtain_state(dip); if (ehcip == NULL) return (DDI_FAILURE); #ifndef lint _NOTE(NO_COMPETING_THREADS_NOW); #endif /* * To reset the host controller, the HCRESET bit should be set to one. * Software should not set this bit to a one when the HCHalted bit in * the USBSTS register is a zero. Attempting to reset an actively * running host controller will result in undefined behavior. * see EHCI SPEC. for more information. */ if (!(Get_OpReg(ehci_status) & EHCI_STS_HOST_CTRL_HALTED)) { /* Stop the EHCI host controller */ Set_OpReg(ehci_command, Get_OpReg(ehci_command) & ~EHCI_CMD_HOST_CTRL_RUN); /* * When this bit is set to 0, the Host Controller completes the * current and any actively pipelined transactions on the USB * and then halts. The Host Controller must halt within 16 * micro-frames after software clears the Run bit. * The HC Halted bit in the status register indicates when the * Host Controller has finished its pending pipelined * transactions and has entered the stopped state. */ drv_usecwait(EHCI_RESET_TIMEWAIT); } /* Reset the EHCI host controller */ Set_OpReg(ehci_command, Get_OpReg(ehci_command) | EHCI_CMD_HOST_CTRL_RESET); #ifndef lint _NOTE(COMPETING_THREADS_NOW); #endif return (DDI_SUCCESS); } /* * ehci_info: */ /* ARGSUSED */ static int ehci_info(dev_info_t *dip, ddi_info_cmd_t infocmd, void *arg, void **result) { dev_t dev; ehci_state_t *ehcip; int instance; int error = DDI_FAILURE; switch (infocmd) { case DDI_INFO_DEVT2DEVINFO: dev = (dev_t)arg; instance = EHCI_UNIT(dev); ehcip = ddi_get_soft_state(ehci_statep, instance); if (ehcip != NULL) { *result = (void *)ehcip->ehci_dip; if (*result != NULL) { error = DDI_SUCCESS; } } else { *result = NULL; } break; case DDI_INFO_DEVT2INSTANCE: dev = (dev_t)arg; instance = EHCI_UNIT(dev); *result = (void *)(uintptr_t)instance; error = DDI_SUCCESS; break; default: break; } return (error); } /* * EHCI CB_OPS entry points. */ static dev_info_t * ehci_get_dip(dev_t dev) { int instance = EHCI_UNIT(dev); ehci_state_t *ehcip = ddi_get_soft_state(ehci_statep, instance); if (ehcip) { return (ehcip->ehci_dip); } else { return (NULL); } } static int ehci_open(dev_t *devp, int flags, int otyp, cred_t *credp) { dev_info_t *dip = ehci_get_dip(*devp); return (usba_hubdi_open(dip, devp, flags, otyp, credp)); } static int ehci_close(dev_t dev, int flag, int otyp, cred_t *credp) { dev_info_t *dip = ehci_get_dip(dev); return (usba_hubdi_close(dip, dev, flag, otyp, credp)); } static int ehci_ioctl(dev_t dev, int cmd, intptr_t arg, int mode, cred_t *credp, int *rvalp) { dev_info_t *dip = ehci_get_dip(dev); return (usba_hubdi_ioctl(dip, dev, cmd, arg, mode, credp, rvalp)); } /* * EHCI Interrupt Handler entry point. */ /* * ehci_intr: * * EHCI (EHCI) interrupt handling routine. */ uint_t ehci_intr(caddr_t arg1, caddr_t arg2) { uint_t intr; ehci_state_t *ehcip = (void *)arg1; USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "ehci_intr: Interrupt occurred, arg1 0x%p arg2 0x%p", (void *)arg1, (void *)arg2); /* Get the ehci global mutex */ mutex_enter(&ehcip->ehci_int_mutex); /* Any interrupt is not handled for the suspended device. */ if (ehcip->ehci_hc_soft_state == EHCI_CTLR_SUSPEND_STATE) { mutex_exit(&ehcip->ehci_int_mutex); return (DDI_INTR_UNCLAIMED); } /* * Now process the actual ehci interrupt events that caused * invocation of this ehci interrupt handler. */ intr = (Get_OpReg(ehci_status) & Get_OpReg(ehci_interrupt)); /* Update kstat values */ ehci_do_intrs_stats(ehcip, intr); /* * We could have gotten a spurious interrupts. If so, do not * claim it. This is quite possible on some architectures * where more than one PCI slots share the IRQs. If so, the * associated driver's interrupt routine may get called even * if the interrupt is not meant for them. * * By unclaiming the interrupt, the other driver gets chance * to service its interrupt. */ if (!intr) { mutex_exit(&ehcip->ehci_int_mutex); return (DDI_INTR_UNCLAIMED); } /* Acknowledge the interrupt */ Set_OpReg(ehci_status, intr); if (ehcip->ehci_hc_soft_state == EHCI_CTLR_ERROR_STATE) { mutex_exit(&ehcip->ehci_int_mutex); return (DDI_INTR_CLAIMED); } USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "Interrupt status 0x%x", intr); /* * If necessary broadcast that an interrupt has occured. This * is only necessary during controller init. */ if (ehcip->ehci_flags & EHCI_CV_INTR) { ehcip->ehci_flags &= ~EHCI_CV_INTR; cv_broadcast(&ehcip->ehci_async_schedule_advance_cv); } /* Check for Frame List Rollover */ if (intr & EHCI_INTR_FRAME_LIST_ROLLOVER) { USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "ehci_intr: Frame List Rollover"); ehci_handle_frame_list_rollover(ehcip); /* VIA VT6202 looses EHCI_INTR_USB interrupts, workaround. */ if ((ehcip->ehci_vendor_id == PCI_VENDOR_VIA) && (ehci_vt62x2_workaround & EHCI_VIA_LOST_INTERRUPTS)) { ehcip->ehci_missed_intr_sts |= EHCI_INTR_USB; } } /* Check for Advance on Asynchronous Schedule */ if (intr & EHCI_INTR_ASYNC_ADVANCE) { USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "ehci_intr: Asynchronous Schedule Advance Notification"); /* Disable async list advance interrupt */ Set_OpReg(ehci_interrupt, (Get_OpReg(ehci_interrupt) & ~EHCI_INTR_ASYNC_ADVANCE)); /* * Call cv_broadcast on every this interrupt to wakeup * all the threads that are waiting the async list advance * event. */ cv_broadcast(&ehcip->ehci_async_schedule_advance_cv); } /* Always process completed itds */ ehci_traverse_active_isoc_list(ehcip); /* * Check for any USB transaction completion notification. Also * process any missed USB transaction completion interrupts. */ if ((intr & EHCI_INTR_USB) || (intr & EHCI_INTR_USB_ERROR) || (ehcip->ehci_missed_intr_sts & EHCI_INTR_USB) || (ehcip->ehci_missed_intr_sts & EHCI_INTR_USB_ERROR)) { USB_DPRINTF_L3(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "ehci_intr: USB Transaction Completion Notification"); /* Clear missed interrupts */ if (ehcip->ehci_missed_intr_sts) { ehcip->ehci_missed_intr_sts = 0; } /* Process completed qtds */ ehci_traverse_active_qtd_list(ehcip); } /* Process endpoint reclamation list */ if (ehcip->ehci_reclaim_list) { ehci_handle_endpoint_reclaimation(ehcip); } /* Check for Host System Error */ if (intr & EHCI_INTR_HOST_SYSTEM_ERROR) { USB_DPRINTF_L2(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "ehci_intr: Unrecoverable error"); ehci_handle_ue(ehcip); } /* * Read interrupt status register to make sure that any PIO * store to clear the ISR has made it on the PCI bus before * returning from its interrupt handler. */ (void) Get_OpReg(ehci_status); /* Release the ehci global mutex */ mutex_exit(&ehcip->ehci_int_mutex); USB_DPRINTF_L4(PRINT_MASK_INTR, ehcip->ehci_log_hdl, "Interrupt handling completed"); return (DDI_INTR_CLAIMED); } /* * EHCI HCDI entry points * * The Host Controller Driver Interfaces (HCDI) are the software interfaces * between the Universal Serial Bus Layer (USBA) and the Host Controller * Driver (HCD). The HCDI interfaces or entry points are subject to change. */ /* * ehci_hcdi_pipe_open: * * Member of HCD Ops structure and called during client specific pipe open * Add the pipe to the data structure representing the device and allocate * bandwidth for the pipe if it is a interrupt or isochronous endpoint. */ int ehci_hcdi_pipe_open( usba_pipe_handle_data_t *ph, usb_flags_t flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); usb_ep_descr_t *epdt = &ph->p_ep; int rval, error = USB_SUCCESS; int kmflag = (flags & USB_FLAGS_SLEEP) ? KM_SLEEP : KM_NOSLEEP; uchar_t smask = 0; uchar_t cmask = 0; uint_t pnode = 0; ehci_pipe_private_t *pp; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: addr = 0x%x, ep%d", ph->p_usba_device->usb_addr, epdt->bEndpointAddress & USB_EP_NUM_MASK); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); mutex_exit(&ehcip->ehci_int_mutex); if (rval != USB_SUCCESS) { return (rval); } /* * Check and handle root hub pipe open. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { mutex_enter(&ehcip->ehci_int_mutex); error = ehci_handle_root_hub_pipe_open(ph, flags); mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * Opening of other pipes excluding root hub pipe are * handled below. Check whether pipe is already opened. */ if (ph->p_hcd_private) { USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: Pipe is already opened"); return (USB_FAILURE); } /* * A portion of the bandwidth is reserved for the non-periodic * transfers, i.e control and bulk transfers in each of one * millisecond frame period & usually it will be 20% of frame * period. Hence there is no need to check for the available * bandwidth before adding the control or bulk endpoints. * * There is a need to check for the available bandwidth before * adding the periodic transfers, i.e interrupt & isochronous, * since all these periodic transfers are guaranteed transfers. * Usually 80% of the total frame time is reserved for periodic * transfers. */ if (EHCI_PERIODIC_ENDPOINT(epdt)) { mutex_enter(&ehcip->ehci_int_mutex); mutex_enter(&ph->p_mutex); error = ehci_allocate_bandwidth(ehcip, ph, &pnode, &smask, &cmask); if (error != USB_SUCCESS) { USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: Bandwidth allocation failed"); mutex_exit(&ph->p_mutex); mutex_exit(&ehcip->ehci_int_mutex); return (error); } mutex_exit(&ph->p_mutex); mutex_exit(&ehcip->ehci_int_mutex); } /* Create the HCD pipe private structure */ pp = kmem_zalloc(sizeof (ehci_pipe_private_t), kmflag); /* * Return failure if ehci pipe private * structure allocation fails. */ if (pp == NULL) { mutex_enter(&ehcip->ehci_int_mutex); /* Deallocate bandwidth */ if (EHCI_PERIODIC_ENDPOINT(epdt)) { mutex_enter(&ph->p_mutex); ehci_deallocate_bandwidth(ehcip, ph, pnode, smask, cmask); mutex_exit(&ph->p_mutex); } mutex_exit(&ehcip->ehci_int_mutex); return (USB_NO_RESOURCES); } mutex_enter(&ehcip->ehci_int_mutex); /* Save periodic nodes */ pp->pp_pnode = pnode; /* Save start and complete split mask values */ pp->pp_smask = smask; pp->pp_cmask = cmask; /* Create prototype for xfer completion condition variable */ cv_init(&pp->pp_xfer_cmpl_cv, NULL, CV_DRIVER, NULL); /* Set the state of pipe as idle */ pp->pp_state = EHCI_PIPE_STATE_IDLE; /* Store a pointer to the pipe handle */ pp->pp_pipe_handle = ph; mutex_enter(&ph->p_mutex); /* Store the pointer in the pipe handle */ ph->p_hcd_private = (usb_opaque_t)pp; /* Store a copy of the pipe policy */ bcopy(&ph->p_policy, &pp->pp_policy, sizeof (usb_pipe_policy_t)); mutex_exit(&ph->p_mutex); /* Allocate the host controller endpoint descriptor */ pp->pp_qh = ehci_alloc_qh(ehcip, ph, EHCI_INTERRUPT_MODE_FLAG); /* Initialize the halting flag */ pp->pp_halt_state = EHCI_HALT_STATE_FREE; /* Create prototype for halt completion condition variable */ cv_init(&pp->pp_halt_cmpl_cv, NULL, CV_DRIVER, NULL); /* Isoch does not use QH, so ignore this */ if ((pp->pp_qh == NULL) && !(EHCI_ISOC_ENDPOINT(epdt))) { ASSERT(pp->pp_qh == NULL); USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: QH allocation failed"); mutex_enter(&ph->p_mutex); /* Deallocate bandwidth */ if (EHCI_PERIODIC_ENDPOINT(epdt)) { ehci_deallocate_bandwidth(ehcip, ph, pnode, smask, cmask); } /* Destroy the xfer completion condition variable */ cv_destroy(&pp->pp_xfer_cmpl_cv); /* * Deallocate the hcd private portion * of the pipe handle. */ kmem_free(ph->p_hcd_private, sizeof (ehci_pipe_private_t)); /* * Set the private structure in the * pipe handle equal to NULL. */ ph->p_hcd_private = NULL; mutex_exit(&ph->p_mutex); mutex_exit(&ehcip->ehci_int_mutex); return (USB_NO_RESOURCES); } /* * Isoch does not use QH so no need to * restore data toggle or insert QH */ if (!(EHCI_ISOC_ENDPOINT(epdt))) { /* Restore the data toggle information */ ehci_restore_data_toggle(ehcip, ph); } /* * Insert the endpoint onto the host controller's * appropriate endpoint list. The host controller * will not schedule this endpoint and will not have * any QTD's to process. It will also update the pipe count. */ ehci_insert_qh(ehcip, ph); USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_open: ph = 0x%p", (void *)ph); ehcip->ehci_open_pipe_count++; mutex_exit(&ehcip->ehci_int_mutex); return (USB_SUCCESS); } /* * ehci_hcdi_pipe_close: * * Member of HCD Ops structure and called during the client specific pipe * close. Remove the pipe and the data structure representing the device. * Deallocate bandwidth for the pipe if it is a interrupt or isochronous * endpoint. */ /* ARGSUSED */ int ehci_hcdi_pipe_close( usba_pipe_handle_data_t *ph, usb_flags_t flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private; usb_ep_descr_t *eptd = &ph->p_ep; int error = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_close: addr = 0x%x, ep%d", ph->p_usba_device->usb_addr, eptd->bEndpointAddress & USB_EP_NUM_MASK); /* Check and handle root hub pipe close */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { mutex_enter(&ehcip->ehci_int_mutex); error = ehci_handle_root_hub_pipe_close(ph); mutex_exit(&ehcip->ehci_int_mutex); return (error); } ASSERT(ph->p_hcd_private != NULL); mutex_enter(&ehcip->ehci_int_mutex); /* Set pipe state to pipe close */ pp->pp_state = EHCI_PIPE_STATE_CLOSE; ehci_pipe_cleanup(ehcip, ph); /* * Remove the endpoint descriptor from Host * Controller's appropriate endpoint list. */ ehci_remove_qh(ehcip, pp, B_TRUE); /* Deallocate bandwidth */ if (EHCI_PERIODIC_ENDPOINT(eptd)) { mutex_enter(&ph->p_mutex); ehci_deallocate_bandwidth(ehcip, ph, pp->pp_pnode, pp->pp_smask, pp->pp_cmask); mutex_exit(&ph->p_mutex); } mutex_enter(&ph->p_mutex); /* Destroy the xfer completion condition variable */ cv_destroy(&pp->pp_xfer_cmpl_cv); /* Destory halt completion condition variable */ cv_destroy(&pp->pp_halt_cmpl_cv); /* * Deallocate the hcd private portion * of the pipe handle. */ kmem_free(ph->p_hcd_private, sizeof (ehci_pipe_private_t)); ph->p_hcd_private = NULL; mutex_exit(&ph->p_mutex); USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_close: ph = 0x%p", (void *)ph); ehcip->ehci_open_pipe_count--; mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * ehci_hcdi_pipe_reset: */ /* ARGSUSED */ int ehci_hcdi_pipe_reset( usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private; int error = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_reset:"); /* * Check and handle root hub pipe reset. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { error = ehci_handle_root_hub_pipe_reset(ph, usb_flags); return (error); } mutex_enter(&ehcip->ehci_int_mutex); /* Set pipe state to pipe reset */ pp->pp_state = EHCI_PIPE_STATE_RESET; ehci_pipe_cleanup(ehcip, ph); mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * ehci_hcdi_pipe_reset_data_toggle: */ void ehci_hcdi_pipe_reset_data_toggle( usba_pipe_handle_data_t *ph) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_reset_data_toggle:"); mutex_enter(&ehcip->ehci_int_mutex); mutex_enter(&ph->p_mutex); usba_hcdi_set_data_toggle(ph->p_usba_device, ph->p_ep.bEndpointAddress, DATA0); mutex_exit(&ph->p_mutex); Set_QH(pp->pp_qh->qh_status, Get_QH(pp->pp_qh->qh_status) & (~EHCI_QH_STS_DATA_TOGGLE)); mutex_exit(&ehcip->ehci_int_mutex); } /* * ehci_hcdi_pipe_ctrl_xfer: */ int ehci_hcdi_pipe_ctrl_xfer( usba_pipe_handle_data_t *ph, usb_ctrl_req_t *ctrl_reqp, usb_flags_t usb_flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private; int rval; int error = USB_SUCCESS; ehci_trans_wrapper_t *tw; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_ctrl_xfer: ph = 0x%p reqp = 0x%p flags = %x", (void *)ph, (void *)ctrl_reqp, usb_flags); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); mutex_exit(&ehcip->ehci_int_mutex); if (rval != USB_SUCCESS) { return (rval); } /* * Check and handle root hub control request. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { error = ehci_handle_root_hub_request(ehcip, ph, ctrl_reqp); return (error); } mutex_enter(&ehcip->ehci_int_mutex); /* * Check whether pipe is in halted state. */ if (pp->pp_state == EHCI_PIPE_STATE_ERROR) { USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_ctrl_xfer: " "Pipe is in error state, need pipe reset to continue"); mutex_exit(&ehcip->ehci_int_mutex); return (USB_FAILURE); } /* Allocate a transfer wrapper */ if ((tw = ehci_allocate_ctrl_resources(ehcip, pp, ctrl_reqp, usb_flags)) == NULL) { error = USB_NO_RESOURCES; } else { /* Insert the qtd's on the endpoint */ ehci_insert_ctrl_req(ehcip, ph, ctrl_reqp, tw, usb_flags); } mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * ehci_hcdi_bulk_transfer_size: * * Return maximum bulk transfer size */ /* ARGSUSED */ int ehci_hcdi_bulk_transfer_size( usba_device_t *usba_device, size_t *size) { ehci_state_t *ehcip = ehci_obtain_state( usba_device->usb_root_hub_dip); int rval; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_bulk_transfer_size:"); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); mutex_exit(&ehcip->ehci_int_mutex); if (rval != USB_SUCCESS) { return (rval); } /* VIA VT6202 may not handle bigger xfers well, workaround. */ if ((ehcip->ehci_vendor_id == PCI_VENDOR_VIA) && (ehci_vt62x2_workaround & EHCI_VIA_REDUCED_MAX_BULK_XFER_SIZE)) { *size = EHCI_VIA_MAX_BULK_XFER_SIZE; } else { *size = EHCI_MAX_BULK_XFER_SIZE; } return (USB_SUCCESS); } /* * ehci_hcdi_pipe_bulk_xfer: */ int ehci_hcdi_pipe_bulk_xfer( usba_pipe_handle_data_t *ph, usb_bulk_req_t *bulk_reqp, usb_flags_t usb_flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); ehci_pipe_private_t *pp = (ehci_pipe_private_t *)ph->p_hcd_private; int rval, error = USB_SUCCESS; ehci_trans_wrapper_t *tw; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_bulk_xfer: ph = 0x%p reqp = 0x%p flags = %x", (void *)ph, (void *)bulk_reqp, usb_flags); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); if (rval != USB_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); return (rval); } /* * Check whether pipe is in halted state. */ if (pp->pp_state == EHCI_PIPE_STATE_ERROR) { USB_DPRINTF_L2(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_bulk_xfer:" "Pipe is in error state, need pipe reset to continue"); mutex_exit(&ehcip->ehci_int_mutex); return (USB_FAILURE); } /* Allocate a transfer wrapper */ if ((tw = ehci_allocate_bulk_resources(ehcip, pp, bulk_reqp, usb_flags)) == NULL) { error = USB_NO_RESOURCES; } else { /* Add the QTD into the Host Controller's bulk list */ ehci_insert_bulk_req(ehcip, ph, bulk_reqp, tw, usb_flags); } mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * ehci_hcdi_pipe_intr_xfer: */ int ehci_hcdi_pipe_intr_xfer( usba_pipe_handle_data_t *ph, usb_intr_req_t *intr_reqp, usb_flags_t usb_flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); int pipe_dir, rval, error = USB_SUCCESS; ehci_trans_wrapper_t *tw; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_intr_xfer: ph = 0x%p reqp = 0x%p flags = %x", (void *)ph, (void *)intr_reqp, usb_flags); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); if (rval != USB_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); return (rval); } /* Get the pipe direction */ pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK; if (pipe_dir == USB_EP_DIR_IN) { error = ehci_start_periodic_pipe_polling(ehcip, ph, (usb_opaque_t)intr_reqp, usb_flags); } else { /* Allocate transaction resources */ if ((tw = ehci_allocate_intr_resources(ehcip, ph, intr_reqp, usb_flags)) == NULL) { error = USB_NO_RESOURCES; } else { ehci_insert_intr_req(ehcip, (ehci_pipe_private_t *)ph->p_hcd_private, tw, usb_flags); } } mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * ehci_hcdi_pipe_stop_intr_polling() */ int ehci_hcdi_pipe_stop_intr_polling( usba_pipe_handle_data_t *ph, usb_flags_t flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); int error = USB_SUCCESS; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_stop_intr_polling: ph = 0x%p fl = 0x%x", (void *)ph, flags); mutex_enter(&ehcip->ehci_int_mutex); error = ehci_stop_periodic_pipe_polling(ehcip, ph, flags); mutex_exit(&ehcip->ehci_int_mutex); return (error); } /* * ehci_hcdi_get_current_frame_number: * * Get the current usb frame number. * Return whether the request is handled successfully. */ int ehci_hcdi_get_current_frame_number( usba_device_t *usba_device, usb_frame_number_t *frame_number) { ehci_state_t *ehcip = ehci_obtain_state( usba_device->usb_root_hub_dip); int rval; ehcip = ehci_obtain_state(usba_device->usb_root_hub_dip); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); if (rval != USB_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); return (rval); } *frame_number = ehci_get_current_frame_number(ehcip); mutex_exit(&ehcip->ehci_int_mutex); USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_get_current_frame_number: " "Current frame number 0x%llx", (unsigned long long)(*frame_number)); return (rval); } /* * ehci_hcdi_get_max_isoc_pkts: * * Get maximum isochronous packets per usb isochronous request. * Return whether the request is handled successfully. */ int ehci_hcdi_get_max_isoc_pkts( usba_device_t *usba_device, uint_t *max_isoc_pkts_per_request) { ehci_state_t *ehcip = ehci_obtain_state( usba_device->usb_root_hub_dip); int rval; mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); mutex_exit(&ehcip->ehci_int_mutex); if (rval != USB_SUCCESS) { return (rval); } *max_isoc_pkts_per_request = EHCI_MAX_ISOC_PKTS_PER_XFER; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_get_max_isoc_pkts: maximum isochronous" "packets per usb isochronous request = 0x%x", *max_isoc_pkts_per_request); return (rval); } /* * ehci_hcdi_pipe_isoc_xfer: */ int ehci_hcdi_pipe_isoc_xfer( usba_pipe_handle_data_t *ph, usb_isoc_req_t *isoc_reqp, usb_flags_t usb_flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); int pipe_dir, rval; ehci_isoc_xwrapper_t *itw; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_isoc_xfer: ph = 0x%p reqp = 0x%p flags = 0x%x", (void *)ph, (void *)isoc_reqp, usb_flags); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); if (rval != USB_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); return (rval); } /* Get the isochronous pipe direction */ pipe_dir = ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK; if (pipe_dir == USB_EP_DIR_IN) { rval = ehci_start_periodic_pipe_polling(ehcip, ph, (usb_opaque_t)isoc_reqp, usb_flags); } else { /* Allocate transaction resources */ if ((itw = ehci_allocate_isoc_resources(ehcip, ph, isoc_reqp, usb_flags)) == NULL) { rval = USB_NO_RESOURCES; } else { rval = ehci_insert_isoc_req(ehcip, (ehci_pipe_private_t *)ph->p_hcd_private, itw, usb_flags); } } mutex_exit(&ehcip->ehci_int_mutex); return (rval); } /* * ehci_hcdi_pipe_stop_isoc_polling() */ /*ARGSUSED*/ int ehci_hcdi_pipe_stop_isoc_polling( usba_pipe_handle_data_t *ph, usb_flags_t flags) { ehci_state_t *ehcip = ehci_obtain_state( ph->p_usba_device->usb_root_hub_dip); int rval; USB_DPRINTF_L4(PRINT_MASK_HCDI, ehcip->ehci_log_hdl, "ehci_hcdi_pipe_stop_isoc_polling: ph = 0x%p fl = 0x%x", (void *)ph, flags); mutex_enter(&ehcip->ehci_int_mutex); rval = ehci_state_is_operational(ehcip); if (rval != USB_SUCCESS) { mutex_exit(&ehcip->ehci_int_mutex); return (rval); } rval = ehci_stop_periodic_pipe_polling(ehcip, ph, flags); mutex_exit(&ehcip->ehci_int_mutex); return (rval); }