Message ID | 20231017200109.11407-5-quic_wcheng@quicinc.com |
---|---|
State | Superseded |
Headers | show |
Series | Introduce QC USB SND audio offloading support | expand |
>+ * Returns the address of the endpoint buffer where xHC controller reads queued >+ * transfer TRBs from. This is the starting address of the ringbuffer where the >+ * sidband cliend should write TRBs to. A typo here 'sideband cliend', it apparently should be 'sideband client'. On Wed, Oct 18, 2023 at 4:03 AM Wesley Cheng <quic_wcheng@quicinc.com> wrote: > > From: Mathias Nyman <mathias.nyman@linux.intel.com> > > Introduce XHCI sideband, which manages the USB endpoints being requested by > a client driver. This is used for when client drivers are attempting to > offload USB endpoints to another entity for handling USB transfers. XHCI > sideband will allow for drivers to fetch the required information about the > transfer ring, so the user can submit transfers independently. Expose the > required APIs for drivers to register and request for a USB endpoint and to > manage XHCI secondary interrupters. > > Multiple ring segment page linking and proper endpoint clean up added by > Wesley Cheng to complete original concept code by Mathias Nyman. > > Signed-off-by: Mathias Nyman <mathias.nyman@linux.intel.com> > Co-developed-by: Wesley Cheng <quic_wcheng@quicinc.com> > Signed-off-by: Wesley Cheng <quic_wcheng@quicinc.com> > --- > drivers/usb/host/Kconfig | 9 + > drivers/usb/host/Makefile | 4 + > drivers/usb/host/xhci-sideband.c | 371 ++++++++++++++++++++++++++++++ > drivers/usb/host/xhci.h | 4 + > include/linux/usb/xhci-sideband.h | 66 ++++++ > 5 files changed, 454 insertions(+) > create mode 100644 drivers/usb/host/xhci-sideband.c > create mode 100644 include/linux/usb/xhci-sideband.h > > diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig > index 4448d0ab06f0..923af11c1982 100644 > --- a/drivers/usb/host/Kconfig > +++ b/drivers/usb/host/Kconfig > @@ -104,6 +104,15 @@ config USB_XHCI_RZV2M > Say 'Y' to enable the support for the xHCI host controller > found in Renesas RZ/V2M SoC. > > +config USB_XHCI_SIDEBAND > + bool "xHCI support for sideband" > + help > + Say 'Y' to enable the support for the xHCI sideband capability. > + provide a mechanism for a sideband datapath for payload associated > + with audio class endpoints. This allows for an audio DSP to use > + xHCI USB endpoints directly, allowing CPU to sleep while playing > + audio > + > config USB_XHCI_TEGRA > tristate "xHCI support for NVIDIA Tegra SoCs" > depends on PHY_TEGRA_XUSB > diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile > index be4e5245c52f..4df946c05ba0 100644 > --- a/drivers/usb/host/Makefile > +++ b/drivers/usb/host/Makefile > @@ -32,6 +32,10 @@ endif > xhci-rcar-hcd-y += xhci-rcar.o > xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o > > +ifneq ($(CONFIG_USB_XHCI_SIDEBAND),) > + xhci-hcd-y += xhci-sideband.o > +endif > + > obj-$(CONFIG_USB_PCI) += pci-quirks.o > > obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o > diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c > new file mode 100644 > index 000000000000..cc4f90375e00 > --- /dev/null > +++ b/drivers/usb/host/xhci-sideband.c > @@ -0,0 +1,371 @@ > +// SPDX-License-Identifier: GPL-2.0 > + > +/* > + * xHCI host controller sideband support > + * > + * Copyright (c) 2023, Intel Corporation. > + * > + * Author: Mathias Nyman > + */ > + > +#include <linux/usb/xhci-sideband.h> > +#include <linux/dma-direct.h> > + > +#include "xhci.h" > + > +/* sideband internal helpers */ > +static struct sg_table * > +xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring) > +{ > + struct xhci_segment *seg; > + struct sg_table *sgt; > + unsigned int n_pages; > + struct page **pages; > + struct device *dev; > + size_t sz; > + int i; > + > + dev = xhci_to_hcd(sb->xhci)->self.sysdev; > + sz = ring->num_segs * TRB_SEGMENT_SIZE; > + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; > + pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL); > + if (!pages) > + return NULL; > + > + sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL); > + if (!sgt) { > + kvfree(pages); > + return NULL; > + } > + > + seg = ring->first_seg; > + /* > + * Rings can potentially have multiple segments, create an array that > + * carries page references to allocated segments. Utilize the > + * sg_alloc_table_from_pages() to create the sg table, and to ensure > + * that page links are created. > + */ > + for (i = 0; i < ring->num_segs; i++) { > + dma_get_sgtable(dev, sgt, seg->trbs, seg->dma, > + TRB_SEGMENT_SIZE); > + pages[i] = sg_page(sgt->sgl); > + sg_free_table(sgt); > + seg = seg->next; > + } > + > + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) { > + kvfree(pages); > + kfree(sgt); > + > + return NULL; > + } > + /* > + * Save first segment dma address to sg dma_address field for the sideband > + * client to have access to the IOVA of the ring. > + */ > + sg_dma_address(sgt->sgl) = ring->first_seg->dma; > + > + return sgt; > +} > + > +static void > +__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep) > +{ > + /* > + * Issue a stop endpoint command when an endpoint is removed. > + * The stop ep cmd handler will handle the ring cleanup. > + */ > + xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); > + > + ep->sideband = NULL; > + sb->eps[ep->ep_index] = NULL; > +} > + > +/* sideband api functions */ > + > +/** > + * xhci_sideband_add_endpoint - add endpoint to sideband access list > + * @sb: sideband instance for this usb device > + * @host_ep: usb host endpoint > + * > + * Adds an endpoint to the list of sideband accessed endpoints for this usb > + * device. > + * After an endpoint is added the sideband client can get the endpoint transfer > + * ring buffer by calling xhci_sideband_endpoint_buffer() > + * > + * Return: 0 on success, negative error otherwise. > + */ > +int > +xhci_sideband_add_endpoint(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep) > +{ > + struct xhci_virt_ep *ep; > + unsigned int ep_index; > + > + ep_index = xhci_get_endpoint_index(&host_ep->desc); > + ep = &sb->vdev->eps[ep_index]; > + > + if (ep->ep_state & EP_HAS_STREAMS) > + return -EINVAL; > + > + /* > + * Note, we don't know the DMA mask of the audio DSP device, if its > + * smaller than for xhci it won't be able to access the endpoint ring > + * buffer. This could be solved by not allowing the audio class driver > + * to add the endpoint the normal way, but instead offload it immediately, > + * and let this function add the endpoint and allocate the ring buffer > + * with the smallest common DMA mask > + */ > + > + if (sb->eps[ep_index] || ep->sideband) > + return -EBUSY; > + > + ep->sideband = sb; > + sb->eps[ep_index] = ep; > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint); > + > +/** > + * xhci_sideband_remove_endpoint - remove endpoint from sideband access list > + * @sb: sideband instance for this usb device > + * @host_ep: usb host endpoint > + * > + * Removes an endpoint from the list of sideband accessed endpoints for this usb > + * device. > + * sideband client should no longer touch the endpoint transfer buffer after > + * calling this. > + * > + * Return: 0 on success, negative error otherwise. > + */ > +int > +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep) > +{ > + struct xhci_virt_ep *ep; > + unsigned int ep_index; > + > + ep_index = xhci_get_endpoint_index(&host_ep->desc); > + ep = sb->eps[ep_index]; > + > + if (!ep || !ep->sideband) > + return -ENODEV; > + > + __xhci_sideband_remove_endpoint(sb, ep); > + xhci_initialize_ring_info(ep->ring, 1); > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint); > + > +int > +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep) > +{ > + struct xhci_virt_ep *ep; > + unsigned int ep_index; > + > + ep_index = xhci_get_endpoint_index(&host_ep->desc); > + ep = sb->eps[ep_index]; > + > + if (!ep || ep->sideband != sb) > + return -EINVAL; > + > + return xhci_stop_endpoint_sync(sb->xhci, ep, 0); > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint); > + > +/** > + * xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address > + * @sb: sideband instance for this usb device > + * @host_ep: usb host endpoint > + * > + * Returns the address of the endpoint buffer where xHC controller reads queued > + * transfer TRBs from. This is the starting address of the ringbuffer where the > + * sidband cliend should write TRBs to. > + * > + * Caller needs to free the returned sg_table > + * > + * Return: struct sg_table * if successful. NULL otherwise. > + */ > +struct sg_table * > +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep) > +{ > + struct xhci_virt_ep *ep; > + unsigned int ep_index; > + > + ep_index = xhci_get_endpoint_index(&host_ep->desc); > + ep = sb->eps[ep_index]; > + > + if (!ep) > + return NULL; > + > + return xhci_ring_to_sgtable(sb, ep->ring); > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer); > + > +/** > + * xhci_sideband_get_event_buffer - return the event buffer for this device > + * @sb: sideband instance for this usb device > + * > + * If a secondary xhci interupter is set up for this usb device then this > + * function returns the address of the event buffer where xHC writes > + * the transfer completion events. > + * > + * Caller needs to free the returned sg_table > + * > + * Return: struct sg_table * if successful. NULL otherwise. > + */ > +struct sg_table * > +xhci_sideband_get_event_buffer(struct xhci_sideband *sb) > +{ > + if (!sb->ir) > + return NULL; > + > + return xhci_ring_to_sgtable(sb, sb->ir->event_ring); > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); > + > +/** > + * xhci_sideband_create_interrupter - creates a new interrupter for this sideband > + * @sb: sideband instance for this usb device > + * > + * Sets up a xhci interrupter that can be used for this sideband accessed usb > + * device. Transfer events for this device can be routed to this interrupters > + * event ring by setting the 'Interrupter Target' field correctly when queueing > + * the transfer TRBs. > + * Once this interrupter is created the interrupter target ID can be obtained > + * by calling xhci_sideband_interrupter_id() > + * > + * Returns 0 on success, negative error otherwise > + */ > +int > +xhci_sideband_create_interrupter(struct xhci_sideband *sb) > +{ > + if (sb->ir) > + return -EBUSY; > + > + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci)); > + if (!sb->ir) > + return -ENOMEM; > + > + return 0; > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); > + > +/** > + * xhci_sideband_remove_interrupter - remove the interrupter from a sideband > + * @sb: sideband instance for this usb device > + * > + * Removes a registered interrupt for a sideband. This would allow for other > + * sideband users to utilize this interrupter. > + */ > +void > +xhci_sideband_remove_interrupter(struct xhci_sideband *sb) > +{ > + if (!sb || !sb->ir) > + return; > + > + xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); > + > + sb->ir = NULL; > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); > + > +/** > + * xhci_sideband_interrupter_id - return the interrupter target id > + * @sb: sideband instance for this usb device > + * > + * If a secondary xhci interrupter is set up for this usb device then this > + * function returns the ID used by the interrupter. The sideband client > + * needs to write this ID to the 'Interrupter Target' field of the transfer TRBs > + * it queues on the endpoints transfer ring to ensure transfer completion event > + * are written by xHC to the correct interrupter event ring. > + * > + * Returns interrupter id on success, negative error othgerwise > + */ > +int > +xhci_sideband_interrupter_id(struct xhci_sideband *sb) > +{ > + if (!sb || !sb->ir) > + return -ENODEV; > + > + return sb->ir->intr_num; > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); > + > +/** > + * xhci_sideband_register - register a sideband for a usb device > + * @udev: usb device to be accessed via sideband > + * > + * Allows for clients to utilize XHCI interrupters and fetch transfer and event > + * ring parameters for executing data transfers. > + * > + * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. > + */ > +struct xhci_sideband * > +xhci_sideband_register(struct usb_device *udev) > +{ > + struct usb_hcd *hcd = bus_to_hcd(udev->bus); > + struct xhci_hcd *xhci = hcd_to_xhci(hcd); > + struct xhci_virt_device *vdev; > + struct xhci_sideband *sb; > + > + /* make sure the usb device is connected to a xhci controller */ > + if (!udev->slot_id) > + return NULL; > + > + sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev)); > + if (!sb) > + return NULL; > + > + /* check this device isn't already controlled via sideband */ > + spin_lock_irq(&xhci->lock); > + > + vdev = xhci->devs[udev->slot_id]; > + > + if (!vdev || vdev->sideband) { > + xhci_warn(xhci, "XHCI sideband for slot %d already in use\n", > + udev->slot_id); > + spin_unlock_irq(&xhci->lock); > + kfree(sb); > + return NULL; > + } > + > + sb->xhci = xhci; > + sb->vdev = vdev; > + vdev->sideband = sb; > + > + spin_unlock_irq(&xhci->lock); > + > + return sb; > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_register); > + > +/** > + * xhci_sideband_unregister - unregister sideband access to a usb device > + * @sb: sideband instance to be unregistered > + * > + * Unregisters sideband access to a usb device and frees the sideband > + * instance. > + * After this the endpoint and interrupter event buffers should no longer > + * be accessed via sideband. The xhci driver can now take over handling > + * the buffers. > + */ > +void > +xhci_sideband_unregister(struct xhci_sideband *sb) > +{ > + int i; > + > + for (i = 0; i < EP_CTX_PER_DEV; i++) > + if (sb->eps[i]) > + __xhci_sideband_remove_endpoint(sb, sb->eps[i]); > + > + xhci_sideband_remove_interrupter(sb); > + > + sb->vdev->sideband = NULL; > + kfree(sb); > +} > +EXPORT_SYMBOL_GPL(xhci_sideband_unregister); > diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h > index 4b8caaed6f95..339d37c3a3d9 100644 > --- a/drivers/usb/host/xhci.h > +++ b/drivers/usb/host/xhci.h > @@ -947,6 +947,8 @@ struct xhci_virt_ep { > int next_frame_id; > /* Use new Isoch TRB layout needed for extended TBC support */ > bool use_extended_tbc; > + /* set if this endpoint is controlled via sideband access*/ > + struct xhci_sideband *sideband; > }; > > enum xhci_overhead_type { > @@ -1010,6 +1012,8 @@ struct xhci_virt_device { > u16 current_mel; > /* Used for the debugfs interfaces. */ > void *debugfs_private; > + /* set if this device is registered for sideband access */ > + struct xhci_sideband *sideband; > }; > > /* > diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h > new file mode 100644 > index 000000000000..c1457d1800f4 > --- /dev/null > +++ b/include/linux/usb/xhci-sideband.h > @@ -0,0 +1,66 @@ > +/* SPDX-License-Identifier: GPL-2.0 */ > +/* > + * xHCI host controller sideband support > + * > + * Copyright (c) 2023, Intel Corporation. > + * > + * Author: Mathias Nyman <mathias.nyman@linux.intel.com> > + */ > + > +#ifndef __LINUX_XHCI_SIDEBAND_H > +#define __LINUX_XHCI_SIDEBAND_H > + > +#include <linux/scatterlist.h> > +#include <linux/usb.h> > + > +#define EP_CTX_PER_DEV 31 /* FIMXME defined twice, from xhci.h */ > + > +struct xhci_sideband; > + > +/** > + * struct xhci_sideband - representation of a sideband accessed usb device. > + * @xhci: The xhci host controller the usb device is connected to > + * @vdev: the usb device accessed via sideband > + * @eps: array of endpoints controlled via sideband > + * @ir: event handling and buffer for sideband accessed device > + * > + * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. > + */ > + > +struct xhci_sideband { > + struct xhci_hcd *xhci; > + struct xhci_virt_device *vdev; > + struct xhci_virt_ep *eps[EP_CTX_PER_DEV]; > + struct xhci_interrupter *ir; > +}; > + > +struct xhci_sideband * > +xhci_sideband_register(struct usb_device *udev); > +void > +xhci_sideband_unregister(struct xhci_sideband *sb); > +int > +xhci_sideband_add_endpoint(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep); > +int > +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep); > +int > +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep); > +struct sg_table * > +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, > + struct usb_host_endpoint *host_ep); > +struct sg_table * > +xhci_sideband_get_event_buffer(struct xhci_sideband *sb); > + > +int > +xhci_sideband_create_interrupter(struct xhci_sideband *sb); > + > +void > +xhci_sideband_remove_interrupter(struct xhci_sideband *sb); > + > +int > +xhci_sideband_interrupter_id(struct xhci_sideband *sb); > + > +#endif /* __LINUX_XHCI_SIDEBAND_H */ > + >
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 4448d0ab06f0..923af11c1982 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -104,6 +104,15 @@ config USB_XHCI_RZV2M Say 'Y' to enable the support for the xHCI host controller found in Renesas RZ/V2M SoC. +config USB_XHCI_SIDEBAND + bool "xHCI support for sideband" + help + Say 'Y' to enable the support for the xHCI sideband capability. + provide a mechanism for a sideband datapath for payload associated + with audio class endpoints. This allows for an audio DSP to use + xHCI USB endpoints directly, allowing CPU to sleep while playing + audio + config USB_XHCI_TEGRA tristate "xHCI support for NVIDIA Tegra SoCs" depends on PHY_TEGRA_XUSB diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index be4e5245c52f..4df946c05ba0 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -32,6 +32,10 @@ endif xhci-rcar-hcd-y += xhci-rcar.o xhci-rcar-hcd-$(CONFIG_USB_XHCI_RZV2M) += xhci-rzv2m.o +ifneq ($(CONFIG_USB_XHCI_SIDEBAND),) + xhci-hcd-y += xhci-sideband.o +endif + obj-$(CONFIG_USB_PCI) += pci-quirks.o obj-$(CONFIG_USB_EHCI_HCD) += ehci-hcd.o diff --git a/drivers/usb/host/xhci-sideband.c b/drivers/usb/host/xhci-sideband.c new file mode 100644 index 000000000000..cc4f90375e00 --- /dev/null +++ b/drivers/usb/host/xhci-sideband.c @@ -0,0 +1,371 @@ +// SPDX-License-Identifier: GPL-2.0 + +/* + * xHCI host controller sideband support + * + * Copyright (c) 2023, Intel Corporation. + * + * Author: Mathias Nyman + */ + +#include <linux/usb/xhci-sideband.h> +#include <linux/dma-direct.h> + +#include "xhci.h" + +/* sideband internal helpers */ +static struct sg_table * +xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring) +{ + struct xhci_segment *seg; + struct sg_table *sgt; + unsigned int n_pages; + struct page **pages; + struct device *dev; + size_t sz; + int i; + + dev = xhci_to_hcd(sb->xhci)->self.sysdev; + sz = ring->num_segs * TRB_SEGMENT_SIZE; + n_pages = PAGE_ALIGN(sz) >> PAGE_SHIFT; + pages = kvmalloc_array(n_pages, sizeof(struct page *), GFP_KERNEL); + if (!pages) + return NULL; + + sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL); + if (!sgt) { + kvfree(pages); + return NULL; + } + + seg = ring->first_seg; + /* + * Rings can potentially have multiple segments, create an array that + * carries page references to allocated segments. Utilize the + * sg_alloc_table_from_pages() to create the sg table, and to ensure + * that page links are created. + */ + for (i = 0; i < ring->num_segs; i++) { + dma_get_sgtable(dev, sgt, seg->trbs, seg->dma, + TRB_SEGMENT_SIZE); + pages[i] = sg_page(sgt->sgl); + sg_free_table(sgt); + seg = seg->next; + } + + if (sg_alloc_table_from_pages(sgt, pages, n_pages, 0, sz, GFP_KERNEL)) { + kvfree(pages); + kfree(sgt); + + return NULL; + } + /* + * Save first segment dma address to sg dma_address field for the sideband + * client to have access to the IOVA of the ring. + */ + sg_dma_address(sgt->sgl) = ring->first_seg->dma; + + return sgt; +} + +static void +__xhci_sideband_remove_endpoint(struct xhci_sideband *sb, struct xhci_virt_ep *ep) +{ + /* + * Issue a stop endpoint command when an endpoint is removed. + * The stop ep cmd handler will handle the ring cleanup. + */ + xhci_stop_endpoint_sync(sb->xhci, ep, 0, GFP_KERNEL); + + ep->sideband = NULL; + sb->eps[ep->ep_index] = NULL; +} + +/* sideband api functions */ + +/** + * xhci_sideband_add_endpoint - add endpoint to sideband access list + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Adds an endpoint to the list of sideband accessed endpoints for this usb + * device. + * After an endpoint is added the sideband client can get the endpoint transfer + * ring buffer by calling xhci_sideband_endpoint_buffer() + * + * Return: 0 on success, negative error otherwise. + */ +int +xhci_sideband_add_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = &sb->vdev->eps[ep_index]; + + if (ep->ep_state & EP_HAS_STREAMS) + return -EINVAL; + + /* + * Note, we don't know the DMA mask of the audio DSP device, if its + * smaller than for xhci it won't be able to access the endpoint ring + * buffer. This could be solved by not allowing the audio class driver + * to add the endpoint the normal way, but instead offload it immediately, + * and let this function add the endpoint and allocate the ring buffer + * with the smallest common DMA mask + */ + + if (sb->eps[ep_index] || ep->sideband) + return -EBUSY; + + ep->sideband = sb; + sb->eps[ep_index] = ep; + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint); + +/** + * xhci_sideband_remove_endpoint - remove endpoint from sideband access list + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Removes an endpoint from the list of sideband accessed endpoints for this usb + * device. + * sideband client should no longer touch the endpoint transfer buffer after + * calling this. + * + * Return: 0 on success, negative error otherwise. + */ +int +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || !ep->sideband) + return -ENODEV; + + __xhci_sideband_remove_endpoint(sb, ep); + xhci_initialize_ring_info(ep->ring, 1); + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_remove_endpoint); + +int +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep || ep->sideband != sb) + return -EINVAL; + + return xhci_stop_endpoint_sync(sb->xhci, ep, 0); +} +EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint); + +/** + * xhci_sideband_get_endpoint_buffer - gets the endpoint transfer buffer address + * @sb: sideband instance for this usb device + * @host_ep: usb host endpoint + * + * Returns the address of the endpoint buffer where xHC controller reads queued + * transfer TRBs from. This is the starting address of the ringbuffer where the + * sidband cliend should write TRBs to. + * + * Caller needs to free the returned sg_table + * + * Return: struct sg_table * if successful. NULL otherwise. + */ +struct sg_table * +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep) +{ + struct xhci_virt_ep *ep; + unsigned int ep_index; + + ep_index = xhci_get_endpoint_index(&host_ep->desc); + ep = sb->eps[ep_index]; + + if (!ep) + return NULL; + + return xhci_ring_to_sgtable(sb, ep->ring); +} +EXPORT_SYMBOL_GPL(xhci_sideband_get_endpoint_buffer); + +/** + * xhci_sideband_get_event_buffer - return the event buffer for this device + * @sb: sideband instance for this usb device + * + * If a secondary xhci interupter is set up for this usb device then this + * function returns the address of the event buffer where xHC writes + * the transfer completion events. + * + * Caller needs to free the returned sg_table + * + * Return: struct sg_table * if successful. NULL otherwise. + */ +struct sg_table * +xhci_sideband_get_event_buffer(struct xhci_sideband *sb) +{ + if (!sb->ir) + return NULL; + + return xhci_ring_to_sgtable(sb, sb->ir->event_ring); +} +EXPORT_SYMBOL_GPL(xhci_sideband_get_event_buffer); + +/** + * xhci_sideband_create_interrupter - creates a new interrupter for this sideband + * @sb: sideband instance for this usb device + * + * Sets up a xhci interrupter that can be used for this sideband accessed usb + * device. Transfer events for this device can be routed to this interrupters + * event ring by setting the 'Interrupter Target' field correctly when queueing + * the transfer TRBs. + * Once this interrupter is created the interrupter target ID can be obtained + * by calling xhci_sideband_interrupter_id() + * + * Returns 0 on success, negative error otherwise + */ +int +xhci_sideband_create_interrupter(struct xhci_sideband *sb) +{ + if (sb->ir) + return -EBUSY; + + sb->ir = xhci_create_secondary_interrupter(xhci_to_hcd(sb->xhci)); + if (!sb->ir) + return -ENOMEM; + + return 0; +} +EXPORT_SYMBOL_GPL(xhci_sideband_create_interrupter); + +/** + * xhci_sideband_remove_interrupter - remove the interrupter from a sideband + * @sb: sideband instance for this usb device + * + * Removes a registered interrupt for a sideband. This would allow for other + * sideband users to utilize this interrupter. + */ +void +xhci_sideband_remove_interrupter(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return; + + xhci_remove_secondary_interrupter(xhci_to_hcd(sb->xhci), sb->ir); + + sb->ir = NULL; +} +EXPORT_SYMBOL_GPL(xhci_sideband_remove_interrupter); + +/** + * xhci_sideband_interrupter_id - return the interrupter target id + * @sb: sideband instance for this usb device + * + * If a secondary xhci interrupter is set up for this usb device then this + * function returns the ID used by the interrupter. The sideband client + * needs to write this ID to the 'Interrupter Target' field of the transfer TRBs + * it queues on the endpoints transfer ring to ensure transfer completion event + * are written by xHC to the correct interrupter event ring. + * + * Returns interrupter id on success, negative error othgerwise + */ +int +xhci_sideband_interrupter_id(struct xhci_sideband *sb) +{ + if (!sb || !sb->ir) + return -ENODEV; + + return sb->ir->intr_num; +} +EXPORT_SYMBOL_GPL(xhci_sideband_interrupter_id); + +/** + * xhci_sideband_register - register a sideband for a usb device + * @udev: usb device to be accessed via sideband + * + * Allows for clients to utilize XHCI interrupters and fetch transfer and event + * ring parameters for executing data transfers. + * + * Return: pointer to a new xhci_sideband instance if successful. NULL otherwise. + */ +struct xhci_sideband * +xhci_sideband_register(struct usb_device *udev) +{ + struct usb_hcd *hcd = bus_to_hcd(udev->bus); + struct xhci_hcd *xhci = hcd_to_xhci(hcd); + struct xhci_virt_device *vdev; + struct xhci_sideband *sb; + + /* make sure the usb device is connected to a xhci controller */ + if (!udev->slot_id) + return NULL; + + sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev)); + if (!sb) + return NULL; + + /* check this device isn't already controlled via sideband */ + spin_lock_irq(&xhci->lock); + + vdev = xhci->devs[udev->slot_id]; + + if (!vdev || vdev->sideband) { + xhci_warn(xhci, "XHCI sideband for slot %d already in use\n", + udev->slot_id); + spin_unlock_irq(&xhci->lock); + kfree(sb); + return NULL; + } + + sb->xhci = xhci; + sb->vdev = vdev; + vdev->sideband = sb; + + spin_unlock_irq(&xhci->lock); + + return sb; +} +EXPORT_SYMBOL_GPL(xhci_sideband_register); + +/** + * xhci_sideband_unregister - unregister sideband access to a usb device + * @sb: sideband instance to be unregistered + * + * Unregisters sideband access to a usb device and frees the sideband + * instance. + * After this the endpoint and interrupter event buffers should no longer + * be accessed via sideband. The xhci driver can now take over handling + * the buffers. + */ +void +xhci_sideband_unregister(struct xhci_sideband *sb) +{ + int i; + + for (i = 0; i < EP_CTX_PER_DEV; i++) + if (sb->eps[i]) + __xhci_sideband_remove_endpoint(sb, sb->eps[i]); + + xhci_sideband_remove_interrupter(sb); + + sb->vdev->sideband = NULL; + kfree(sb); +} +EXPORT_SYMBOL_GPL(xhci_sideband_unregister); diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h index 4b8caaed6f95..339d37c3a3d9 100644 --- a/drivers/usb/host/xhci.h +++ b/drivers/usb/host/xhci.h @@ -947,6 +947,8 @@ struct xhci_virt_ep { int next_frame_id; /* Use new Isoch TRB layout needed for extended TBC support */ bool use_extended_tbc; + /* set if this endpoint is controlled via sideband access*/ + struct xhci_sideband *sideband; }; enum xhci_overhead_type { @@ -1010,6 +1012,8 @@ struct xhci_virt_device { u16 current_mel; /* Used for the debugfs interfaces. */ void *debugfs_private; + /* set if this device is registered for sideband access */ + struct xhci_sideband *sideband; }; /* diff --git a/include/linux/usb/xhci-sideband.h b/include/linux/usb/xhci-sideband.h new file mode 100644 index 000000000000..c1457d1800f4 --- /dev/null +++ b/include/linux/usb/xhci-sideband.h @@ -0,0 +1,66 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * xHCI host controller sideband support + * + * Copyright (c) 2023, Intel Corporation. + * + * Author: Mathias Nyman <mathias.nyman@linux.intel.com> + */ + +#ifndef __LINUX_XHCI_SIDEBAND_H +#define __LINUX_XHCI_SIDEBAND_H + +#include <linux/scatterlist.h> +#include <linux/usb.h> + +#define EP_CTX_PER_DEV 31 /* FIMXME defined twice, from xhci.h */ + +struct xhci_sideband; + +/** + * struct xhci_sideband - representation of a sideband accessed usb device. + * @xhci: The xhci host controller the usb device is connected to + * @vdev: the usb device accessed via sideband + * @eps: array of endpoints controlled via sideband + * @ir: event handling and buffer for sideband accessed device + * + * FIXME usb device accessed via sideband Keeping track of sideband accessed usb devices. + */ + +struct xhci_sideband { + struct xhci_hcd *xhci; + struct xhci_virt_device *vdev; + struct xhci_virt_ep *eps[EP_CTX_PER_DEV]; + struct xhci_interrupter *ir; +}; + +struct xhci_sideband * +xhci_sideband_register(struct usb_device *udev); +void +xhci_sideband_unregister(struct xhci_sideband *sb); +int +xhci_sideband_add_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +int +xhci_sideband_remove_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +int +xhci_sideband_stop_endpoint(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +struct sg_table * +xhci_sideband_get_endpoint_buffer(struct xhci_sideband *sb, + struct usb_host_endpoint *host_ep); +struct sg_table * +xhci_sideband_get_event_buffer(struct xhci_sideband *sb); + +int +xhci_sideband_create_interrupter(struct xhci_sideband *sb); + +void +xhci_sideband_remove_interrupter(struct xhci_sideband *sb); + +int +xhci_sideband_interrupter_id(struct xhci_sideband *sb); + +#endif /* __LINUX_XHCI_SIDEBAND_H */ +