/* * This file and its contents are supplied under the terms of the * Common Development and Distribution License ("CDDL"), version 1.0. * You may only use this file in accordance with the terms of version * 1.0 of the CDDL. * * A full copy of the text of the CDDL should have accompanied this * source. A copy of the CDDL is also available via the Internet at * http://www.illumos.org/license/CDDL. */ /* * Copyright (c) 2018, Joyent, Inc. * Copyright (c) 2019 by Western Digital Corporation */ /* * illumos USB framework endpoints and functions for xHCI. * * Please see the big theory statement in xhci.c for more information. */ #include #include #include #include xhci_t * xhci_hcdi_get_xhcip_from_dev(usba_device_t *ud) { dev_info_t *dip = ud->usb_root_hub_dip; xhci_t *xhcip = ddi_get_soft_state(xhci_soft_state, ddi_get_instance(dip)); VERIFY(xhcip != NULL); return (xhcip); } static xhci_t * xhci_hcdi_get_xhcip(usba_pipe_handle_data_t *ph) { return (xhci_hcdi_get_xhcip_from_dev(ph->p_usba_device)); } /* * While the xHCI hardware is capable of supporting power management, we don't * in the driver right now. Note, USBA doesn't seem to end up calling this entry * point. */ /* ARGSUSED */ static int xhci_hcdi_pm_support(dev_info_t *dip) { return (USB_FAILURE); } static int xhci_hcdi_pipe_open(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); xhci_pipe_t *pipe; xhci_endpoint_t *xep; xhci_device_t *xd; int kmflags = usb_flags & USB_FLAGS_SLEEP ? KM_SLEEP : KM_NOSLEEP; int ret; uint_t epid; mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } mutex_exit(&xhcip->xhci_lock); /* * If we're here, something must be trying to open an already-opened * pipe which is bad news. */ if (ph->p_hcd_private != NULL) { return (USB_FAILURE); } pipe = kmem_zalloc(sizeof (xhci_pipe_t), kmflags); if (pipe == NULL) { return (USB_NO_RESOURCES); } pipe->xp_opentime = gethrtime(); pipe->xp_pipe = ph; /* * If this is the root hub, there's nothing special to do on open. Just * go ahead and allow it to be opened. All we have to do is add this to * the list of our tracking structures for open pipes. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { xep = NULL; goto add; } /* * Now that we're here, we're being asked to open up an endpoint of some * kind. Because we've already handled the case of the root hub, * everything should have a device. */ epid = xhci_endpoint_pipe_to_epid(ph); xd = usba_hcdi_get_device_private(ph->p_usba_device); if (xd == NULL) { xhci_error(xhcip, "!encountered endpoint (%d) without device " "during pipe open", epid); kmem_free(pipe, sizeof (xhci_pipe_t)); return (USB_FAILURE); } /* * See if this endpoint exists or not, in general endpoints should not * exist except for the default control endpoint, which we don't tear * down until the device itself is cleaned up. Otherwise, a given pipe * can only be open once. */ mutex_enter(&xhcip->xhci_lock); if (epid == XHCI_DEFAULT_ENDPOINT) { xep = xd->xd_endpoints[epid]; VERIFY(xep != NULL); VERIFY(xep->xep_pipe == NULL); xep->xep_pipe = ph; mutex_exit(&xhcip->xhci_lock); ret = xhci_endpoint_update_default(xhcip, xd, xep); if (ret != USB_SUCCESS) { kmem_free(pipe, sizeof (xhci_pipe_t)); return (ret); } goto add; } if (xd->xd_endpoints[epid] != NULL) { mutex_exit(&xhcip->xhci_lock); kmem_free(pipe, sizeof (xhci_pipe_t)); xhci_log(xhcip, "!asked to open endpoint %d on slot %d and " "port %d, but endpoint already exists", epid, xd->xd_slot, xd->xd_port); return (USB_FAILURE); } /* * If we're opening an endpoint other than the default control endpoint, * then the device should have had a USB address assigned by the * controller. Sanity check that before continuing. */ if (epid != XHCI_DEFAULT_ENDPOINT) { VERIFY(xd->xd_addressed == B_TRUE); } /* * Okay, at this point we need to go create and set up an endpoint. * Once we're done, we'll try to install it and make sure that it * doesn't conflict with something else going on. */ ret = xhci_endpoint_init(xhcip, xd, ph); if (ret != 0) { mutex_exit(&xhcip->xhci_lock); kmem_free(pipe, sizeof (xhci_pipe_t)); if (ret == EIO) { xhci_error(xhcip, "failed to initialize endpoint %d " "on device slot %d and port %d: encountered fatal " "FM error, resetting device", epid, xd->xd_slot, xd->xd_port); xhci_fm_runtime_reset(xhcip); } return (USB_HC_HARDWARE_ERROR); } xep = xd->xd_endpoints[epid]; mutex_enter(&xd->xd_imtx); mutex_exit(&xhcip->xhci_lock); /* * Update the slot and input context for this endpoint. We make sure to * always set the slot as having changed in the context field as the * specification suggests we should and some hardware requires it. */ xd->xd_input->xic_drop_flags = LE_32(0); xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0) | XHCI_INCTX_MASK_DCI(epid + 1)); if (epid + 1 > XHCI_SCTX_GET_DCI(LE_32(xd->xd_slotin->xsc_info))) { uint32_t info; info = xd->xd_slotin->xsc_info; info &= ~XHCI_SCTX_DCI_MASK; info |= XHCI_SCTX_SET_DCI(epid + 1); xd->xd_slotin->xsc_info = info; } XHCI_DMA_SYNC(xd->xd_ictx, DDI_DMA_SYNC_FORDEV); if (xhci_check_dma_handle(xhcip, &xd->xd_ictx) != DDI_FM_OK) { mutex_exit(&xd->xd_imtx); xhci_endpoint_fini(xd, epid); kmem_free(pipe, sizeof (xhci_pipe_t)); xhci_error(xhcip, "failed to open pipe on endpoint %d of " "device with slot %d and port %d: encountered fatal FM " "error syncing device input context, resetting device", epid, xd->xd_slot, xd->xd_port); xhci_fm_runtime_reset(xhcip); return (USB_HC_HARDWARE_ERROR); } if ((ret = xhci_command_configure_endpoint(xhcip, xd)) != USB_SUCCESS) { mutex_exit(&xd->xd_imtx); xhci_endpoint_fini(xd, epid); kmem_free(pipe, sizeof (xhci_pipe_t)); return (ret); } mutex_exit(&xd->xd_imtx); add: pipe->xp_ep = xep; ph->p_hcd_private = (usb_opaque_t)pipe; mutex_enter(&xhcip->xhci_lock); list_insert_tail(&xhcip->xhci_usba.xa_pipes, pipe); mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } static void xhci_hcdi_periodic_free(xhci_t *xhcip, xhci_pipe_t *xp) { int i; xhci_periodic_pipe_t *xpp = &xp->xp_periodic; if (xpp->xpp_tsize == 0) return; for (i = 0; i < xpp->xpp_ntransfers; i++) { if (xpp->xpp_transfers[i] == NULL) continue; xhci_transfer_free(xhcip, xpp->xpp_transfers[i]); xpp->xpp_transfers[i] = NULL; } xpp->xpp_ntransfers = 0; xpp->xpp_tsize = 0; } /* * Iterate over all transfers and free everything on the pipe. Once done, update * the ring to basically 'consume' everything. For periodic IN endpoints, we * need to handle this somewhat differently and actually close the original * request and not deallocate the related pieces as those exist for the lifetime * of the endpoint and are constantly reused. */ static void xhci_hcdi_pipe_flush(xhci_t *xhcip, xhci_endpoint_t *xep, int intr_code) { xhci_transfer_t *xt; ASSERT(MUTEX_HELD(&xhcip->xhci_lock)); while ((xt = list_remove_head(&xep->xep_transfers)) != NULL) { if (xhci_endpoint_is_periodic_in(xep) == B_FALSE) { usba_hcdi_cb(xep->xep_pipe, xt->xt_usba_req, USB_CR_FLUSHED); xhci_transfer_free(xhcip, xt); } } if (xhci_endpoint_is_periodic_in(xep) == B_TRUE) { xhci_pipe_t *xp = (xhci_pipe_t *)xep->xep_pipe->p_hcd_private; xhci_periodic_pipe_t *xpp = &xp->xp_periodic; if (xpp->xpp_usb_req != NULL) { usba_hcdi_cb(xep->xep_pipe, xpp->xpp_usb_req, intr_code); xpp->xpp_usb_req = NULL; } } } /* * We've been asked to terminate some set of regular I/O on an interrupt pipe. * If this is for the root device, e.g. the xhci driver itself, then we remove * our interrupt callback. Otherwise we stop the device for interrupt polling as * follows: * * 1. Issue a stop endpoint command * 2. Check to make sure that the endpoint stopped and reset it if needed. * 3. Any thing that gets resolved can callback in the interim. * 4. Ensure that nothing is scheduled on the ring * 5. Skip the contents of the ring and set the TR dequeue pointer. * 6. Return the original callback with a USB_CR_STOPPED_POLLING, NULL out the * callback in the process. */ static int xhci_hcdi_pipe_poll_fini(usba_pipe_handle_data_t *ph, boolean_t is_close) { int ret; uint_t epid; xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); xhci_device_t *xd; xhci_endpoint_t *xep; xhci_pipe_t *xp; xhci_periodic_pipe_t *xpp; usb_opaque_t urp; mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { xhci_root_hub_intr_root_disable(xhcip); ret = USB_SUCCESS; mutex_exit(&xhcip->xhci_lock); return (ret); } xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { mutex_exit(&xhcip->xhci_lock); xhci_error(xhcip, "asked to stop intr polling on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; xp = (xhci_pipe_t *)ph->p_hcd_private; if (xp == NULL) { mutex_exit(&xhcip->xhci_lock); xhci_error(xhcip, "asked to do finish polling on slot %d, " "port %d, endpoint: %d, but no pipe structure", xd->xd_slot, xd->xd_port, epid); return (USB_FAILURE); } xpp = &xp->xp_periodic; /* * Ensure that no other resets or time outs are going on right now. */ while ((xep->xep_state & (XHCI_ENDPOINT_SERIALIZE)) != 0) { cv_wait(&xep->xep_state_cv, &xhcip->xhci_lock); } if (xpp->xpp_poll_state == XHCI_PERIODIC_POLL_IDLE) { mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } if (xpp->xpp_poll_state == XHCI_PERIODIC_POLL_STOPPING) { mutex_exit(&xhcip->xhci_lock); return (USB_FAILURE); } xpp->xpp_poll_state = XHCI_PERIODIC_POLL_STOPPING; xep->xep_state |= XHCI_ENDPOINT_QUIESCE; ret = xhci_endpoint_quiesce(xhcip, xd, xep); if (ret != USB_SUCCESS) { xhci_error(xhcip, "!failed to quiesce endpoint on slot %d, " "port %d, endpoint: %d, failed with %d.", xd->xd_slot, xd->xd_port, epid, ret); xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE; cv_broadcast(&xep->xep_state_cv); mutex_exit(&xhcip->xhci_lock); return (ret); } /* * Okay, we've stopped this ring time to wrap it all up. Remove all the * transfers, note they aren't freed like a pipe reset. */ while (list_is_empty(&xep->xep_transfers) == 0) (void) list_remove_head(&xep->xep_transfers); xhci_ring_skip(&xep->xep_ring); mutex_exit(&xhcip->xhci_lock); if ((ret = xhci_command_set_tr_dequeue(xhcip, xd, xep)) != USB_SUCCESS) { xhci_error(xhcip, "!failed to reset endpoint ring on slot %d, " "port %d, endpoint: %d, failed with %d.", xd->xd_slot, xd->xd_port, epid, ret); mutex_enter(&xhcip->xhci_lock); xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE; cv_broadcast(&xep->xep_state_cv); mutex_exit(&xhcip->xhci_lock); return (ret); } mutex_enter(&xhcip->xhci_lock); urp = xpp->xpp_usb_req; xpp->xpp_usb_req = NULL; xpp->xpp_poll_state = XHCI_PERIODIC_POLL_IDLE; xep->xep_state &= ~XHCI_ENDPOINT_PERIODIC; mutex_exit(&xhcip->xhci_lock); /* * It's possible that with a persistent pipe, we may not actually have * anything left to call back on, because we already had. */ if (urp != NULL) { usba_hcdi_cb(ph, urp, is_close == B_TRUE ? USB_CR_PIPE_CLOSING : USB_CR_STOPPED_POLLING); } /* * Notify anything waiting for us that we're done quiescing this device. */ mutex_enter(&xhcip->xhci_lock); xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE; cv_broadcast(&xep->xep_state_cv); mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } /* * Tear down everything that we did in open. After this, the consumer of this * USB device is done. */ /* ARGSUSED */ static int xhci_hcdi_pipe_close(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); xhci_pipe_t *xp; xhci_device_t *xd; xhci_endpoint_t *xep; uint32_t info; int ret, i; uint_t epid; if ((ph->p_ep.bmAttributes & USB_EP_ATTR_MASK) == USB_EP_ATTR_INTR && xhcip->xhci_usba.xa_intr_cb_ph != NULL) { if ((ret = xhci_hcdi_pipe_poll_fini(ph, B_TRUE)) != USB_SUCCESS) { return (ret); } } mutex_enter(&xhcip->xhci_lock); xp = (xhci_pipe_t *)ph->p_hcd_private; VERIFY(xp != NULL); /* * The default endpoint is special. It is created and destroyed with the * device. So like with open, closing it is just state tracking. The * same is true for the root hub. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) goto remove; xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { mutex_exit(&xhcip->xhci_lock); xhci_error(xhcip, "asked to do close pipe on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; if (xp->xp_ep != NULL && xp->xp_ep->xep_num == XHCI_DEFAULT_ENDPOINT) { xep->xep_pipe = NULL; goto remove; } /* * We need to clean up the endpoint. So the first thing we need to do is * stop it with a configure endpoint command. Once it's stopped, we can * free all associated resources. */ mutex_enter(&xd->xd_imtx); /* * Potentially update the slot input context about the current max * endpoint. Make sure to set that the slot context is being updated * here as it may be changing and some hardware requires it. */ xd->xd_input->xic_drop_flags = LE_32(XHCI_INCTX_MASK_DCI(epid + 1)); xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0)); for (i = XHCI_NUM_ENDPOINTS - 1; i >= 0; i--) { if (xd->xd_endpoints[i] != NULL && xd->xd_endpoints[i] != xep) break; } info = xd->xd_slotin->xsc_info; info &= ~XHCI_SCTX_DCI_MASK; info |= XHCI_SCTX_SET_DCI(i + 1); xd->xd_slotin->xsc_info = info; /* * Also zero out our context for this endpoint. Note that we don't * bother with syncing DMA memory here as it's not required to be synced * for this operation. */ bzero(xd->xd_endin[xep->xep_num], sizeof (xhci_endpoint_context_t)); /* * Stop the device and kill our timeout. Note, it is safe to hold the * device's input mutex across the untimeout, this lock should never be * referenced by the timeout code. */ xep->xep_state |= XHCI_ENDPOINT_TEARDOWN; mutex_exit(&xhcip->xhci_lock); (void) untimeout(xep->xep_timeout); ret = xhci_command_configure_endpoint(xhcip, xd); mutex_exit(&xd->xd_imtx); if (ret != USB_SUCCESS) return (ret); mutex_enter(&xhcip->xhci_lock); /* * Now that we've unconfigured the endpoint. See if we need to flush any * transfers. */ xhci_hcdi_pipe_flush(xhcip, xep, USB_CR_PIPE_CLOSING); if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { xhci_hcdi_periodic_free(xhcip, xp); } xhci_endpoint_fini(xd, epid); remove: ph->p_hcd_private = NULL; list_remove(&xhcip->xhci_usba.xa_pipes, xp); kmem_free(xp, sizeof (xhci_pipe_t)); mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } /* * We've been asked to reset a pipe aka an endpoint. This endpoint may be in an * arbitrary state, it may be running or it may be halted. In this case, we go * through and check whether or not we know it's been halted or not. If it has * not, then we stop the endpoint. * * Once the endpoint has been stopped, walk all transfers and go ahead and * basically return them as being flushed. Then finally set the dequeue point * for this endpoint. */ /* ARGSUSED */ static int xhci_hcdi_pipe_reset(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); xhci_device_t *xd; xhci_endpoint_t *xep; uint_t epid; int ret; mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { mutex_exit(&xhcip->xhci_lock); return (USB_NOT_SUPPORTED); } xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { mutex_exit(&xhcip->xhci_lock); xhci_error(xhcip, "asked to do reset pipe on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; /* * Ensure that no other resets or time outs are going on right now. */ while ((xep->xep_state & (XHCI_ENDPOINT_SERIALIZE)) != 0) { cv_wait(&xep->xep_state_cv, &xhcip->xhci_lock); } xep->xep_state |= XHCI_ENDPOINT_QUIESCE; ret = xhci_endpoint_quiesce(xhcip, xd, xep); if (ret != USB_SUCCESS) { /* * We failed to quiesce for some reason, remove the flag and let * someone else give it a shot. */ xhci_error(xhcip, "!failed to quiesce endpoint on slot %d, " "port %d, endpoint: %d, failed with %d.", xd->xd_slot, xd->xd_port, epid, ret); xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE; cv_broadcast(&xep->xep_state_cv); mutex_exit(&xhcip->xhci_lock); return (ret); } xhci_ring_skip(&xep->xep_ring); mutex_exit(&xhcip->xhci_lock); if ((ret = xhci_command_set_tr_dequeue(xhcip, xd, xep)) != USB_SUCCESS) { xhci_error(xhcip, "!failed to reset endpoint ring on slot %d, " "port %d, endpoint: %d, failed setting ring dequeue with " "%d.", xd->xd_slot, xd->xd_port, epid, ret); mutex_enter(&xhcip->xhci_lock); xep->xep_state &= ~XHCI_ENDPOINT_QUIESCE; cv_broadcast(&xep->xep_state_cv); mutex_exit(&xhcip->xhci_lock); return (ret); } mutex_enter(&xhcip->xhci_lock); xhci_hcdi_pipe_flush(xhcip, xep, USB_CR_PIPE_RESET); /* * We need to remove the periodic flag as part of resetting, as if this * was used for periodic activity, it no longer is and therefore can now * be used for such purposes. * * Notify anything waiting for us that we're done quiescing this device. */ xep->xep_state &= ~(XHCI_ENDPOINT_QUIESCE | XHCI_ENDPOINT_PERIODIC); cv_broadcast(&xep->xep_state_cv); mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } /* * We're asked to reset or change the data toggle, which is used in a few cases. * However, there doesn't seem to be a good way to do this in xHCI as the data * toggle isn't exposed. It seems that dropping a reset endpoint would * theoretically do this; however, that can only be used when in the HALTED * state. As such, for now we just return. */ /* ARGSUSED */ void xhci_hcdi_pipe_reset_data_toggle(usba_pipe_handle_data_t *pipe_handle) { } /* * We need to convert the USB request to an 8-byte little endian value. If we * didn't have to think about big endian systems, this would be fine. * Unfortunately, with them, this is a bit confusing. The problem is that if you * think of this as a struct layout, the order that we or things together * represents their byte layout. e.g. ctrl_bRequest is at offset 1 in the SETUP * STAGE trb. However, when it becomes a part of a 64-bit big endian number, if * ends up at byte 7, where as it needs to be at one. Hence why we do a final * LE_64 at the end of this, to convert this into the byte order that it's * expected to be in. */ static uint64_t xhci_hcdi_ctrl_req_to_trb(usb_ctrl_req_t *ucrp) { uint64_t ret = ucrp->ctrl_bmRequestType | (ucrp->ctrl_bRequest << 8) | ((uint64_t)LE_16(ucrp->ctrl_wValue) << 16) | ((uint64_t)LE_16(ucrp->ctrl_wIndex) << 32) | ((uint64_t)LE_16(ucrp->ctrl_wLength) << 48); return (LE_64(ret)); } /* * USBA calls us in order to make a specific control type request to a device, * potentially even the root hub. If the request is for the root hub, then we * need to intercept this and cons up the requested data. */ static int xhci_hcdi_pipe_ctrl_xfer(usba_pipe_handle_data_t *ph, usb_ctrl_req_t *ucrp, usb_flags_t usb_flags) { int ret, statusdir, trt; uint_t ep; xhci_device_t *xd; xhci_endpoint_t *xep; xhci_transfer_t *xt; boolean_t datain; xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { ret = xhci_root_hub_ctrl_req(xhcip, ph, ucrp); mutex_exit(&xhcip->xhci_lock); return (ret); } /* * Determine the device and endpoint. */ xd = usba_hcdi_get_device_private(ph->p_usba_device); ep = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[ep] == NULL) { mutex_exit(&xhcip->xhci_lock); xhci_error(xhcip, "asked to do control transfer on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, ep); return (USB_FAILURE); } xep = xd->xd_endpoints[ep]; /* * There are several types of requests that we have to handle in special * ways in xHCI. If we have one of those requests, then we don't * necessarily go through the normal path. These special cases are all * documented in xHCI 1.1 / 4.5.4. * * Looking at that, you may ask why aren't SET_CONFIGURATION and SET_IF * special cased here. This action is a little confusing by default. The * xHCI specification requires that we may need to issue a configure * endpoint command as part of this. However, the xHCI 1.1 / 4.5.4.2 * states that we don't actually need to if nothing in the endpoint * configuration context has changed. Because nothing in it should have * changed as part of this, we don't need to do anything and instead * just can issue the request normally. We're also assuming in the * USB_REQ_SET_IF case that if something's changing the interface, the * non-default endpoint will have yet to be opened. */ if (ucrp->ctrl_bmRequestType == USB_DEV_REQ_HOST_TO_DEV && ucrp->ctrl_bRequest == USB_REQ_SET_ADDRESS) { /* * As we've defined an explicit set-address endpoint, we should * never call this function. If we get here, always fail. */ mutex_exit(&xhcip->xhci_lock); usba_hcdi_cb(ph, (usb_opaque_t)ucrp, USB_CR_NOT_SUPPORTED); return (USB_SUCCESS); } mutex_exit(&xhcip->xhci_lock); /* * Allocate the transfer memory, etc. */ xt = xhci_transfer_alloc(xhcip, xep, ucrp->ctrl_wLength, 2, usb_flags); if (xt == NULL) { return (USB_NO_RESOURCES); } xt->xt_usba_req = (usb_opaque_t)ucrp; xt->xt_timeout = ucrp->ctrl_timeout; if (xt->xt_timeout == 0) { xt->xt_timeout = HCDI_DEFAULT_TIMEOUT; } if (ucrp->ctrl_wLength > 0) { if ((ucrp->ctrl_bmRequestType & USB_DEV_REQ_DEV_TO_HOST) != 0) { trt = XHCI_TRB_TRT_IN; datain = B_TRUE; statusdir = 0; } else { trt = XHCI_TRB_TRT_OUT; datain = B_FALSE; statusdir = XHCI_TRB_DIR_IN; xhci_transfer_copy(xt, ucrp->ctrl_data->b_rptr, ucrp->ctrl_wLength, B_FALSE); if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) != DDI_FM_OK) { xhci_transfer_free(xhcip, xt); xhci_error(xhcip, "failed to synchronize ctrl " "transfer DMA memory on endpoint %u of " "device on slot %d and port %d: resetting " "device", xep->xep_num, xd->xd_slot, xd->xd_port); xhci_fm_runtime_reset(xhcip); return (USB_HC_HARDWARE_ERROR); } } } else { trt = 0; datain = B_FALSE; statusdir = XHCI_TRB_DIR_IN; } /* * We always fill in the required setup and status TRBs ourselves; * however, to minimize our knowledge about how the data has been split * across multiple DMA cookies in an SGL, we leave that to the transfer * logic to fill in. */ xt->xt_trbs[0].trb_addr = xhci_hcdi_ctrl_req_to_trb(ucrp); xt->xt_trbs[0].trb_status = LE_32(XHCI_TRB_LEN(8) | XHCI_TRB_INTR(0)); xt->xt_trbs[0].trb_flags = LE_32(trt | XHCI_TRB_IDT | XHCI_TRB_TYPE_SETUP); if (ucrp->ctrl_wLength > 0) xhci_transfer_trb_fill_data(xep, xt, 1, datain); xt->xt_trbs[xt->xt_ntrbs - 1].trb_addr = 0; xt->xt_trbs[xt->xt_ntrbs - 1].trb_status = LE_32(XHCI_TRB_INTR(0)); xt->xt_trbs[xt->xt_ntrbs - 1].trb_flags = LE_32(XHCI_TRB_TYPE_STATUS | XHCI_TRB_IOC | statusdir); mutex_enter(&xhcip->xhci_lock); /* * Schedule the transfer, allocating resources in the process. */ if (xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE) != 0) { xhci_transfer_free(xhcip, xt); mutex_exit(&xhcip->xhci_lock); return (USB_NO_RESOURCES); } mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } /* * This request is trying to get the upper bound on the amount of data we're * willing transfer in one go. Note that this amount can be broken down into * multiple SGL entries, this interface doesn't particularly care about that. */ /* ARGSUSED */ static int xhci_hcdi_bulk_transfer_size(usba_device_t *ud, size_t *sizep) { if (sizep != NULL) *sizep = XHCI_MAX_TRANSFER; return (USB_SUCCESS); } /* * Perform a bulk transfer. This is a pretty straightforward action. We * basically just allocate the appropriate transfer and try to schedule it, * hoping there is enough space. */ static int xhci_hcdi_pipe_bulk_xfer(usba_pipe_handle_data_t *ph, usb_bulk_req_t *ubrp, usb_flags_t usb_flags) { uint_t epid; xhci_device_t *xd; xhci_endpoint_t *xep; xhci_transfer_t *xt; boolean_t datain; xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { mutex_exit(&xhcip->xhci_lock); return (USB_NOT_SUPPORTED); } xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { mutex_exit(&xhcip->xhci_lock); xhci_error(xhcip, "asked to do control transfer on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; mutex_exit(&xhcip->xhci_lock); if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { datain = B_TRUE; } else { datain = B_FALSE; } xt = xhci_transfer_alloc(xhcip, xep, ubrp->bulk_len, 0, usb_flags); if (xt == NULL) { return (USB_NO_RESOURCES); } xt->xt_usba_req = (usb_opaque_t)ubrp; xt->xt_timeout = ubrp->bulk_timeout; if (xt->xt_timeout == 0) { xt->xt_timeout = HCDI_DEFAULT_TIMEOUT; } if (ubrp->bulk_len > 0 && datain == B_FALSE) { xhci_transfer_copy(xt, ubrp->bulk_data->b_rptr, ubrp->bulk_len, B_FALSE); if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) != DDI_FM_OK) { xhci_transfer_free(xhcip, xt); xhci_error(xhcip, "failed to synchronize bulk " "transfer DMA memory on endpoint %u of " "device on slot %d and port %d: resetting " "device", xep->xep_num, xd->xd_slot, xd->xd_port); xhci_fm_runtime_reset(xhcip); return (USB_HC_HARDWARE_ERROR); } } xhci_transfer_trb_fill_data(xep, xt, 0, datain); mutex_enter(&xhcip->xhci_lock); if (xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE) != 0) { xhci_transfer_free(xhcip, xt); mutex_exit(&xhcip->xhci_lock); return (USB_NO_RESOURCES); } mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } static void xhci_hcdi_isoc_transfer_fill(xhci_device_t *xd, xhci_endpoint_t *xep, xhci_transfer_t *xt, usb_isoc_req_t *usrp) { int i; uintptr_t buf; buf = xt->xt_buffer.xdb_cookies[0].dmac_laddress; for (i = 0; i < usrp->isoc_pkts_count; i++) { int flags; uint_t tbc, tlbpc; ushort_t len = usrp->isoc_pkt_descr[i].isoc_pkt_length; xhci_trb_t *trb = &xt->xt_trbs[i]; trb->trb_addr = LE_64(buf); /* * Because we know that a single frame can have all of its data * in a single instance, we know that we don't need to do * anything special here. */ trb->trb_status = LE_32(XHCI_TRB_LEN(len) | XHCI_TRB_TDREM(0) | XHCI_TRB_INTR(0)); /* * Always enable SIA to start the frame ASAP. We also always * enable an interrupt on a short packet. If this is the last * trb, then we will set IOC. Each TRB created here is really * its own TD. However, we only set an interrupt on the last * entry to better deal with scheduling. */ flags = XHCI_TRB_SIA | XHCI_TRB_ISP | XHCI_TRB_SET_FRAME(0); flags |= XHCI_TRB_TYPE_ISOCH; if (i + 1 == usrp->isoc_pkts_count) flags |= XHCI_TRB_IOC; /* * Now we need to calculate the TBC and the TLBPC. */ xhci_transfer_calculate_isoc(xd, xep, len, &tbc, &tlbpc); flags |= XHCI_TRB_SET_TBC(tbc); flags |= XHCI_TRB_SET_TLBPC(tlbpc); trb->trb_flags = LE_32(flags); buf += len; /* * Go through and copy the required data to our local copy of * the isoc descriptor. By default, we assume that all data will * be copied and the status set to OK. This mirrors the fact * that we won't get a notification unless there's been an * error or short packet transfer. */ xt->xt_isoc[i].isoc_pkt_length = len; xt->xt_isoc[i].isoc_pkt_actual_length = len; xt->xt_isoc[i].isoc_pkt_status = USB_CR_OK; } } /* * Initialize periodic IN requests (both interrupt and isochronous) */ static int xhci_hcdi_periodic_init(xhci_t *xhcip, usba_pipe_handle_data_t *ph, usb_opaque_t usb_req, size_t len, int usb_flags) { int i, ret; uint_t epid; xhci_device_t *xd; xhci_endpoint_t *xep; xhci_pipe_t *xp; xhci_periodic_pipe_t *xpp; mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { xhci_error(xhcip, "asked to do periodic transfer on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); mutex_exit(&xhcip->xhci_lock); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; xp = (xhci_pipe_t *)ph->p_hcd_private; if (xp == NULL) { xhci_error(xhcip, "asked to do periodic transfer on slot %d, " "port %d, endpoint: %d, but no pipe structure", xd->xd_slot, xd->xd_port, epid); mutex_exit(&xhcip->xhci_lock); return (USB_FAILURE); } xpp = &xp->xp_periodic; /* * Only allow a single polling request at any given time. */ if (xpp->xpp_usb_req != NULL) { mutex_exit(&xhcip->xhci_lock); return (USB_BUSY); } /* * We keep allocations around in case we restart polling, which most * devices do (not really caring about a lost event). However, we don't * support a driver changing that size on us, which it probably won't. * If we stumble across driver that does, then this will need to become * a lot more complicated. */ if (xpp->xpp_tsize > 0 && xpp->xpp_tsize < len) { mutex_exit(&xhcip->xhci_lock); return (USB_INVALID_REQUEST); } if (xpp->xpp_tsize == 0) { int ntrbs; int ntransfers; /* * What we allocate varies based on whether or not this is an * isochronous or interrupt IN periodic. */ if (xep->xep_type == USB_EP_ATTR_INTR) { ntrbs = 0; ntransfers = XHCI_INTR_IN_NTRANSFERS; } else { usb_isoc_req_t *usrp; ASSERT(xep->xep_type == USB_EP_ATTR_ISOCH); usrp = (usb_isoc_req_t *)usb_req; ntrbs = usrp->isoc_pkts_count; ntransfers = XHCI_ISOC_IN_NTRANSFERS; } xpp->xpp_tsize = len; xpp->xpp_ntransfers = ntransfers; for (i = 0; i < xpp->xpp_ntransfers; i++) { xhci_transfer_t *xt = xhci_transfer_alloc(xhcip, xep, len, ntrbs, usb_flags); if (xt == NULL) { xhci_hcdi_periodic_free(xhcip, xp); mutex_exit(&xhcip->xhci_lock); return (USB_NO_RESOURCES); } if (xep->xep_type == USB_EP_ATTR_INTR) { xhci_transfer_trb_fill_data(xep, xt, 0, B_TRUE); } else { usb_isoc_req_t *usrp; usrp = (usb_isoc_req_t *)usb_req; xhci_hcdi_isoc_transfer_fill(xd, xep, xt, usrp); xt->xt_data_tohost = B_TRUE; } xpp->xpp_transfers[i] = xt; } } /* * Mark the endpoint as periodic so we don't have timeouts at play. */ xep->xep_state |= XHCI_ENDPOINT_PERIODIC; /* * Now that we've allocated everything, go ahead and schedule them and * kick off the ring. */ for (i = 0; i < xpp->xpp_ntransfers; i++) { int ret; ret = xhci_endpoint_schedule(xhcip, xd, xep, xpp->xpp_transfers[i], B_FALSE); if (ret != 0) { (void) xhci_ring_reset(xhcip, &xep->xep_ring); xep->xep_state &= ~XHCI_ENDPOINT_PERIODIC; mutex_exit(&xhcip->xhci_lock); return (ret); } } /* * Don't worry about freeing memory, it'll be done when the endpoint * closes and the whole system is reset. */ xpp->xpp_usb_req = usb_req; xpp->xpp_poll_state = XHCI_PERIODIC_POLL_ACTIVE; ret = xhci_endpoint_ring(xhcip, xd, xep); mutex_exit(&xhcip->xhci_lock); return (ret); } static int xhci_hcdi_intr_oneshot(xhci_t *xhcip, usba_pipe_handle_data_t *ph, usb_intr_req_t *uirp, usb_flags_t usb_flags) { uint_t epid; xhci_device_t *xd; xhci_endpoint_t *xep; xhci_transfer_t *xt; boolean_t datain; mblk_t *mp = NULL; mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { xhci_error(xhcip, "asked to do interrupt transfer on slot %d, " "port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); mutex_exit(&xhcip->xhci_lock); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; mutex_exit(&xhcip->xhci_lock); if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { datain = B_TRUE; } else { datain = B_FALSE; } xt = xhci_transfer_alloc(xhcip, xep, uirp->intr_len, 0, usb_flags); if (xt == NULL) { return (USB_NO_RESOURCES); } xt->xt_usba_req = (usb_opaque_t)uirp; xt->xt_timeout = uirp->intr_timeout; if (xt->xt_timeout == 0) { xt->xt_timeout = HCDI_DEFAULT_TIMEOUT; } /* * Unlike other request types, USB Interrupt-IN requests aren't required * to have allocated the message block for data. If they haven't, we * take care of that now. */ if (uirp->intr_len > 0 && datain == B_TRUE && uirp->intr_data == NULL) { if (usb_flags & USB_FLAGS_SLEEP) { mp = allocb_wait(uirp->intr_len, BPRI_LO, STR_NOSIG, NULL); } else { mp = allocb(uirp->intr_len, 0); } if (mp == NULL) { xhci_transfer_free(xhcip, xt); mutex_exit(&xhcip->xhci_lock); return (USB_NO_RESOURCES); } uirp->intr_data = mp; } if (uirp->intr_len > 0 && datain == B_FALSE) { xhci_transfer_copy(xt, uirp->intr_data->b_rptr, uirp->intr_len, B_FALSE); if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) != DDI_FM_OK) { xhci_transfer_free(xhcip, xt); xhci_error(xhcip, "failed to synchronize interrupt " "transfer DMA memory on endpoint %u of " "device on slot %d and port %d: resetting " "device", xep->xep_num, xd->xd_slot, xd->xd_port); xhci_fm_runtime_reset(xhcip); return (USB_HC_HARDWARE_ERROR); } } xhci_transfer_trb_fill_data(xep, xt, 0, datain); mutex_enter(&xhcip->xhci_lock); if (xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE) != 0) { if (mp != NULL) { uirp->intr_data = NULL; freemsg(mp); } xhci_transfer_free(xhcip, xt); mutex_exit(&xhcip->xhci_lock); return (USB_NO_RESOURCES); } mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } /* * We've been asked to perform an interrupt transfer. When this is an interrupt * IN endpoint, that means that the hcd is being asked to start polling on the * endpoint. When the endpoint is the root hub, it effectively becomes synthetic * polling. * * When we have an interrupt out endpoint, then this is just a single simple * interrupt request that we send out and there isn't much special to do beyond * the normal activity. */ static int xhci_hcdi_pipe_intr_xfer(usba_pipe_handle_data_t *ph, usb_intr_req_t *uirp, usb_flags_t usb_flags) { int ret; xhci_t *xhcip = xhci_hcdi_get_xhcip(ph); if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { ret = xhci_root_hub_intr_root_enable(xhcip, ph, uirp); } else if (uirp->intr_attributes & USB_ATTRS_ONE_XFER) { ret = xhci_hcdi_intr_oneshot(xhcip, ph, uirp, usb_flags); } else { ret = xhci_hcdi_periodic_init(xhcip, ph, (usb_opaque_t)uirp, uirp->intr_len, usb_flags); } } else { if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { return (USB_NOT_SUPPORTED); } ret = xhci_hcdi_intr_oneshot(xhcip, ph, uirp, usb_flags); } return (ret); } /* ARGSUSED */ static int xhci_hcdi_pipe_stop_intr_polling(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { return (xhci_hcdi_pipe_poll_fini(ph, B_FALSE)); } static int xhci_hcdi_isoc_periodic(xhci_t *xhcip, usba_pipe_handle_data_t *ph, usb_isoc_req_t *usrp, usb_flags_t usb_flags) { int i; size_t count; count = 0; for (i = 0; i < usrp->isoc_pkts_count; i++) { count += usrp->isoc_pkt_descr[i].isoc_pkt_length; } return (xhci_hcdi_periodic_init(xhcip, ph, (usb_opaque_t)usrp, count, usb_flags)); } /* * This is used to create an isochronous request to send data out to the device. * This is a single one shot request, it is not something that we'll have to * repeat over and over. */ static int xhci_hcdi_isoc_oneshot(xhci_t *xhcip, usba_pipe_handle_data_t *ph, usb_isoc_req_t *usrp, usb_flags_t usb_flags) { int i, ret; uint_t epid; size_t count, mblen; xhci_device_t *xd; xhci_endpoint_t *xep; xhci_transfer_t *xt; count = 0; for (i = 0; i < usrp->isoc_pkts_count; i++) { count += usrp->isoc_pkt_descr[i].isoc_pkt_length; } mblen = MBLKL(usrp->isoc_data); if (count != mblen) { return (USB_INVALID_ARGS); } mutex_enter(&xhcip->xhci_lock); if (xhcip->xhci_state & XHCI_S_ERROR) { mutex_exit(&xhcip->xhci_lock); return (USB_HC_HARDWARE_ERROR); } xd = usba_hcdi_get_device_private(ph->p_usba_device); epid = xhci_endpoint_pipe_to_epid(ph); if (xd->xd_endpoints[epid] == NULL) { xhci_error(xhcip, "asked to do isochronous transfer on slot " "%d, port %d, endpoint: %d, but no endpoint structure", xd->xd_slot, xd->xd_port, epid); mutex_exit(&xhcip->xhci_lock); return (USB_FAILURE); } xep = xd->xd_endpoints[epid]; mutex_exit(&xhcip->xhci_lock); xt = xhci_transfer_alloc(xhcip, xep, mblen, usrp->isoc_pkts_count, usb_flags); if (xt == NULL) { return (USB_NO_RESOURCES); } xt->xt_usba_req = (usb_opaque_t)usrp; /* * USBA doesn't provide any real way for a timeout to be defined for an * isochronous event. However, since we technically aren't a periodic * endpoint, go ahead and always set the default timeout. It's better * than nothing. */ xt->xt_timeout = HCDI_DEFAULT_TIMEOUT; xhci_transfer_copy(xt, usrp->isoc_data->b_rptr, mblen, B_FALSE); if (xhci_transfer_sync(xhcip, xt, DDI_DMA_SYNC_FORDEV) != DDI_FM_OK) { xhci_transfer_free(xhcip, xt); xhci_error(xhcip, "failed to synchronize isochronous " "transfer DMA memory on endpoint %u of " "device on slot %d and port %d: resetting " "device", xep->xep_num, xd->xd_slot, xd->xd_port); xhci_fm_runtime_reset(xhcip); return (USB_HC_HARDWARE_ERROR); } /* * Fill in the ISOC data. Note, that we always use ASAP scheduling and * we don't support specifying the frame at this time, for better or * worse. */ xhci_hcdi_isoc_transfer_fill(xd, xep, xt, usrp); mutex_enter(&xhcip->xhci_lock); ret = xhci_endpoint_schedule(xhcip, xd, xep, xt, B_TRUE); mutex_exit(&xhcip->xhci_lock); return (ret); } static int xhci_hcdi_pipe_isoc_xfer(usba_pipe_handle_data_t *ph, usb_isoc_req_t *usrp, usb_flags_t usb_flags) { int ret; xhci_t *xhcip; xhcip = xhci_hcdi_get_xhcip(ph); /* * We don't support isochronous transactions on the root hub at all. * Always fail them if for some reason we end up here. */ if (ph->p_usba_device->usb_addr == ROOT_HUB_ADDR) { return (USB_NOT_SUPPORTED); } /* * We do not support being asked to set the frame ID at this time. We * require that everything specify the attribute * USB_ATTRS_ISOC_XFER_ASAP. */ if (!(usrp->isoc_attributes & USB_ATTRS_ISOC_XFER_ASAP)) { return (USB_NOT_SUPPORTED); } if ((ph->p_ep.bEndpointAddress & USB_EP_DIR_MASK) == USB_EP_DIR_IN) { /* * Note, there is no such thing as a non-periodic isochronous * incoming transfer. */ ret = xhci_hcdi_isoc_periodic(xhcip, ph, usrp, usb_flags); } else { ret = xhci_hcdi_isoc_oneshot(xhcip, ph, usrp, usb_flags); } return (ret); } /* ARGSUSED */ static int xhci_hcdi_pipe_stop_isoc_polling(usba_pipe_handle_data_t *ph, usb_flags_t usb_flags) { return (xhci_hcdi_pipe_poll_fini(ph, B_FALSE)); } /* * This is asking us for the current frame number. The USBA expects this to * actually be a bit of a fiction, as it tries to maintain a frame number well * beyond what the hardware actually contains in its registers. Hardware * basically has a 14-bit counter, whereas we need to have a constant amount of * milliseconds. * * Today, no client drivers actually use this API and everyone specifies the * attribute to say that we should schedule things ASAP. So until we have some * real device that want this functionality, we're going to fail. */ /* ARGSUSED */ static int xhci_hcdi_get_current_frame_number(usba_device_t *usba_device, usb_frame_number_t *frame_number) { return (USB_FAILURE); } /* * See the comments around the XHCI_ISOC_MAX_TRB macro for more information. */ /* ARGSUSED */ static int xhci_hcdi_get_max_isoc_pkts(usba_device_t *usba_device, uint_t *max_isoc_pkts_per_request) { *max_isoc_pkts_per_request = XHCI_ISOC_MAX_TRB; return (USB_SUCCESS); } /* * VERSION 2 ops and helpers */ static void xhci_hcdi_device_free(xhci_device_t *xd) { xhci_dma_free(&xd->xd_ictx); xhci_dma_free(&xd->xd_octx); mutex_destroy(&xd->xd_imtx); kmem_free(xd, sizeof (xhci_device_t)); } /* * Calculate the device's route string. In USB 3.0 the route string is a 20-bit * number. Each four bits represent a port number attached to a deeper hub. * Particularly it represents the port on that current hub that you need to go * down to reach the next device. Bits 0-3 represent the first *external* hub. * So a device connected to a root hub has a route string of zero. Imagine the * following set of devices: * * . port 2 . port 5 * . . * +----------+ . +--------+ . +-------+ * | root hub |-*->| hub 1 |-*->| hub 2 | * +----------+ +--------+ +-------+ * * . port 12 * . port 8 * . port 1 * v v v * +-------+ +-------+ +-------+ * | dev a | | dev b | | dev c | * +-------+ +-------+ +-------+ * * So, based on the above diagram, device a should have a route string of 0, * because it's directly connected to the root port. Device b would simply have * a route string of 8. This is because it travels through one non-root hub, hub * 1, and it does so on port 8. The root ports basically don't matter. Device c * would then have a route string of 0x15, as it's first traversing through hub * 1 on port 2 and then hub 2 on port 5. * * Finally, it's worth mentioning that because it's a four bit field, if for * some reason a device has more than 15 ports, we just treat the value as 15. * * Note, as part of this, we also grab what port on the root hub this whole * chain is on, as we're required to store that information in the slot context. */ static void xhci_hcdi_device_route(usba_device_t *ud, uint32_t *routep, uint32_t *root_port) { uint32_t route = 0; usba_device_t *hub = ud->usb_parent_hub; usba_device_t *port_dev = ud; ASSERT(hub != NULL); /* * Iterate over every hub, updating the route as we go. When we * encounter a hub without a parent, then we're at the root hub. At * which point, the port we want is on port_dev (the child of hub). */ while (hub->usb_parent_hub != NULL) { uint32_t p; p = port_dev->usb_port; if (p > 15) p = 15; route <<= 4; route |= p & 0xf; port_dev = hub; hub = hub->usb_parent_hub; } ASSERT(port_dev->usb_parent_hub == hub); *root_port = port_dev->usb_port; *routep = XHCI_ROUTE_MASK(route); } /* * If a low or full speed device is behind a high-speed device that is not a * root hub, then we must include the port and slot of that device. USBA already * stores this device in the usb_hs_hub_usba_dev member. */ static uint32_t xhci_hcdi_device_tt(usba_device_t *ud) { uint32_t ret; xhci_device_t *xd; if (ud->usb_port_status >= USBA_HIGH_SPEED_DEV) return (0); if (ud->usb_hs_hub_usba_dev == NULL) return (0); ASSERT(ud->usb_hs_hub_usba_dev != NULL); ASSERT(ud->usb_hs_hub_usba_dev->usb_parent_hub != NULL); xd = usba_hcdi_get_device_private(ud->usb_hs_hub_usba_dev); ASSERT(xd != NULL); ret = XHCI_SCTX_SET_TT_HUB_SID(xd->xd_slot); ret |= XHCI_SCTX_SET_TT_PORT_NUM(ud->usb_hs_hub_usba_dev->usb_port); return (ret); } /* * Initialize a new device. This allocates a device slot from the controller, * which tranfers it to our control. */ static int xhci_hcdi_device_init(usba_device_t *ud, usb_port_t port, void **hcdpp) { int ret, i; xhci_device_t *xd; ddi_device_acc_attr_t acc; ddi_dma_attr_t attr; xhci_t *xhcip = xhci_hcdi_get_xhcip_from_dev(ud); size_t isize, osize, incr; uint32_t route, rp, info, info2, tt; xd = kmem_zalloc(sizeof (xhci_device_t), KM_SLEEP); xd->xd_port = port; xd->xd_usbdev = ud; mutex_init(&xd->xd_imtx, NULL, MUTEX_DRIVER, (void *)(uintptr_t)xhcip->xhci_intr_pri); /* * The size of the context structures is based upon the presence of the * context flag which determines whether we have a 32-byte or 64-byte * context. Note that the input context always has to account for the * entire size of the xhci_input_contex_t, which is 32-bytes by default. */ if (xhcip->xhci_caps.xcap_flags & XCAP_CSZ) { incr = 64; osize = XHCI_DEVICE_CONTEXT_64; isize = XHCI_DEVICE_CONTEXT_64 + incr; } else { incr = 32; osize = XHCI_DEVICE_CONTEXT_32; isize = XHCI_DEVICE_CONTEXT_32 + incr; } xhci_dma_acc_attr(xhcip, &acc); xhci_dma_dma_attr(xhcip, &attr); if (xhci_dma_alloc(xhcip, &xd->xd_ictx, &attr, &acc, B_TRUE, isize, B_FALSE) == B_FALSE) { xhci_hcdi_device_free(xd); return (USB_NO_RESOURCES); } xd->xd_input = (xhci_input_context_t *)xd->xd_ictx.xdb_va; xd->xd_slotin = (xhci_slot_context_t *)(xd->xd_ictx.xdb_va + incr); for (i = 0; i < XHCI_NUM_ENDPOINTS; i++) { xd->xd_endin[i] = (xhci_endpoint_context_t *)(xd->xd_ictx.xdb_va + (i + 2) * incr); } if (xhci_dma_alloc(xhcip, &xd->xd_octx, &attr, &acc, B_TRUE, osize, B_FALSE) == B_FALSE) { xhci_hcdi_device_free(xd); return (USB_NO_RESOURCES); } xd->xd_slotout = (xhci_slot_context_t *)xd->xd_octx.xdb_va; for (i = 0; i < XHCI_NUM_ENDPOINTS; i++) { xd->xd_endout[i] = (xhci_endpoint_context_t *)(xd->xd_octx.xdb_va + (i + 1) * incr); } ret = xhci_command_enable_slot(xhcip, &xd->xd_slot); if (ret != USB_SUCCESS) { xhci_hcdi_device_free(xd); return (ret); } /* * These are the default slot context and the endpoint zero context that * we're enabling. See 4.3.3. */ xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0) | XHCI_INCTX_MASK_DCI(1)); /* * Note, we never need to set the MTT bit as illumos never enables the * alternate MTT interface. */ xhci_hcdi_device_route(ud, &route, &rp); info = XHCI_SCTX_SET_ROUTE(route) | XHCI_SCTX_SET_DCI(1); switch (ud->usb_port_status) { case USBA_LOW_SPEED_DEV: info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_LOW); break; case USBA_HIGH_SPEED_DEV: info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_HIGH); break; case USBA_FULL_SPEED_DEV: info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_FULL); break; case USBA_SUPER_SPEED_DEV: default: info |= XHCI_SCTX_SET_SPEED(XHCI_SPEED_SUPER); break; } info2 = XHCI_SCTX_SET_RHPORT(rp); tt = XHCI_SCTX_SET_IRQ_TARGET(0); tt |= xhci_hcdi_device_tt(ud); xd->xd_slotin->xsc_info = LE_32(info); xd->xd_slotin->xsc_info2 = LE_32(info2); xd->xd_slotin->xsc_tt = LE_32(tt); if ((ret = xhci_endpoint_init(xhcip, xd, NULL)) != 0) { (void) xhci_command_disable_slot(xhcip, xd->xd_slot); xhci_hcdi_device_free(xd); return (USB_HC_HARDWARE_ERROR); } if (xhci_context_slot_output_init(xhcip, xd) != B_TRUE) { (void) xhci_command_disable_slot(xhcip, xd->xd_slot); xhci_endpoint_fini(xd, 0); xhci_hcdi_device_free(xd); return (USB_HC_HARDWARE_ERROR); } if ((ret = xhci_command_set_address(xhcip, xd, B_TRUE)) != 0) { (void) xhci_command_disable_slot(xhcip, xd->xd_slot); xhci_context_slot_output_fini(xhcip, xd); xhci_endpoint_fini(xd, 0); xhci_hcdi_device_free(xd); return (ret); } mutex_enter(&xhcip->xhci_lock); list_insert_tail(&xhcip->xhci_usba.xa_devices, xd); mutex_exit(&xhcip->xhci_lock); *hcdpp = xd; return (ret); } /* * We're tearing down a device now. That means that the only endpoint context * that's still valid would be endpoint zero. */ static void xhci_hcdi_device_fini(usba_device_t *ud, void *hcdp) { int ret; xhci_endpoint_t *xep; xhci_device_t *xd; xhci_t *xhcip; /* * Right now, it's theoretically possible that USBA may try and call * us here even if we hadn't successfully finished the device_init() * endpoint. We should probably modify the USBA to make sure that this * can't happen. */ if (hcdp == NULL) return; xd = hcdp; xhcip = xhci_hcdi_get_xhcip_from_dev(ud); /* * Make sure we have no timeout running on the default endpoint still. */ xep = xd->xd_endpoints[XHCI_DEFAULT_ENDPOINT]; mutex_enter(&xhcip->xhci_lock); xep->xep_state |= XHCI_ENDPOINT_TEARDOWN; mutex_exit(&xhcip->xhci_lock); (void) untimeout(xep->xep_timeout); /* * Go ahead and disable the slot. There's no reason to do anything * special about the default endpoint as it will be disabled as a part * of the slot disabling. However, if this all fails, we'll leave this * sitting here in a failed state, eating up a device slot. It is * unlikely this will occur. */ ret = xhci_command_disable_slot(xhcip, xd->xd_slot); if (ret != USB_SUCCESS) { xhci_error(xhcip, "failed to disable slot %d: %d", xd->xd_slot, ret); return; } xhci_context_slot_output_fini(xhcip, xd); xhci_endpoint_fini(xd, XHCI_DEFAULT_ENDPOINT); mutex_enter(&xhcip->xhci_lock); list_remove(&xhcip->xhci_usba.xa_devices, xd); mutex_exit(&xhcip->xhci_lock); xhci_hcdi_device_free(xd); } /* * Synchronously attempt to set the device address. For xHCI this involves it * deciding what address to use. */ static int xhci_hcdi_device_address(usba_device_t *ud) { int ret; xhci_t *xhcip = xhci_hcdi_get_xhcip_from_dev(ud); xhci_device_t *xd = usba_hcdi_get_device_private(ud); xhci_endpoint_t *xep; mutex_enter(&xhcip->xhci_lock); /* * This device may already be addressed from the perspective of the xhci * controller. For example, the device this represents may have been * unconfigured, which does not actually remove the slot or other * information, merely tears down all the active use of it and the child * driver. In such cases, if we're already addressed, just return * success. The actual USB address is a fiction for USBA anyways. */ if (xd->xd_addressed == B_TRUE) { mutex_exit(&xhcip->xhci_lock); return (USB_SUCCESS); } ASSERT(xd->xd_addressed == B_FALSE); xd->xd_addressed = B_TRUE; VERIFY3P(xd->xd_endpoints[XHCI_DEFAULT_ENDPOINT], !=, NULL); xep = xd->xd_endpoints[XHCI_DEFAULT_ENDPOINT]; mutex_exit(&xhcip->xhci_lock); if ((ret = xhci_endpoint_setup_default_context(xhcip, xd, xep)) != 0) { ASSERT(ret == EIO); return (USB_HC_HARDWARE_ERROR); } ret = xhci_command_set_address(xhcip, xd, B_FALSE); if (ret != USB_SUCCESS) { mutex_enter(&xhcip->xhci_lock); xd->xd_addressed = B_FALSE; mutex_exit(&xhcip->xhci_lock); } return (ret); } /* * This is called relatively early on in a hub's life time. At this point, it's * descriptors have all been pulled and the default control pipe is still open. * What we need to do is go through and update the slot context to indicate that * this is a hub, otherwise, the controller will never let us speak to * downstream ports. */ static int xhci_hcdi_hub_update(usba_device_t *ud, uint8_t nports, uint8_t tt) { int ret; xhci_t *xhcip = xhci_hcdi_get_xhcip_from_dev(ud); xhci_device_t *xd = usba_hcdi_get_device_private(ud); if (xd == NULL) return (USB_FAILURE); if (ud->usb_hubdi == NULL) { return (USB_FAILURE); } mutex_enter(&xd->xd_imtx); /* * Note, that usba never sets the interface of a hub to Multi TT. Hence * why we're never setting the MTT bit in xsc_info. */ xd->xd_slotin->xsc_info |= LE_32(XHCI_SCTX_SET_HUB(1)); xd->xd_slotin->xsc_info2 |= LE_32(XHCI_SCTX_SET_NPORTS(nports)); if (ud->usb_port_status == USBA_HIGH_SPEED_DEV) xd->xd_slotin->xsc_tt |= LE_32(XHCI_SCTX_SET_TT_THINK_TIME(tt)); /* * We're only updating the slot context, no endpoint contexts should be * touched. */ xd->xd_input->xic_drop_flags = LE_32(0); xd->xd_input->xic_add_flags = LE_32(XHCI_INCTX_MASK_DCI(0)); ret = xhci_command_evaluate_context(xhcip, xd); mutex_exit(&xd->xd_imtx); return (ret); } void xhci_hcd_fini(xhci_t *xhcip) { usba_hcdi_unregister(xhcip->xhci_dip); usba_free_hcdi_ops(xhcip->xhci_usba.xa_ops); list_destroy(&xhcip->xhci_usba.xa_pipes); list_destroy(&xhcip->xhci_usba.xa_devices); } int xhci_hcd_init(xhci_t *xhcip) { usba_hcdi_register_args_t hreg; usba_hcdi_ops_t *ops; ops = usba_alloc_hcdi_ops(); VERIFY(ops != NULL); ops->usba_hcdi_ops_version = HCDI_OPS_VERSION; ops->usba_hcdi_dip = xhcip->xhci_dip; ops->usba_hcdi_pm_support = xhci_hcdi_pm_support; ops->usba_hcdi_pipe_open = xhci_hcdi_pipe_open; ops->usba_hcdi_pipe_close = xhci_hcdi_pipe_close; ops->usba_hcdi_pipe_reset = xhci_hcdi_pipe_reset; ops->usba_hcdi_pipe_reset_data_toggle = xhci_hcdi_pipe_reset_data_toggle; ops->usba_hcdi_pipe_ctrl_xfer = xhci_hcdi_pipe_ctrl_xfer; ops->usba_hcdi_bulk_transfer_size = xhci_hcdi_bulk_transfer_size; ops->usba_hcdi_pipe_bulk_xfer = xhci_hcdi_pipe_bulk_xfer; ops->usba_hcdi_pipe_intr_xfer = xhci_hcdi_pipe_intr_xfer; ops->usba_hcdi_pipe_stop_intr_polling = xhci_hcdi_pipe_stop_intr_polling; ops->usba_hcdi_pipe_isoc_xfer = xhci_hcdi_pipe_isoc_xfer; ops->usba_hcdi_pipe_stop_isoc_polling = xhci_hcdi_pipe_stop_isoc_polling; ops->usba_hcdi_get_current_frame_number = xhci_hcdi_get_current_frame_number; ops->usba_hcdi_get_max_isoc_pkts = xhci_hcdi_get_max_isoc_pkts; ops->usba_hcdi_console_input_init = xhci_hcdi_console_input_init; ops->usba_hcdi_console_input_fini = xhci_hcdi_console_input_fini; ops->usba_hcdi_console_input_enter = xhci_hcdi_console_input_enter; ops->usba_hcdi_console_read = xhci_hcdi_console_read; ops->usba_hcdi_console_input_exit = xhci_hcdi_console_input_exit; ops->usba_hcdi_console_output_init = xhci_hcdi_console_output_init; ops->usba_hcdi_console_output_fini = xhci_hcdi_console_output_fini; ops->usba_hcdi_console_output_enter = xhci_hcdi_console_output_enter; ops->usba_hcdi_console_write = xhci_hcdi_console_write; ops->usba_hcdi_console_output_exit = xhci_hcdi_console_output_exit; ops->usba_hcdi_device_init = xhci_hcdi_device_init; ops->usba_hcdi_device_fini = xhci_hcdi_device_fini; ops->usba_hcdi_device_address = xhci_hcdi_device_address; ops->usba_hcdi_hub_update = xhci_hcdi_hub_update; hreg.usba_hcdi_register_version = HCDI_REGISTER_VERSION; hreg.usba_hcdi_register_dip = xhcip->xhci_dip; hreg.usba_hcdi_register_ops = ops; /* * We're required to give xhci a set of DMA attributes that it may loan * out to other devices. Therefore we'll be conservative with what we * end up giving it. */ xhci_dma_dma_attr(xhcip, &xhcip->xhci_usba.xa_dma_attr); hreg.usba_hcdi_register_dma_attr = &xhcip->xhci_usba.xa_dma_attr; hreg.usba_hcdi_register_iblock_cookie = (ddi_iblock_cookie_t)(uintptr_t)xhcip->xhci_intr_pri; if (usba_hcdi_register(&hreg, 0) != DDI_SUCCESS) { usba_free_hcdi_ops(ops); return (DDI_FAILURE); } xhcip->xhci_usba.xa_ops = ops; list_create(&xhcip->xhci_usba.xa_devices, sizeof (xhci_device_t), offsetof(xhci_device_t, xd_link)); list_create(&xhcip->xhci_usba.xa_pipes, sizeof (xhci_pipe_t), offsetof(xhci_pipe_t, xp_link)); return (DDI_SUCCESS); }