blob: 9dafe2143471ae1e5b9af49f8138f94e7b18e6e3 [file] [log] [blame]
/*
* 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.
*/
/*
* illumos USB framework endpoints and functions for xHCI.
*
* Please see the big theory statement in xhci.c for more information.
*/
#include <sys/usb/hcd/xhci/xhci.h>
#include <sys/sysmacros.h>
#include <sys/strsun.h>
#include <sys/strsubr.h>
static 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);
}
/*
* The next series of routines is used for both the OBP console and general USB
* console polled I/O. In general, we opt not to support any of that at this
* time in xHCI. As we have the need of that, we can start plumbing that
* through.
*/
/* ARGSUSED */
static int
xhci_hcdi_console_input_init(usba_pipe_handle_data_t *pipe_handle,
uchar_t **obp_buf, usb_console_info_impl_t *console_input_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_input_fini(usb_console_info_impl_t *console_input_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_input_enter(usb_console_info_impl_t *console_input_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_read(usb_console_info_impl_t *console_input_info,
uint_t *num_characters)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_input_exit(usb_console_info_impl_t *console_input_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_output_init(usba_pipe_handle_data_t *pipe_handle,
usb_console_info_impl_t *console_output_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_output_fini(usb_console_info_impl_t *console_output_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_output_enter(usb_console_info_impl_t *console_output_info)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_write(usb_console_info_impl_t *console_output_info,
uchar_t *buf, uint_t num_characters, uint_t *num_characters_written)
{
return (USB_NOT_SUPPORTED);
}
/* ARGSUSED */
static int
xhci_hcdi_console_output_exit(usb_console_info_impl_t *console_output_info)
{
return (USB_NOT_SUPPORTED);
}
/*
* 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);
}