[PATCH v3 4/9] vfio: selftests: igb: Program MSI-X interrupt routing

From: Josh Hilke

Date: Wed Jul 08 2026 - 19:20:37 EST


From: Alex Williamson <alex.williamson@xxxxxxxxxx>

The submitted driver writes only GPIE.EIAME (with a register value of
0x10, which is actually GPIE.Multiple_MSIX, bit 4) and clears EICR by
reading it. On QEMU this works because the emulated loopback path is
synchronous and EICR is implemented as read-to-clear unconditionally.
Real 82576 hardware needs the full MSI-X programming sequence.

Per 82576 datasheet section 7.3.2.11 Table 7-47, MSI-X mode requires:

GPIE.Multiple_MSIX (bit 4): route causes through IVAR.
GPIE.EIAME (bit 30): apply EIAM on MSI-X assertion. Without EIAME,
section 7.3.2.11 specifies EIAM only takes effect on EICR
read/write, which is not the path used here.

Configure auto-clear and auto-mask for vector 0:

EIAC (section 8.8.5): auto-clear of EICR cause bit on MSI-X assertion.
EIAM (section 8.8.6): with EIAME set, auto-mask of EIMS on MSI-X
assertion. This guarantees one interrupt per memcpy batch and
prevents repeat delivery if the cause re-asserts before EIMS is
restored.

Replace the read-to-clear of EICR with write-to-clear. Section 8.8.5
states "If any bits are set in EIAC, the EICR register should not be
read", and section 7.3.4.3 cautions against read-to-clear in MSI-X
mode in general. Write-to-clear (section 7.3.4.2) is unconditional.

Replace the magic '1' values written to EIMS/EIMC with IGB_EICR_VEC0,
add the GPIE/EIAC/EIAM macros, and drop the wrong-valued IGB_GPIE_EIAME
macro (the new definition lives next to IGB_GPIE_MULTIPLE_MSIX).

Assisted-by: Claude:claude-opus-4-7
Signed-off-by: Alex Williamson <alex.williamson@xxxxxxxxxx>
---
tools/testing/selftests/vfio/lib/drivers/igb/igb.c | 40 ++++++++++++++++++----
1 file changed, 34 insertions(+), 6 deletions(-)

diff --git a/tools/testing/selftests/vfio/lib/drivers/igb/igb.c b/tools/testing/selftests/vfio/lib/drivers/igb/igb.c
index f4f6d2ee262d..637d05e872de 100644
--- a/tools/testing/selftests/vfio/lib/drivers/igb/igb.c
+++ b/tools/testing/selftests/vfio/lib/drivers/igb/igb.c
@@ -280,11 +280,32 @@ static void igb_init(struct vfio_pci_device *device)
/* Enable MSI-X with 1 vector for the test */
vfio_pci_msix_enable(device, MSIX_VECTOR, 1);

- /* Enable auto-masking of interrupts to avoid storms without a real ISR */
- igb_write32(igb, E1000_GPIE, E1000_GPIE_EIAME);
+ /*
+ * Program MSI-X interrupt routing per 82576 datasheet:
+ *
+ * GPIE (section 7.3.2.11, Table 7-47): set Multiple_MSIX (bit 4) to
+ * route interrupt causes through IVAR mapping, and EIAME (bit 30)
+ * to apply EIAM on MSI-X assertion (without EIAME, EIAM only
+ * applies on EICR read/write).
+ *
+ * EIAC (section 8.8.5): enable auto-clear of EICR for vector 0.
+ * Without auto-clear the cause stays set after delivery and the
+ * test can see spurious interrupts on the next memcpy batch.
+ *
+ * EIAM (section 8.8.6): enable auto-mask of EIMS for vector 0 on
+ * MSI-X assertion (effective because EIAME is set), so a single
+ * interrupt is delivered per memcpy batch even if the cause
+ * re-asserts before software re-enables the mask.
+ *
+ * IVAR (section 7.3.1.2, register definition in 8.8.13): map RX
+ * cause 0 to MSI-X vector 0 and mark the entry valid.
+ */
+ igb_write32(igb, E1000_GPIE, E1000_GPIE_MSIX_MODE | E1000_GPIE_EIAME);
+ igb_write32(igb, E1000_EIAC, E1000_EICR_RX_QUEUE0);
+ igb_write32(igb, E1000_EIAM, E1000_EICR_RX_QUEUE0);

/* Enable interrupts on vector 0 */
- igb_write32(igb, E1000_EIMS, 1);
+ igb_write32(igb, E1000_EIMS, E1000_EICR_RX_QUEUE0);

/* Map vector 0 to interrupt cause 0 and mark it valid */
igb_write32(igb, E1000_IVAR0, E1000_IVAR_VALID);
@@ -310,17 +331,24 @@ static void igb_remove(struct vfio_pci_device *device)

static void igb_irq_disable(struct igb *igb)
{
- igb_write32(igb, E1000_EIMC, 1);
+ igb_write32(igb, E1000_EIMC, E1000_EICR_RX_QUEUE0);
}

static void igb_irq_enable(struct igb *igb)
{
- igb_write32(igb, E1000_EIMS, 1);
+ igb_write32(igb, E1000_EIMS, E1000_EICR_RX_QUEUE0);
}

static void igb_irq_clear(struct igb *igb)
{
- igb_read32(igb, E1000_EICR);
+ /*
+ * Use write-to-clear (datasheet 7.3.4.2). In MSI-X mode with EIAC
+ * programmed, section 8.8.5 explicitly states "If any bits are set
+ * in EIAC, the EICR register should not be read", which rules out
+ * the read-to-clear path in 7.3.4.3. Bits not in EIAC are still
+ * cleared by writing 1.
+ */
+ igb_write32(igb, E1000_EICR, 0xFFFFFFFF);
}

static void igb_memcpy_start(struct vfio_pci_device *device, iova_t src,

--
2.55.0.795.g602f6c329a-goog