diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c index 9cf78e8f4e41..8a83068a986e 100644 --- a/drivers/usb/host/xhci.c +++ b/drivers/usb/host/xhci.c @@ -5383,6 +5383,63 @@ phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_device *udev, } EXPORT_SYMBOL(xhci_get_xfer_ring_phys_addr); +int xhci_stop_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + unsigned int ep_index; + struct xhci_virt_device *virt_dev; + struct xhci_command *cmd; + unsigned long flags; + int ret = 0; + + if (udev->state == USB_STATE_NOTATTACHED || !HCD_RH_RUNNING(hcd)) + return 0; + + cmd = xhci_alloc_command(xhci, true, GFP_NOIO); + if (!cmd) + return -ENOMEM; + + spin_lock_irqsave(&xhci->lock, flags); + virt_dev = xhci->devs[udev->slot_id]; + if (!virt_dev) { + ret = -ENODEV; + goto err; + } + + ep_index = xhci_get_endpoint_index(&ep->desc); + if (virt_dev->eps[ep_index].ring && + virt_dev->eps[ep_index].ring->dequeue) { + ret = xhci_queue_stop_endpoint(xhci, cmd, udev->slot_id, + ep_index, 0); + if (ret) + goto err; + + xhci_ring_cmd_db(xhci); + spin_unlock_irqrestore(&xhci->lock, flags); + + /* Wait for stop endpoint command to finish */ + wait_for_completion(cmd->completion); + + if (cmd->status == COMP_COMMAND_ABORTED || + cmd->status == COMP_STOPPED) { + xhci_warn(xhci, + "stop endpoint command timeout for ep%d%s\n", + usb_endpoint_num(&ep->desc), + usb_endpoint_dir_in(&ep->desc) ? "in" : "out"); + ret = -ETIME; + } + goto free_cmd; + } + +err: + spin_unlock_irqrestore(&xhci->lock, flags); +free_cmd: + xhci_free_command(xhci, cmd); + return ret; +} +EXPORT_SYMBOL(xhci_stop_endpoint); + static const struct hc_driver xhci_hc_driver = { .description = "xhci-hcd", .product_desc = "xHCI Host Controller", diff --git a/include/linux/usb/xhci-sec.h b/include/linux/usb/xhci-sec.h index 878d45d372c9..5f4c3b1962db 100644 --- a/include/linux/usb/xhci-sec.h +++ b/include/linux/usb/xhci-sec.h @@ -17,6 +17,7 @@ phys_addr_t xhci_get_sec_event_ring_phys_addr(struct usb_device *udev, unsigned int intr_num, dma_addr_t *dma); phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_device *udev, struct usb_host_endpoint *ep, dma_addr_t *dma); +int xhci_stop_endpoint(struct usb_device *udev, struct usb_host_endpoint *ep); #else static inline int xhci_sec_event_ring_setup(struct usb_device *udev, unsigned int intr_num); @@ -42,6 +43,12 @@ static inline phys_addr_t xhci_get_xfer_ring_phys_addr(struct usb_device *udev, { return NULL; } + +static inline int xhci_stop_endpoint(struct usb_device *udev, + struct usb_host_endpoint *ep) +{ + return -ENODEV; +} #endif #endif /* __LINUX_XHCI_SEC_H */