@@ -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
@@ -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
new file mode 100644
@@ -0,0 +1,198 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * xHCI host controller sideband support
+ *
+ * Copyright (c) 2023, Intel Corporation.
+ *
+ * Author: Mathias Nyman
+ */
+#include <linux/usb.h>
+#include <linux/dma-direct.h>
+
+#include "xhci.h"
+
+static int
+xhci_ring_to_sgtable(struct xhci_sideband *sb, struct xhci_ring *ring, struct device *dev)
+{
+ struct sg_table *sgt;
+ struct xhci_segment *seg;
+ struct page **pages;
+ unsigned int n_pages;
+ size_t sz;
+ int i;
+
+ 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 0;
+
+ sgt = kzalloc(sizeof(struct sg_table), GFP_KERNEL);
+ if (!sgt) {
+ kvfree(pages);
+ return 0;
+ }
+
+ 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 0;
+ }
+
+ sb->sgt = sgt;
+
+ return 0;
+}
+
+static int __xhci_sideband_remove_endpoint(struct xhci_sideband *sb,
+ int idx)
+{
+ struct xhci_virt_ep *ep = sb->eps[idx];
+
+ /*
+ * 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);
+
+ sg_free_table(sb->sgt);
+ ep->sideband = NULL;
+ sb->eps[idx] = NULL;
+
+ return 0;
+}
+
+/* sidband api functions */
+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;
+
+ /* check this device isn't already controller via sideband */
+ vdev = xhci->devs[udev->slot_id];
+
+ if (!vdev || vdev->sideband) {
+ xhci_warn(xhci, "XHCI sideband slot already in use %d\n",
+ udev->slot_id);
+ return NULL;
+ }
+
+ sb = kzalloc_node(sizeof(*sb), GFP_KERNEL, dev_to_node(hcd->self.sysdev));
+ if (!sb)
+ return NULL;
+
+ sb->xhci = xhci;
+ sb->vdev = vdev;
+ vdev->sideband = sb;
+
+ return sb;
+}
+EXPORT_SYMBOL_GPL(xhci_sideband_register);
+
+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, i);
+
+ sb->vdev->sideband = NULL;
+ kfree(sb);
+}
+EXPORT_SYMBOL_GPL(xhci_sideband_unregister);
+
+int
+xhci_sideband_add_endpoint(struct xhci_sideband *sb,
+ struct usb_host_endpoint *host_ep)
+{
+ struct xhci_hcd *xhci = sb->xhci;
+ struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
+ struct xhci_virt_ep *ep;
+ unsigned int ep_index;
+
+ ep_index = xhci_get_endpoint_index(&host_ep->desc);
+ ep = &sb->vdev->eps[ep_index];
+
+ /*
+ * 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;
+ sb->ring = ep->ring;
+ xhci_ring_to_sgtable(sb, ep->ring, dev);
+
+ return 0;
+
+}
+EXPORT_SYMBOL_GPL(xhci_sideband_add_endpoint);
+
+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 || sb->vdev != ep->sideband->vdev)
+ return -ENODEV;
+
+ __xhci_sideband_remove_endpoint(sb, ep_index);
+
+ 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 || sb->vdev != ep->sideband->vdev)
+ return -EINVAL;
+
+ return xhci_stop_endpoint_sync(sb->xhci, ep, 0);
+}
+EXPORT_SYMBOL_GPL(xhci_sideband_stop_endpoint);
@@ -18,6 +18,7 @@
#include <linux/usb/hcd.h>
#include <linux/io-64-nonatomic-lo-hi.h>
#include <linux/usb/xhci-intr.h>
+#include <linux/usb/xhci-sideband.h>
/* Code sharing between pci-quirks and xhci hcd */
#include "xhci-ext-caps.h"
@@ -975,6 +976,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 {
@@ -1004,8 +1007,6 @@ struct xhci_interval_bw_table {
unsigned int ss_bw_out;
};
-#define EP_CTX_PER_DEV 31
-
struct xhci_virt_device {
int slot_id;
struct usb_device *udev;
@@ -1038,6 +1039,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;
};
/*
@@ -1518,22 +1521,6 @@ static inline const char *xhci_trb_type_string(u8 type)
#define NEC_FW_MINOR(p) (((p) >> 0) & 0xff)
#define NEC_FW_MAJOR(p) (((p) >> 8) & 0xff)
-/*
- * TRBS_PER_SEGMENT must be a multiple of 4,
- * since the command ring is 64-byte aligned.
- * It must also be greater than 16.
- */
-#define TRBS_PER_SEGMENT 256
-/* Allow two commands + a link TRB, along with any reserved command TRBs */
-#define MAX_RSVD_CMD_TRBS (TRBS_PER_SEGMENT - 3)
-#define TRB_SEGMENT_SIZE (TRBS_PER_SEGMENT*16)
-#define TRB_SEGMENT_SHIFT (ilog2(TRB_SEGMENT_SIZE))
-/* TRB buffer pointers can't cross 64KB boundaries */
-#define TRB_MAX_BUFF_SHIFT 16
-#define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT)
-/* How much data is left before the 64KB boundary? */
-#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \
- (addr & (TRB_MAX_BUFF_SIZE - 1)))
#define MAX_SOFT_RETRY 3
/*
* Limits of consecutive isoc trbs that can Block Event Interrupt (BEI) if
new file mode 100644
@@ -0,0 +1,57 @@
+/* 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 // fixme check ok
+#define __LINUX_XHCI_SIDEBAND_H
+
+#include <linux/dma-mapping.h>
+/*
+ * TRBS_PER_SEGMENT must be a multiple of 4,
+ * since the command ring is 64-byte aligned.
+ * It must also be greater than 16.
+ */
+#define TRBS_PER_SEGMENT 256
+/* Allow two commands + a link TRB, along with any reserved command TRBs */
+#define MAX_RSVD_CMD_TRBS (TRBS_PER_SEGMENT - 3)
+#define TRB_SEGMENT_SIZE (TRBS_PER_SEGMENT*16)
+#define TRB_SEGMENT_SHIFT (ilog2(TRB_SEGMENT_SIZE))
+/* TRB buffer pointers can't cross 64KB boundaries */
+#define TRB_MAX_BUFF_SHIFT 16
+#define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT)
+/* How much data is left before the 64KB boundary? */
+#define TRB_BUFF_LEN_UP_TO_BOUNDARY(addr) (TRB_MAX_BUFF_SIZE - \
+ (addr & (TRB_MAX_BUFF_SIZE - 1)))
+
+#define EP_CTX_PER_DEV 31
+
+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 sg_table *sgt;
+ struct xhci_ring *ring;
+ dma_addr_t dma;
+};
+
+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);
+#endif /* __LINUX_XHCI_SIDEBAND_H */
+