[RFC PATCH 16/19] vfio/pci: Quiesce INTx during PCI recovery
From: Shameer Kolothum
Date: Tue Sep 01 2026 - 05:45:30 EST
Mask INTx when recovery blocks device access. A PCI 2.3 device shares its
line, so leaving a level interrupt asserted and returning IRQ_NONE would
storm until note_interrupt() disables the line for every device on it.
Mask through pci_check_and_mask_intx() and return IRQ_HANDLED instead. A
device without per-function masking has the line to itself and is quiesced
through genirq.
That is a different job from the masking recovery does for itself, so
vfio_pci_intx_mask_for_recovery() gains a @quiesce argument to tell them
apart. The handler knows an interrupt has fired but not whether this
device is the one asserting the shared line, which is what
pci_check_and_mask_intx() reports, and a false return means the interrupt
belongs to another device. It is also the user who unmasks afterwards,
having been sent the eventfd, so the handler does not record the mask as
one recovery owes.
Record unmask requests received during recovery instead of losing them, so
vfio_pci_intx_recovery_finish() can replay them once the event ends.
The unmask handler checks for a blocked device before the existing INTx
test, so a blocked device is not touched through pci_intx(). It only
dereferences the interrupt context once that test has passed. The DisINTx
emulation calls the unmask path whatever irq_type is set to, and the
context is NULL when INTx is not in use.
Replay from the reset path as well. The unmask eventfd reaches
vfio_pci_intx_unmask_handler() through virqfd, which takes no recovery_lock
and so cannot be refused, and VFIO_DEVICE_RESET blocks access for its
duration. Without a replay there the line stays masked and the guest waits
for an interrupt which cannot arrive, since it only unmasks again after
receiving one.
Assisted-by: Claude:claude-opus-5
Signed-off-by: Shameer Kolothum <skolothumtho@xxxxxxxxxx>
---
drivers/vfio/pci/vfio_pci_core.c | 1 +
drivers/vfio/pci/vfio_pci_intrs.c | 61 ++++++++++++++++++++++++++-----
2 files changed, 53 insertions(+), 9 deletions(-)
diff --git a/drivers/vfio/pci/vfio_pci_core.c b/drivers/vfio/pci/vfio_pci_core.c
index b3ad7ed261e1..658cccecab12 100644
--- a/drivers/vfio/pci/vfio_pci_core.c
+++ b/drivers/vfio/pci/vfio_pci_core.c
@@ -1558,6 +1558,7 @@ int vfio_pci_try_reset_function(struct vfio_pci_core_device *vdev,
VFIO_PCI_RECOVERY_FAILED))) {
vfio_pci_recovery_rom_disable(vdev);
WRITE_ONCE(vdev->pci_recovery_access_blocked, false);
+ vfio_pci_intx_recovery_finish(vdev);
}
up_write(&vdev->recovery_lock);
/*
diff --git a/drivers/vfio/pci/vfio_pci_intrs.c b/drivers/vfio/pci/vfio_pci_intrs.c
index c4a075b5bb2e..1981a64b6e18 100644
--- a/drivers/vfio/pci/vfio_pci_intrs.c
+++ b/drivers/vfio/pci/vfio_pci_intrs.c
@@ -51,6 +51,15 @@ static bool is_irq_none(struct vfio_pci_core_device *vdev)
vdev->irq_type == VFIO_PCI_MSIX_IRQ_INDEX);
}
+static bool vfio_pci_recovery_blocks_irq(struct vfio_pci_core_device *vdev)
+{
+ if (!vdev->pci_recovery_supported)
+ return false;
+
+ return READ_ONCE(vdev->pci_recovery_enabled) &&
+ READ_ONCE(vdev->pci_recovery_access_blocked);
+}
+
static
struct vfio_pci_irq_ctx *vfio_irq_ctx_get(struct vfio_pci_core_device *vdev,
unsigned long index)
@@ -173,6 +182,16 @@ static int vfio_pci_intx_unmask_handler(void *opaque, void *data)
int ret = 0;
spin_lock_irqsave(&vdev->irqlock, flags);
+ /*
+ * Check for a blocked device before the INTx test below, so a blocked
+ * device is not touched through pci_intx(). @ctx is only valid when
+ * INTx is in use, so record the request only then.
+ */
+ if (unlikely(vfio_pci_recovery_blocks_irq(vdev))) {
+ if (is_intx(vdev))
+ ctx->unmask_pending = true;
+ goto out_unlock;
+ }
/*
* Unmasking comes from ioctl or config, so again, have the
@@ -184,6 +203,9 @@ static int vfio_pci_intx_unmask_handler(void *opaque, void *data)
goto out_unlock;
}
+ ctx->unmask_pending = false;
+ ctx->recovery_masked = false;
+
if (ctx->masked && !vdev->virq_disabled) {
/*
* A pending interrupt here would immediately trigger,
@@ -224,13 +246,16 @@ void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
/*
* Mask INTx because recovery has blocked device access. Returns true if this
- * call did the masking, which means recovery is the one which must unmask.
+ * call did the masking.
*
- * Nothing is normally asserted when a recovery starts, and
+ * Set @quiesce when recovery is masking the line itself rather than masking
+ * one delivered interrupt. Nothing is normally asserted at that point, and
* pci_check_and_mask_intx() only writes DisINTx when the status register says
- * an interrupt is pending, so it would leave the line alone. pci_intx() masks
- * whatever the device is doing, as __vfio_pci_intx_mask() already does for the
- * same reason.
+ * an interrupt is pending, so it would leave the line alone. The interrupt
+ * handler wants that test, since a false return there means the interrupt
+ * belongs to another device on a shared line. @quiesce also records that
+ * recovery is the one which must unmask. A masked interrupt which was
+ * delivered is the user's to unmask, exactly as outside recovery.
*
* Masking a pci_2_3 device goes through config space. If the error left
* config space unreadable the write has no effect and the line stays
@@ -239,7 +264,8 @@ void vfio_pci_intx_unmask(struct vfio_pci_core_device *vdev)
* access is blocked.
*/
static bool vfio_pci_intx_mask_for_recovery(struct vfio_pci_core_device *vdev,
- struct vfio_pci_irq_ctx *ctx)
+ struct vfio_pci_irq_ctx *ctx,
+ bool quiesce)
{
lockdep_assert_held(&vdev->irqlock);
@@ -248,11 +274,14 @@ static bool vfio_pci_intx_mask_for_recovery(struct vfio_pci_core_device *vdev,
if (!vdev->pci_2_3)
disable_irq_nosync(vdev->pdev->irq);
- else
+ else if (quiesce)
pci_intx(vdev->pdev, 0);
+ else if (!pci_check_and_mask_intx(vdev->pdev))
+ return false;
ctx->masked = true;
- ctx->recovery_masked = true;
+ if (quiesce)
+ ctx->recovery_masked = true;
return true;
}
@@ -264,6 +293,19 @@ static irqreturn_t vfio_intx_handler(int irq, void *dev_id)
int ret = IRQ_NONE;
spin_lock_irqsave(&vdev->irqlock, flags);
+ if (unlikely(vfio_pci_recovery_blocks_irq(vdev))) {
+ /*
+ * Mask rather than return IRQ_NONE with the line still
+ * asserted. For a shared pci_2_3 line an unhandled level
+ * interrupt storms until note_interrupt() disables the line
+ * for every device on it, not just this one.
+ */
+ if (vfio_pci_intx_mask_for_recovery(vdev, ctx, false))
+ ret = IRQ_HANDLED;
+ else if (ctx->masked && !vdev->pci_2_3)
+ ret = IRQ_HANDLED;
+ goto out_unlock;
+ }
if (!vdev->pci_2_3) {
disable_irq_nosync(vdev->pdev->irq);
@@ -275,6 +317,7 @@ static irqreturn_t vfio_intx_handler(int irq, void *dev_id)
ret = IRQ_HANDLED;
}
+out_unlock:
spin_unlock_irqrestore(&vdev->irqlock, flags);
if (ret == IRQ_HANDLED)
@@ -298,7 +341,7 @@ void vfio_pci_intx_recovery_start(struct vfio_pci_core_device *vdev)
if (WARN_ON_ONCE(!ctx))
goto out_unlock;
- vfio_pci_intx_mask_for_recovery(vdev, ctx);
+ vfio_pci_intx_mask_for_recovery(vdev, ctx, true);
out_unlock:
spin_unlock_irqrestore(&vdev->irqlock, flags);
--
2.43.0