* [PATCH v10 1/2] usb: xhci: refactor IRQ/interrupter plumbing for
2026-01-26 8:55 [PATCH v10 0/2] usb:xhc:route device to secondary interrupters raoxu
@ 2026-01-26 8:58 ` raoxu
2026-01-26 9:06 ` Greg KH
2026-01-26 8:58 ` [PATCH v10 2/2] usb: xhci: enable secondary interrupters and route raoxu
1 sibling, 1 reply; 6+ messages in thread
From: raoxu @ 2026-01-26 8:58 UTC (permalink / raw)
To: raoxu
Cc: gregkh, kenny, linux-usb, mathias.nyman, michal.pecio,
niklas.neronin, zhanjun
Prepare xHCI for multiple IRQ vectors/interrupters without changing the
current behavior.
Introduce a per-vector irq context (hcd + intr_num) to use as request_irq()
dev_id, and track the active vector count in xhci->irqs_enabled. Use this
single bound to enable/disable interrupters consistently across run/stop/
resume and to sync/free IRQs.
Legacy IRQ fallback also keeps irqs_enabled >= 1 so interrupter 0 remains
functional when MSI/MSI-X is unavailable.
No functional change intended: still uses interrupter 0 only.
Signed-off-by: raoxu <raoxu@uniontech.com>
---
drivers/usb/host/xhci-mem.c | 17 +++++++++++++++++
drivers/usb/host/xhci-pci.c | 34 +++++++++++++++++++++++++---------
drivers/usb/host/xhci-ring.c | 9 +++++++--
drivers/usb/host/xhci.c | 23 +++++++++++++----------
drivers/usb/host/xhci.h | 16 +++++++++++++++-
5 files changed, 77 insertions(+), 22 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index c708bdd69f16..524c58a149d3 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -2402,6 +2402,7 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
dma_addr_t dma;
+ unsigned int i;
/*
* xHCI section 5.4.6 - Device Context array must be
@@ -2495,6 +2496,22 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
if (!xhci->interrupters[0])
goto fail;
+ /* Since only the main interrupt is used, secondary_irqs_alloc is set to 0. */
+ xhci->secondary_irqs_alloc = 0;
+
+ /*
+ * Initialize all allocated interrupters here.
+ * Use allocated count as loop bound to avoid touching non-allocated
+ * or non-operational interrupters.
+ */
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Add allocated interrupters");
+ for (i = 0; i < xhci->secondary_irqs_alloc + 1; i++) {
+ if (!xhci->interrupters[i])
+ continue;
+ xhci_add_interrupter(xhci, i);
+ xhci->interrupters[i]->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
+ }
+
if (scratchpad_alloc(xhci, flags))
goto fail;
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index 585b2f3117b0..b3efd6d8fd9c 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -116,13 +116,12 @@ static const struct xhci_driver_overrides xhci_pci_overrides __initconst = {
static void xhci_msix_sync_irqs(struct xhci_hcd *xhci)
{
struct usb_hcd *hcd = xhci_to_hcd(xhci);
+ struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
+ int i;
- if (hcd->msix_enabled) {
- struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
-
- /* for now, the driver only supports one primary interrupter */
- synchronize_irq(pci_irq_vector(pdev, 0));
- }
+ if (hcd->msix_enabled)
+ for (i = 0; i < xhci->irqs_enabled; i++)
+ synchronize_irq(pci_irq_vector(pdev, i));
}
/* Legacy IRQ is freed by usb_remove_hcd() or usb_hcd_pci_shutdown() */
@@ -130,11 +129,17 @@ static void xhci_cleanup_msix(struct xhci_hcd *xhci)
{
struct usb_hcd *hcd = xhci_to_hcd(xhci);
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
+ int i;
if (hcd->irq > 0)
return;
- free_irq(pci_irq_vector(pdev, 0), xhci_to_hcd(xhci));
+ for (i = 0; i < xhci->irqs_enabled; i++)
+ free_irq(pci_irq_vector(pdev, i), &xhci->irq_ctx[i]);
+
+ xhci->irqs_enabled = 0;
+ memset(xhci->irq_ctx, 0, sizeof(xhci->irq_ctx));
+
pci_free_irq_vectors(pdev);
hcd->msix_enabled = 0;
}
@@ -145,6 +150,7 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
int ret;
+ struct xhci_irq_ctx *ctx;
/*
* Some Fresco Logic host controllers advertise MSI, but fail to
@@ -174,11 +180,17 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
goto legacy_irq;
}
- ret = request_irq(pci_irq_vector(pdev, 0), xhci_msi_irq, 0, "xhci_hcd",
- xhci_to_hcd(xhci));
+ memset(xhci->irq_ctx, 0, sizeof(xhci->irq_ctx));
+ xhci->irqs_enabled = 0;
+
+ ctx = &xhci->irq_ctx[0];
+ ctx->hcd = hcd;
+ ctx->intr_num = 0;
+ ret = request_irq(pci_irq_vector(pdev, 0), xhci_msi_irq, 0, "xhci_hcd", ctx);
if (ret)
goto free_irq_vectors;
+ xhci->irqs_enabled = 1;
hcd->msi_enabled = 1;
hcd->msix_enabled = pdev->msix_enabled;
return 0;
@@ -186,6 +198,7 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
free_irq_vectors:
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable %s interrupt",
pdev->msix_enabled ? "MSI-X" : "MSI");
+ xhci->irqs_enabled = 0;
pci_free_irq_vectors(pdev);
legacy_irq:
@@ -198,6 +211,9 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
snprintf(hcd->irq_descr, sizeof(hcd->irq_descr), "%s:usb%d",
hcd->driver->description, hcd->self.busnum);
+ /* legacy IRQ path still needs interrupter 0 */
+ xhci->irqs_enabled = 1;
+
/* fall back to legacy interrupt */
ret = request_irq(pdev->irq, &usb_hcd_irq, IRQF_SHARED, hcd->irq_descr, hcd);
if (ret) {
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 9315ba18310d..3ea134c07c5f 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -3220,9 +3220,14 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
return ret;
}
-irqreturn_t xhci_msi_irq(int irq, void *hcd)
+irqreturn_t xhci_msi_irq(int irq, void *data)
{
- return xhci_irq(hcd);
+ struct xhci_irq_ctx *ctx = data;
+
+ /* For now only vector 0 is requested; keep behavior unchanged. */
+ if (!ctx || !ctx->hcd)
+ return IRQ_NONE;
+ return xhci_irq(ctx->hcd);
}
EXPORT_SYMBOL_GPL(xhci_msi_irq);
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index b3ba16b9718c..cbf96cb51583 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -576,10 +576,6 @@ static int xhci_init(struct usb_hcd *hcd)
/* Set USB 3.0 device notifications for function remote wake */
xhci_set_dev_notifications(xhci);
- /* Initialize the Primary interrupter */
- xhci_add_interrupter(xhci, 0);
- xhci->interrupters[0]->isoc_bei_interval = AVOID_BEI_INTERVAL_MAX;
-
/* Initializing Compliance Mode Recovery Data If Needed */
if (xhci_compliance_mode_recovery_timer_quirk_check()) {
xhci->quirks |= XHCI_COMP_MODE_QUIRK;
@@ -594,9 +590,9 @@ static int xhci_init(struct usb_hcd *hcd)
static int xhci_run_finished(struct xhci_hcd *xhci)
{
- struct xhci_interrupter *ir = xhci->interrupters[0];
unsigned long flags;
u32 temp;
+ int i;
/*
* Enable interrupts before starting the host (xhci 4.2 and 5.5.2).
@@ -609,8 +605,10 @@ static int xhci_run_finished(struct xhci_hcd *xhci)
temp |= (CMD_EIE);
writel(temp, &xhci->op_regs->command);
- xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable primary interrupter");
- xhci_enable_interrupter(ir);
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init, "Enable all interrupters");
+ for (i = 0; i < xhci->irqs_enabled; i++)
+ if (xhci->interrupters[i])
+ xhci_enable_interrupter(xhci->interrupters[i]);
if (xhci_start(xhci)) {
xhci_halt(xhci);
@@ -707,7 +705,7 @@ void xhci_stop(struct usb_hcd *hcd)
{
u32 temp;
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
- struct xhci_interrupter *ir = xhci->interrupters[0];
+ int i;
mutex_lock(&xhci->mutex);
@@ -742,7 +740,9 @@ void xhci_stop(struct usb_hcd *hcd)
"// Disabling event ring interrupts");
temp = readl(&xhci->op_regs->status);
writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
- xhci_disable_interrupter(xhci, ir);
+ for (i = 0; i < xhci->irqs_enabled; i++)
+ if (xhci->interrupters[i])
+ xhci_disable_interrupter(xhci, xhci->interrupters[i]);
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "cleaning up memory");
xhci_mem_cleanup(xhci);
@@ -1087,6 +1087,7 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
bool comp_timer_running = false;
bool pending_portevent = false;
bool suspended_usb3_devs = false;
+ int i;
if (!hcd->state)
return 0;
@@ -1180,7 +1181,9 @@ int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume)
xhci_dbg(xhci, "// Disabling event ring interrupts\n");
temp = readl(&xhci->op_regs->status);
writel((temp & ~0x1fff) | STS_EINT, &xhci->op_regs->status);
- xhci_disable_interrupter(xhci, xhci->interrupters[0]);
+ for (i = 0; i < xhci->irqs_enabled; i++)
+ if (xhci->interrupters[i])
+ xhci_disable_interrupter(xhci, xhci->interrupters[i]);
xhci_dbg(xhci, "cleaning up memory\n");
xhci_mem_cleanup(xhci);
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 2b0796f6d00e..7707fd7564c5 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -45,6 +45,9 @@
*/
#define MAX_HC_INTRS 128
+/* Software cap for secondary interrupters; not a hardware limit. */
+#define MAX_SECONDARY_INTRNUM 4
+
/*
* xHCI register interface.
* This corresponds to the eXtensible Host Controller Interface (xHCI)
@@ -1497,6 +1500,11 @@ struct xhci_hub {
u8 min_rev;
};
+struct xhci_irq_ctx {
+ struct usb_hcd *hcd;
+ u8 intr_num;
+};
+
/* There is one xhci_hcd structure per controller */
struct xhci_hcd {
struct usb_hcd *main_hcd;
@@ -1533,6 +1541,12 @@ struct xhci_hcd {
/* data structures */
struct xhci_device_context_array *dcbaa;
struct xhci_interrupter **interrupters;
+ /* Number of secondary interrupters successfully allocated */
+ unsigned int secondary_irqs_alloc;
+ /* Number of IRQ vectors successfully requested (includes vector 0) */
+ unsigned int irqs_enabled;
+ /* MSI/MSI-X vector contexts. Vector 0 uses [0], secondary use [1..] */
+ struct xhci_irq_ctx irq_ctx[MAX_SECONDARY_INTRNUM + 1];
struct xhci_ring *cmd_ring;
unsigned int cmd_ring_state;
#define CMD_RING_STATE_RUNNING (1 << 0)
@@ -1895,7 +1909,7 @@ int xhci_suspend(struct xhci_hcd *xhci, bool do_wakeup);
int xhci_resume(struct xhci_hcd *xhci, bool power_lost, bool is_auto_resume);
irqreturn_t xhci_irq(struct usb_hcd *hcd);
-irqreturn_t xhci_msi_irq(int irq, void *hcd);
+irqreturn_t xhci_msi_irq(int irq, void *data);
int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev);
int xhci_alloc_tt_info(struct xhci_hcd *xhci,
struct xhci_virt_device *virt_dev,
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread* [PATCH v10 2/2] usb: xhci: enable secondary interrupters and route
2026-01-26 8:55 [PATCH v10 0/2] usb:xhc:route device to secondary interrupters raoxu
2026-01-26 8:58 ` [PATCH v10 1/2] usb: xhci: refactor IRQ/interrupter plumbing for raoxu
@ 2026-01-26 8:58 ` raoxu
2026-01-26 9:10 ` Greg KH
2026-01-26 14:49 ` kernel test robot
1 sibling, 2 replies; 6+ messages in thread
From: raoxu @ 2026-01-26 8:58 UTC (permalink / raw)
To: raoxu
Cc: gregkh, kenny, linux-usb, mathias.nyman, michal.pecio,
niklas.neronin, zhanjun
Some xHCI hosts expose multiple MSI/MSI-X vectors and support multiple
interrupters with independent event rings, but Linux commonly routes all
transfer completions through interrupter 0.
Allocate a small capped set of secondary interrupters in xhci_mem_init()
(MAX_SECONDARY_INTRNUM, default 4) and request up to the matching number
of IRQ vectors (bounded by what PCI core provides). Dispatch each vector
to its interrupter via the per-vector irq_ctx.
Route transfers per USB device (slot) by assigning vdev->interrupter at
device allocation time and programming the interrupter target (slot
context + TRB_INTR_TARGET) from that selection, so completions land on the
selected event ring. Drain a slot's secondary event ring at selected
command completion boundaries to reduce late-event artifacts when teardown
happens on interrupter 0.
Signed-off-by: raoxu <raoxu@uniontech.com>
---
drivers/usb/host/xhci-mem.c | 35 +++++++-
drivers/usb/host/xhci-pci.c | 35 +++++---
drivers/usb/host/xhci-ring.c | 151 ++++++++++++++++++++++++++++-------
drivers/usb/host/xhci.c | 14 ++++
drivers/usb/host/xhci.h | 10 +++
5 files changed, 207 insertions(+), 38 deletions(-)
diff --git a/drivers/usb/host/xhci-mem.c b/drivers/usb/host/xhci-mem.c
index 524c58a149d3..e65f84bc1c8f 100644
--- a/drivers/usb/host/xhci-mem.c
+++ b/drivers/usb/host/xhci-mem.c
@@ -1197,6 +1197,15 @@ int xhci_setup_addressable_virt_dev(struct xhci_hcd *xhci, struct usb_device *ud
ep0_ctx->tx_info = cpu_to_le32(EP_AVG_TRB_LENGTH(8));
+ /* Match Slot Context interrupter target to vdev->interrupter. */
+ if (dev->interrupter) {
+ u32 tt = le32_to_cpu(slot_ctx->tt_info);
+
+ tt &= ~SLOT_INTR_TARGET_MASK;
+ tt |= SLOT_INTR_TARGET(dev->interrupter->intr_num);
+ slot_ctx->tt_info = cpu_to_le32(tt);
+ }
+
trace_xhci_setup_addressable_virt_device(dev);
/* Steps 7 and 8 were done in xhci_alloc_virt_device() */
@@ -2402,7 +2411,8 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
{
struct device *dev = xhci_to_hcd(xhci)->self.sysdev;
dma_addr_t dma;
- unsigned int i;
+ unsigned int secondary_intr_num;
+ int i;
/*
* xHCI section 5.4.6 - Device Context array must be
@@ -2496,9 +2506,30 @@ int xhci_mem_init(struct xhci_hcd *xhci, gfp_t flags)
if (!xhci->interrupters[0])
goto fail;
- /* Since only the main interrupt is used, secondary_irqs_alloc is set to 0. */
+ xhci_dbg_trace(xhci, trace_xhci_dbg_init,
+ "Allocating secondary event ring");
xhci->secondary_irqs_alloc = 0;
+ if (xhci->max_interrupters > 1)
+ secondary_intr_num = min_t(unsigned int,
+ xhci->max_interrupters - 1,
+ (unsigned int)MAX_SECONDARY_INTRNUM);
+
+ for (i = 1; i <= secondary_intr_num; i++) {
+ if (xhci->interrupters[i])
+ continue;
+ xhci->interrupters[i] = xhci_alloc_interrupter(xhci, i, flags);
+ if (!xhci->interrupters[i]) {
+ while (--i >= 1) {
+ xhci_free_interrupter(xhci, xhci->interrupters[i]);
+ xhci->interrupters[i] = NULL;
+ }
+ xhci->secondary_irqs_alloc = 0;
+ break;
+ }
+ xhci->secondary_irqs_alloc++;
+ }
+
/*
* Initialize all allocated interrupters here.
* Use allocated count as loop bound to avoid touching non-allocated
diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c
index b3efd6d8fd9c..4ff3e29070b1 100644
--- a/drivers/usb/host/xhci-pci.c
+++ b/drivers/usb/host/xhci-pci.c
@@ -150,6 +150,8 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
struct pci_dev *pdev = to_pci_dev(hcd->self.controller);
struct xhci_hcd *xhci = hcd_to_xhci(hcd);
int ret;
+ unsigned int irqs_num;
+ int i;
struct xhci_irq_ctx *ctx;
/*
@@ -173,7 +175,7 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
/* TODO: Check with MSI Soc for sysdev */
xhci->nvecs = pci_alloc_irq_vectors(pdev, 1, xhci->nvecs,
- PCI_IRQ_MSIX | PCI_IRQ_MSI);
+ PCI_IRQ_MSIX | PCI_IRQ_MSI | PCI_IRQ_AFFINITY);
if (xhci->nvecs < 0) {
xhci_dbg_trace(xhci, trace_xhci_dbg_init,
"failed to allocate IRQ vectors");
@@ -183,14 +185,30 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
memset(xhci->irq_ctx, 0, sizeof(xhci->irq_ctx));
xhci->irqs_enabled = 0;
- ctx = &xhci->irq_ctx[0];
- ctx->hcd = hcd;
- ctx->intr_num = 0;
- ret = request_irq(pci_irq_vector(pdev, 0), xhci_msi_irq, 0, "xhci_hcd", ctx);
- if (ret)
- goto free_irq_vectors;
+ /*
+ * Request up to 1 + secondary_irqs_alloc vectors (vector 0 + secondary),
+ * limited by what PCI core actually allocated.
+ */
+ irqs_num = min_t(unsigned int,
+ (unsigned int)xhci->nvecs,
+ (unsigned int)(1 + xhci->secondary_irqs_alloc));
+
+ for (i = 0; i < irqs_num; i++) {
+ ctx = &xhci->irq_ctx[i];
+ ctx->hcd = hcd;
+ ctx->intr_num = i;
+ ret = request_irq(pci_irq_vector(pdev, i), xhci_msi_irq, 0,
+ "xhci_hcd", ctx);
+ if (ret) {
+ while (--i >= 0)
+ free_irq(pci_irq_vector(pdev, i), &xhci->irq_ctx[i]);
+ xhci->irqs_enabled = 0;
+ memset(xhci->irq_ctx, 0, sizeof(xhci->irq_ctx));
+ goto free_irq_vectors;
+ }
+ xhci->irqs_enabled++;
+ }
- xhci->irqs_enabled = 1;
hcd->msi_enabled = 1;
hcd->msix_enabled = pdev->msix_enabled;
return 0;
@@ -198,7 +216,6 @@ static int xhci_try_enable_msi(struct usb_hcd *hcd)
free_irq_vectors:
xhci_dbg_trace(xhci, trace_xhci_dbg_init, "disable %s interrupt",
pdev->msix_enabled ? "MSI-X" : "MSI");
- xhci->irqs_enabled = 0;
pci_free_irq_vectors(pdev);
legacy_irq:
diff --git a/drivers/usb/host/xhci-ring.c b/drivers/usb/host/xhci-ring.c
index 3ea134c07c5f..f16339f47ed4 100644
--- a/drivers/usb/host/xhci-ring.c
+++ b/drivers/usb/host/xhci-ring.c
@@ -64,6 +64,9 @@ static int queue_command(struct xhci_hcd *xhci, struct xhci_command *cmd,
u32 field1, u32 field2,
u32 field3, u32 field4, bool command_must_succeed);
+static void xhci_handle_slot_secondary_events(struct xhci_hcd *xhci,
+ union xhci_trb *cmd_trb);
+
/*
* Returns zero if the TRB isn't in this segment, otherwise it returns the DMA
* address of the TRB.
@@ -1859,6 +1862,9 @@ static void handle_cmd_completion(struct xhci_hcd *xhci,
}
}
+ /* Handle slot secondary events before command-specific teardown logic */
+ xhci_handle_slot_secondary_events(xhci, cmd_trb);
+
cmd_type = TRB_FIELD_TO_TYPE(le32_to_cpu(cmd_trb->generic.field[3]));
switch (cmd_type) {
case TRB_ENABLE_SLOT:
@@ -3138,6 +3144,72 @@ static int xhci_handle_events(struct xhci_hcd *xhci, struct xhci_interrupter *ir
return 0;
}
+/*
+ * Handle a slot's secondary event ring at command completion boundaries.
+ *
+ * With secondary interrupters, transfer events may lag behind command
+ * completions (handled on interrupter 0). Commands that stop/reset/disable
+ * endpoints/slots can reclaim TD state before those transfer events are
+ * processed, leading to "Spurious event dma" reports. Call this from
+ * handle_cmd_completion() before command-specific completion handling.
+ */
+static void xhci_handle_slot_secondary_events(struct xhci_hcd *xhci,
+ union xhci_trb *cmd_trb)
+{
+ u32 field3, cmd_type, slot_id;
+ struct xhci_virt_device *vdev;
+ struct xhci_interrupter *sec_ir;
+ unsigned int intr;
+
+ /* No secondary routing -> nothing to do */
+ if (xhci->irqs_enabled <= 1)
+ return;
+
+ field3 = le32_to_cpu(cmd_trb->generic.field[3]);
+ cmd_type = TRB_FIELD_TO_TYPE(field3);
+ slot_id = TRB_TO_SLOT_ID(field3);
+
+ if (!slot_id)
+ return;
+
+ /*
+ * Handle only for commands that can stop/reset/disable endpoints/slots
+ * and/or cause the driver to reclaim TD ownership.
+ */
+ switch (cmd_type) {
+ case TRB_STOP_RING:
+ case TRB_SET_DEQ:
+ case TRB_RESET_EP:
+ case TRB_RESET_DEV:
+ case TRB_DISABLE_SLOT:
+ break;
+ case TRB_CONFIG_EP:
+ /* Only needed for deconfigure (drop endpoints) */
+ if (!(field3 & TRB_DC))
+ return;
+ break;
+ default:
+ return;
+ }
+
+ vdev = xhci->devs[slot_id];
+ if (!vdev)
+ return;
+
+ intr = vdev->interrupter->intr_num;
+ if (!intr)
+ return; /* slot is on primary interrupter */
+
+ sec_ir = xhci->interrupters[intr];
+ if (!sec_ir || !sec_ir->event_ring)
+ return;
+
+ lockdep_assert_held(&xhci->lock);
+
+ /* Handle pending events normally to complete URB/TD bookkeeping. */
+ xhci_handle_events(xhci, sec_ir, false);
+}
+
/*
* Move the event ring dequeue pointer to skip events kept in the secondary
* event ring. This is used to ensure that pending events in the ring are
@@ -3169,14 +3241,8 @@ void xhci_skip_sec_intr_events(struct xhci_hcd *xhci,
xhci_handle_events(xhci, ir, true);
}
-/*
- * xHCI spec says we can get an interrupt, and if the HC has an error condition,
- * we might get bad data out of the event ring. Section 4.10.2.7 has a list of
- * indicators of an event TRB error, but we check the status *first* to be safe.
- */
-irqreturn_t xhci_irq(struct usb_hcd *hcd)
+static irqreturn_t xhci_irq_ir(struct xhci_hcd *xhci, struct xhci_interrupter *ir)
{
- struct xhci_hcd *xhci = hcd_to_xhci(hcd);
irqreturn_t ret = IRQ_HANDLED;
u32 status;
@@ -3188,7 +3254,11 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
goto out;
}
- if (!(status & STS_EINT)) {
+ /*
+ * STS_EINT is only meaningful for the primary interrupter (0).
+ * Secondary vectors may interrupt without STS_EINT set.
+ */
+ if (ir->intr_num == 0 && !(status & STS_EINT)) {
ret = IRQ_NONE;
goto out;
}
@@ -3204,30 +3274,52 @@ irqreturn_t xhci_irq(struct usb_hcd *hcd)
goto out;
}
- /*
- * Clear the op reg interrupt status first,
- * so we can receive interrupts from other MSI-X interrupters.
- * Write 1 to clear the interrupt status.
- */
- status |= STS_EINT;
- writel(status, &xhci->op_regs->status);
+ if (ir->intr_num == 0) {
+ /*
+ * Clear the op reg interrupt status first,
+ * so we can receive interrupts from other MSI-X interrupters.
+ * Write 1 to clear the interrupt status.
+ */
+ status |= STS_EINT;
+ writel(status, &xhci->op_regs->status);
+ }
- /* This is the handler of the primary interrupter */
- xhci_handle_events(xhci, xhci->interrupters[0], false);
+ /* Handle events for this interrupter. */
+ xhci_handle_events(xhci, ir, false);
out:
spin_unlock(&xhci->lock);
return ret;
}
+/*
+ * xHCI spec says we can get an interrupt, and if the HC has an error condition,
+ * we might get bad data out of the event ring. Section 4.10.2.7 has a list of
+ * indicators of an event TRB error, but we check the status *first* to be safe.
+ */
+irqreturn_t xhci_irq(struct usb_hcd *hcd)
+{
+ struct xhci_hcd *xhci = hcd_to_xhci(hcd);
+
+ return xhci_irq_ir(xhci, xhci->interrupters[0]);
+}
+
irqreturn_t xhci_msi_irq(int irq, void *data)
{
struct xhci_irq_ctx *ctx = data;
+ struct xhci_hcd *xhci;
+ struct xhci_interrupter *ir;
- /* For now only vector 0 is requested; keep behavior unchanged. */
+ /* All MSI/MSI-X vectors use ctx (dev_id) to select interrupter.*/
if (!ctx || !ctx->hcd)
return IRQ_NONE;
- return xhci_irq(ctx->hcd);
+
+ xhci = hcd_to_xhci(ctx->hcd);
+ ir = xhci->interrupters[ctx->intr_num];
+ if (!ir)
+ return IRQ_NONE;
+
+ return xhci_irq_ir(xhci, ir);
}
EXPORT_SYMBOL_GPL(xhci_msi_irq);
@@ -3629,6 +3721,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
int sent_len, ret;
u32 field, length_field, remainder;
u64 addr, send_addr;
+ struct xhci_interrupter *ir;
ring = xhci_urb_to_transfer_ring(xhci, urb);
if (!ring)
@@ -3669,6 +3762,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
start_trb = &ring->enqueue->generic;
start_cycle = ring->cycle_state;
send_addr = addr;
+ ir = xhci->devs[slot_id]->interrupter;
/* Queue the TRBs, even if they are zero-length */
for (enqd_len = 0; first_trb || enqd_len < full_len;
@@ -3729,7 +3823,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
length_field = TRB_LEN(trb_buff_len) |
TRB_TD_SIZE(remainder) |
- TRB_INTR_TARGET(0);
+ TRB_INTR_TARGET(ir->intr_num);
queue_trb(xhci, ring, more_trbs_coming | need_zero_pkt,
lower_32_bits(send_addr),
@@ -3761,7 +3855,7 @@ int xhci_queue_bulk_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
urb_priv->td[1].end_trb = ring->enqueue;
urb_priv->td[1].end_seg = ring->enq_seg;
field = TRB_TYPE(TRB_NORMAL) | ring->cycle_state | TRB_IOC;
- queue_trb(xhci, ring, 0, 0, 0, TRB_INTR_TARGET(0), field);
+ queue_trb(xhci, ring, 0, 0, 0, TRB_INTR_TARGET(ir->intr_num), field);
}
check_trb_math(urb, enqd_len);
@@ -3783,6 +3877,9 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
u32 field;
struct urb_priv *urb_priv;
struct xhci_td *td;
+ struct xhci_interrupter *ir;
+
+ ir = xhci->devs[slot_id]->interrupter;
ep_ring = xhci_urb_to_transfer_ring(xhci, urb);
if (!ep_ring)
@@ -3805,7 +3902,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
if (last_trb_on_seg(ep_ring->enq_seg, ep_ring->enqueue + 1)) {
field = TRB_TYPE(TRB_TR_NOOP) | ep_ring->cycle_state;
queue_trb(xhci, ep_ring, false, 0, 0,
- TRB_INTR_TARGET(0), field);
+ TRB_INTR_TARGET(ir->intr_num), field);
}
}
@@ -3856,7 +3953,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
queue_trb(xhci, ep_ring, true,
setup->bRequestType | setup->bRequest << 8 | le16_to_cpu(setup->wValue) << 16,
le16_to_cpu(setup->wIndex) | le16_to_cpu(setup->wLength) << 16,
- TRB_LEN(8) | TRB_INTR_TARGET(0),
+ TRB_LEN(8) | TRB_INTR_TARGET(ir->intr_num),
/* Immediate data in pointer */
field);
@@ -3886,7 +3983,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
urb, 1);
length_field = TRB_LEN(urb->transfer_buffer_length) |
TRB_TD_SIZE(remainder) |
- TRB_INTR_TARGET(0);
+ TRB_INTR_TARGET(ir->intr_num);
if (setup->bRequestType & USB_DIR_IN)
field |= TRB_DIR_IN;
queue_trb(xhci, ep_ring, true,
@@ -3909,7 +4006,7 @@ int xhci_queue_ctrl_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
queue_trb(xhci, ep_ring, false,
0,
0,
- TRB_INTR_TARGET(0),
+ TRB_INTR_TARGET(ir->intr_num),
/* Event on completion */
field | TRB_IOC | TRB_TYPE(TRB_STATUS) | ep_ring->cycle_state);
@@ -4099,7 +4196,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
xep = &xhci->devs[slot_id]->eps[ep_index];
ep_ring = xhci->devs[slot_id]->eps[ep_index].ring;
- ir = xhci->interrupters[0];
+ ir = xhci->devs[slot_id]->interrupter;
num_tds = urb->number_of_packets;
if (num_tds < 1) {
@@ -4200,7 +4297,7 @@ static int xhci_queue_isoc_tx(struct xhci_hcd *xhci, gfp_t mem_flags,
urb, more_trbs_coming);
length_field = TRB_LEN(trb_buff_len) |
- TRB_INTR_TARGET(0);
+ TRB_INTR_TARGET(ir->intr_num);
/* xhci 1.1 with ETE uses TD Size field for TBC */
if (first_trb && xep->use_extended_tbc)
diff --git a/drivers/usb/host/xhci.c b/drivers/usb/host/xhci.c
index cbf96cb51583..96934a29e39b 100644
--- a/drivers/usb/host/xhci.c
+++ b/drivers/usb/host/xhci.c
@@ -4123,6 +4123,7 @@ static void xhci_free_dev(struct usb_hcd *hcd, struct usb_device *udev)
virt_dev->eps[i].ep_state &= ~EP_STOP_CMD_PENDING;
virt_dev->udev = NULL;
xhci_disable_slot(xhci, udev->slot_id);
+ virt_dev->interrupter = NULL;
spin_lock_irqsave(&xhci->lock, flags);
xhci_free_virt_device(xhci, virt_dev, udev->slot_id);
@@ -4219,6 +4220,7 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev)
unsigned long flags;
int ret, slot_id;
struct xhci_command *command;
+ unsigned int sec_irqs, intr_num;
command = xhci_alloc_command(xhci, true, GFP_KERNEL);
if (!command)
@@ -4275,6 +4277,18 @@ int xhci_alloc_dev(struct usb_hcd *hcd, struct usb_device *udev)
udev->slot_id = slot_id;
+ /*
+ * Spread devices across IRQ-backed secondary interrupters [1..].
+ * If only vector 0 is enabled, default to interrupter 0.
+ */
+ sec_irqs = (xhci->irqs_enabled > 1) ? (xhci->irqs_enabled - 1) : 0;
+ if (sec_irqs) {
+ intr_num = slot_id % sec_irqs;
+ vdev->interrupter = xhci->interrupters[intr_num + 1];
+ } else {
+ vdev->interrupter = xhci->interrupters[0];
+ }
+
xhci_debugfs_create_slot(xhci, slot_id);
/*
diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h
index 7707fd7564c5..b30ace66ff62 100644
--- a/drivers/usb/host/xhci.h
+++ b/drivers/usb/host/xhci.h
@@ -408,6 +408,11 @@ struct xhci_slot_ctx {
#define SLOT_STATE_ADDRESSED 2
#define SLOT_STATE_CONFIGURED 3
+/* Interrupter Target - tt_info[31:22] (xHCI Slot Context) */
+#define SLOT_INTR_TARGET_SHIFT 22
+#define SLOT_INTR_TARGET_MASK (0x3ffU << SLOT_INTR_TARGET_SHIFT)
+#define SLOT_INTR_TARGET(p) (((p) & 0x3ffU) << SLOT_INTR_TARGET_SHIFT)
+
/**
* struct xhci_ep_ctx
* @ep_info: endpoint state, streams, mult, and interval information.
@@ -767,6 +772,11 @@ struct xhci_virt_device {
void *debugfs_private;
/* set if this endpoint is controlled via sideband access*/
struct xhci_sideband *sideband;
+ /*
+ * Default transfer-event interrupter for this USB device.
+ * Queueing code programs TRB_INTR_TARGET() from vdev->interrupter.
+ */
+ struct xhci_interrupter *interrupter;
};
/*
--
2.50.1
^ permalink raw reply related [flat|nested] 6+ messages in thread