mirror of https://lore.kernel.org/lkml/
 help / color / mirror / Atom feed
* [PATCH v1] iommu/amd: Prevent spurious IRQ disable on high-rate GALog MSIs
@ 2026-11-01  6:21 Mina Almasry
  0 siblings, 0 replies; only message in thread
From: Mina Almasry @ 2026-11-01  6:21 UTC (permalink / raw)
  To: Joerg Roedel, Suravee Suthikulpanit, Vasant Hegde,
	Jerry Snitselaar, iommu, linux-kernel
  Cc: Mina Almasry, Joerg Roedel (AMD), Will Deacon, Robin Murphy, stable

On AMD EPYC (Turin) hosts with AVIC enabled, passed-through SR-IOV VFs
in KVM guests log interrupts targeting non-running vCPUs into the IOMMU
Guest Virtual APIC Log (GALog) ring buffer and signal the host via an
edge-triggered IntCapXT MSI (AMD-Vi*-GA).

Both __iommu_setup_intcapxt() and iommu_setup_msi() register their
handlers via request_threaded_irq(..., NULL, thread_fn, IRQF_ONESHOT,
...), using irq_default_primary_handler() (which returns
IRQ_WAKE_THREAD), while intcapxt_irqdomain_alloc() sets handle_edge_irq
which does not mask edge-triggered MSIs during oneshot thread execution.

Previously, amd_iommu_handle_irq() cleared MMIO_STATUS_OFFSET at the top
of the while (status & mask) loop before calling int_handler(iommu)
(iommu_poll_ga_log()). Under overcommitted multi-VM TCP_RR workloads
(1,000 to 10,000 flows across 4-8 VMs with vCPU overcommit), new VF
interrupts for non-running vCPUs arrive while iommu_poll_ga_log() is
draining the ring buffer, causing GALogInt to transition 0 -> 1 and fire
additional hardware edge MSIs while the IRQ thread is still inside
amd_iommu_handle_irq(). Because desc->threads_handled only increments in
irq_thread_fn() after action->thread_fn() returns, note_interrupt()
(kernel/irq/spurious.c) accounts every intermediate edge MSI as IRQ_NONE
(desc->irqs_unhandled++). Once desc->irqs_unhandled exceeds 99,900 out
of 100,000 interrupts, __report_bad_irq() logs "irq <N>: nobody cared"
and permanently disables the IOMMU GALog IRQ, breaking guest VF MSI-X
wakeups.

Fix this in two ways:
1. Drain the log ring buffer via int_handler(iommu) before writing
   status & mask to MMIO_STATUS_OFFSET (clearing only the status bits
   observed set so a concurrent overflow bit is never cleared without
   running overflow_handler()), followed by a post-clear poll pass so
   entries written before the status clear took effect are processed
   without generating redundant edge MSIs or leaving entries stranded.
2. Set IRQ_IS_POLLED on the AMD-Vi MSI and IntCapXT IRQs (clearing it on
   request_threaded_irq() failure paths) so note_interrupt() never
   spuriously disables the dedicated IOMMU event/PPR/GALog IRQ lines
   under sustained high-rate edge MSI bursts.

Discovered in production testing on AMD EPYC (Turin) hosts running
multi-VM KVM guests with passed-through SR-IOV VFs where the host
permanently disabled AMD-Vi-GA under heavy TCP_RR traffic, and verified
on the same AMD EPYC (Turin) hardware across 4-VM and 8-VM SR-IOV VF
test suites.

Fixes: 2379f3485239 ("iommu/amd: Refactor IOMMU interrupt handling logic for Event, PPR, and GA logs")
Cc: stable@vger.kernel.org
Signed-off-by: Mina Almasry <almasrymina@google.com>
---
 drivers/iommu/amd/init.c  |  4 ++++
 drivers/iommu/amd/iommu.c | 13 ++++++++++---
 2 files changed, 14 insertions(+), 3 deletions(-)

diff --git a/drivers/iommu/amd/init.c b/drivers/iommu/amd/init.c
index de6db5621194..5eea9204f209 100644
--- a/drivers/iommu/amd/init.c
+++ b/drivers/iommu/amd/init.c
@@ -2404,9 +2404,11 @@ static int iommu_setup_msi(struct amd_iommu *iommu)
 	if (r)
 		return r;
 
+	irq_set_status_flags(iommu->dev->irq, IRQ_IS_POLLED);
 	r = request_threaded_irq(iommu->dev->irq, NULL, amd_iommu_int_thread,
 				 IRQF_ONESHOT, "AMD-Vi", iommu);
 	if (r) {
+		irq_clear_status_flags(iommu->dev->irq, IRQ_IS_POLLED);
 		pci_disable_msi(iommu->dev);
 		return r;
 	}
@@ -2579,9 +2581,11 @@ static int __iommu_setup_intcapxt(struct amd_iommu *iommu, const char *devname,
 		return irq;
 	}
 
+	irq_set_status_flags(irq, IRQ_IS_POLLED);
 	ret = request_threaded_irq(irq, NULL, thread_fn, IRQF_ONESHOT, devname,
 				   iommu);
 	if (ret) {
+		irq_clear_status_flags(irq, IRQ_IS_POLLED);
 		irq_domain_free_irqs(irq, 1);
 		irq_domain_remove(domain);
 		return ret;
diff --git a/drivers/iommu/amd/iommu.c b/drivers/iommu/amd/iommu.c
index 960e1dc05ec6..ec392da008f9 100644
--- a/drivers/iommu/amd/iommu.c
+++ b/drivers/iommu/amd/iommu.c
@@ -1112,9 +1112,6 @@ static void amd_iommu_handle_irq(void *data, const char *evt_type,
 	u32 mask = int_mask | overflow_mask;
 
 	while (status & mask) {
-		/* Enable interrupt sources again */
-		writel(mask, iommu->mmio_base + MMIO_STATUS_OFFSET);
-
 		if (int_handler) {
 			pr_devel("Processing IOMMU (ivhd%d) %s Log\n",
 				 iommu->index, evt_type);
@@ -1124,6 +1121,16 @@ static void amd_iommu_handle_irq(void *data, const char *evt_type,
 		if ((status & overflow_mask) && overflow_handler)
 			overflow_handler(iommu);
 
+		/*
+		 * Clear only the status bits observed above, then poll once
+		 * more so entries written before the clear took effect are not
+		 * left unprocessed with the interrupt status bit cleared.
+		 */
+		writel(status & mask, iommu->mmio_base + MMIO_STATUS_OFFSET);
+
+		if (int_handler)
+			int_handler(iommu);
+
 		/*
 		 * Hardware bug: ERBT1312
 		 * When re-enabling interrupt (by writing 1

base-commit: 4dd16e31135ea8437a720222df34e8745f385d6d
-- 
2.56.0.385.gac671c819a-goog


^ permalink raw reply	[flat|nested] only message in thread

only message in thread, other threads:[~2026-11-01  6:21 UTC | newest]

Thread overview: (only message) (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2026-11-01  6:21 [PATCH v1] iommu/amd: Prevent spurious IRQ disable on high-rate GALog MSIs Mina Almasry

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox

all inboxes | Powered by JetHome®