From 574aabfd5a408bc10da01bb4abe222abc013ea45 Mon Sep 17 00:00:00 2001 From: Jonathan Bell Date: Tue, 11 Jun 2019 11:33:39 +0100 Subject: [PATCH] xhci: implement xhci_fixup_endpoint for interval adjustments Must be called in a non-atomic context, after the endpoint has been registered with the hardware via xhci_add_endpoint and before the first URB is submitted for the endpoint. Signed-off-by: Jonathan Bell --- drivers/usb/host/xhci.c | 98 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -1492,6 +1492,103 @@ command_cleanup: } /* + * RPI: Fixup endpoint intervals when requested + * - Check interval versus the (cached) endpoint context + * - set the endpoint interval to the new value + * - force an endpoint configure command + * XXX: bandwidth is not recalculated. We should probably do that. + */ +static void xhci_fixup_endpoint(struct usb_hcd *hcd, struct usb_device *udev, + struct usb_host_endpoint *ep, int interval) +{ + struct xhci_hcd *xhci; + struct xhci_ep_ctx *ep_ctx_out, *ep_ctx_in; + struct xhci_command *command; + struct xhci_input_control_ctx *ctrl_ctx; + struct xhci_virt_device *vdev; + int xhci_interval; + int ret; + int ep_index; + unsigned long flags; + u32 ep_info_tmp; + + xhci = hcd_to_xhci(hcd); + ep_index = xhci_get_endpoint_index(&ep->desc); + + /* FS/LS interval translations */ + if ((udev->speed == USB_SPEED_FULL || + udev->speed == USB_SPEED_LOW)) + interval *= 8; + + mutex_lock(&xhci->mutex); + + spin_lock_irqsave(&xhci->lock, flags); + + vdev = xhci->devs[udev->slot_id]; + /* Get context-derived endpoint interval */ + ep_ctx_out = xhci_get_ep_ctx(xhci, vdev->out_ctx, ep_index); + ep_ctx_in = xhci_get_ep_ctx(xhci, vdev->in_ctx, ep_index); + xhci_interval = EP_INTERVAL_TO_UFRAMES(le32_to_cpu(ep_ctx_out->ep_info)); + + if (interval == xhci_interval) { + spin_unlock_irqrestore(&xhci->lock, flags); + mutex_unlock(&xhci->mutex); + return; + } + + xhci_dbg(xhci, "Fixup interval=%d xhci_interval=%d\n", + interval, xhci_interval); + command = xhci_alloc_command_with_ctx(xhci, true, GFP_ATOMIC); + if (!command) { + /* Failure here is benign, poll at the original rate */ + spin_unlock_irqrestore(&xhci->lock, flags); + mutex_unlock(&xhci->mutex); + return; + } + + /* xHCI uses exponents for intervals... */ + xhci_interval = fls(interval) - 1; + xhci_interval = clamp_val(xhci_interval, 3, 10); + ep_info_tmp = le32_to_cpu(ep_ctx_out->ep_info); + ep_info_tmp &= ~EP_INTERVAL(255); + ep_info_tmp |= EP_INTERVAL(xhci_interval); + + /* Keep the endpoint context up-to-date while issuing the command. */ + xhci_endpoint_copy(xhci, vdev->in_ctx, + vdev->out_ctx, ep_index); + ep_ctx_in->ep_info = cpu_to_le32(ep_info_tmp); + + /* + * We need to drop the lock, so take an explicit copy + * of the ep context. + */ + xhci_endpoint_copy(xhci, command->in_ctx, vdev->in_ctx, ep_index); + + ctrl_ctx = xhci_get_input_control_ctx(command->in_ctx); + if (!ctrl_ctx) { + xhci_warn(xhci, + "%s: Could not get input context, bad type.\n", + __func__); + spin_unlock_irqrestore(&xhci->lock, flags); + xhci_free_command(xhci, command); + mutex_unlock(&xhci->mutex); + return; + } + ctrl_ctx->add_flags = xhci_get_endpoint_flag_from_index(ep_index); + ctrl_ctx->drop_flags = 0; + + spin_unlock_irqrestore(&xhci->lock, flags); + + ret = xhci_configure_endpoint(xhci, udev, command, + false, false); + if (ret) + xhci_warn(xhci, "%s: Configure endpoint failed: %d\n", + __func__, ret); + xhci_free_command(xhci, command); + mutex_unlock(&xhci->mutex); +} + +/* * non-error returns are a promise to giveback() the urb later * we drop ownership so next owner (or urb unlink) can get it */ @@ -5412,6 +5509,7 @@ static const struct hc_driver xhci_hc_dr .endpoint_reset = xhci_endpoint_reset, .check_bandwidth = xhci_check_bandwidth, .reset_bandwidth = xhci_reset_bandwidth, + .fixup_endpoint = xhci_fixup_endpoint, .address_device = xhci_address_device, .enable_device = xhci_enable_device, .update_hub_device = xhci_update_hub_device,