On 11/2/2026 5:14 AM, Zephyr Li wrote:
The system IOMMU currently sends MSIs without checking the
corresponding vector mask bit in the MSI configuration table.

Implement per-vector masking and maintain a separate pending flag
for each vector. Notifications generated while masked are deferred
and coalesced. Unmasking a vector delivers one pending MSI, if any,
and reset clears all pending flags.

Keep the pending state separate from msix_pba because the current
PBA MMIO mapping overlaps the reserved RISC-V IOMMU register space.

Do not release deferred MSIs while FCTL.WSI selects wired interrupt mode.
When switching back to MSI mode, release unmasked pending vectors and
retain masked ones until unmask.

Add qtests covering masked and unmasked delivery, vector isolation,
MSI configuration updates, wired interrupt delivery, mode
transitions, and reset.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/4444
Signed-off-by: Zephyr Li <[email protected]>
---

Reviewed-by: Daniel Henrique Barboza <[email protected]>


Changes in v2:
- Document why the MSI pending state is kept separate from msix_pba.
- Add RISC-V IOMMU specification references for the reserved MMIO
   region and PCIe MSI-X PBA.
- Add a qtest ensuring pending MSIs are not exposed through the
   reserved MSI-X PBA MMIO region.
- Rebase onto the latest QEMU master.

  hw/riscv/riscv-iommu-sys.c     |  83 +++++-
  hw/riscv/riscv-iommu.c         |  12 +
  hw/riscv/riscv-iommu.h         |   3 +
  tests/qtest/iommu-riscv-test.c | 469 +++++++++++++++++++++++++++++++++
  4 files changed, 565 insertions(+), 2 deletions(-)

diff --git a/hw/riscv/riscv-iommu-sys.c b/hw/riscv/riscv-iommu-sys.c
index 258c171efd..f849b7a763 100644
--- a/hw/riscv/riscv-iommu-sys.c
+++ b/hw/riscv/riscv-iommu-sys.c
@@ -52,8 +52,66 @@ struct RISCVIOMMUStateSys {
      MemoryRegion msix_pba_mmio;
      uint8_t *msix_table;
      uint8_t *msix_pba;
+    /*
+     * Keep MSI pending state separate from msix_pba.
+     *
+     * The msix_pba MMIO region is mapped at offset 0x400, which is
+     * within the RISC-V IOMMU reserved register space (0x400-0xfff).
+     * Reads from this reserved region must return zero. Using msix_pba
+     * to track pending MSIs would expose non-zero bits through MMIO.
+     *
+     * The specification describes the MSI-X PBA in the context of
+     * PCIe IOMMU integration, while this device is a SysBus device.
+     * Therefore, maintain pending MSIs independently without updating
+     * msix_pba.
+     *
+     * See:
+     * https://docs.riscv.org/reference/iommu/v20260222/iommu_registers.html
+     * 
https://docs.riscv.org/reference/iommu/v20260222/iommu_hw_guidelines.html
+     */
+    bool msi_pending[RISCV_IOMMU_PCI_MSIX_VECTORS];
  };
+static void riscv_iommu_sysdev_send_MSI_unmasked(RISCVIOMMUStateSys *s,
+                                                 uint32_t vector);
+
+static bool riscv_iommu_sysdev_msi_masked(RISCVIOMMUStateSys *s,
+                                          uint32_t vector)
+{
+    uint8_t *table_entry;
+
+    g_assert(vector < RISCV_IOMMU_PCI_MSIX_VECTORS);
+    table_entry = s->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
+
+    return pci_get_long(table_entry + PCI_MSIX_ENTRY_VECTOR_CTRL) &
+           PCI_MSIX_ENTRY_CTRL_MASKBIT;
+}
+
+static void riscv_iommu_sysdev_release_pending(RISCVIOMMUStateSys *s,
+                                              uint32_t vector)
+{
+    /* Retain deferred MSI messages until MSI mode is restored. */
+    if (riscv_iommu_reg_get32(&s->iommu, RISCV_IOMMU_REG_FCTL) &
+        RISCV_IOMMU_FCTL_WSI) {
+        return;
+    }
+
+    if (s->msi_pending[vector] &&
+        !riscv_iommu_sysdev_msi_masked(s, vector)) {
+        s->msi_pending[vector] = false;
+        riscv_iommu_sysdev_send_MSI_unmasked(s, vector);
+    }
+}
+
+static void riscv_iommu_sysdev_msi_enabled(RISCVIOMMUState *iommu)
+{
+    RISCVIOMMUStateSys *s = container_of(iommu, RISCVIOMMUStateSys, iommu);
+
+    for (unsigned vector = 0; vector < ARRAY_SIZE(s->msi_pending); vector++) {
+        riscv_iommu_sysdev_release_pending(s, vector);
+    }
+}
+
  static uint64_t msix_table_mmio_read(void *opaque, hwaddr addr,
                                       unsigned size)
  {
@@ -67,9 +125,17 @@ static void msix_table_mmio_write(void *opaque, hwaddr addr,
                                    uint64_t val, unsigned size)
  {
      RISCVIOMMUStateSys *s = opaque;
+    uint32_t vector = addr / PCI_MSIX_ENTRY_SIZE;
+    bool was_masked;
g_assert(addr + size <= RISCV_IOMMU_PCI_MSIX_VECTORS * PCI_MSIX_ENTRY_SIZE);
+
+    was_masked = riscv_iommu_sysdev_msi_masked(s, vector);
      pci_set_long(s->msix_table + addr, val);
+
+    if (was_masked && !riscv_iommu_sysdev_msi_masked(s, vector)) {
+        riscv_iommu_sysdev_release_pending(s, vector);
+    }
  }
static const MemoryRegionOps msix_table_mmio_ops = {
@@ -134,8 +200,8 @@ static void riscv_iommu_sysdev_init_msi(RISCVIOMMUStateSys 
*s,
                                  &s->msix_pba_mmio);
  }
-static void riscv_iommu_sysdev_send_MSI(RISCVIOMMUStateSys *s,
-                                        uint32_t vector)
+static void riscv_iommu_sysdev_send_MSI_unmasked(RISCVIOMMUStateSys *s,
+                                                 uint32_t vector)
  {
      uint8_t *table_entry = s->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
      uint64_t msi_addr = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
@@ -160,6 +226,17 @@ static void riscv_iommu_sysdev_send_MSI(RISCVIOMMUStateSys 
*s,
      }
  }
+static void riscv_iommu_sysdev_send_MSI(RISCVIOMMUStateSys *s,
+                                        uint32_t vector)
+{
+    if (riscv_iommu_sysdev_msi_masked(s, vector)) {
+        s->msi_pending[vector] = true;
+        return;
+    }
+
+    riscv_iommu_sysdev_send_MSI_unmasked(s, vector);
+}
+
  static void riscv_iommu_sysdev_notify(RISCVIOMMUState *iommu,
                                        unsigned vector)
  {
@@ -194,6 +271,7 @@ static void riscv_iommu_sys_realize(DeviceState *dev, Error 
**errp)
      }
s->iommu.notify = riscv_iommu_sysdev_notify;
+    s->iommu.msi_enabled = riscv_iommu_sysdev_msi_enabled;
/* 4 IRQs are defined starting from s->base_irq */
      for (int i = 0; i < RISCV_IOMMU_INTR_COUNT; i++) {
@@ -255,6 +333,7 @@ static void riscv_iommu_sys_reset_hold(Object *obj, 
ResetType type)
      RISCVIOMMUState *iommu = &sys->iommu;
riscv_iommu_reset(iommu);
+    memset(sys->msi_pending, 0, sizeof(sys->msi_pending));
trace_riscv_iommu_sys_reset_hold(type);
  }
diff --git a/hw/riscv/riscv-iommu.c b/hw/riscv/riscv-iommu.c
index 0e6970773a..5209ed2c23 100644
--- a/hw/riscv/riscv-iommu.c
+++ b/hw/riscv/riscv-iommu.c
@@ -2424,6 +2424,7 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, 
hwaddr addr,
      RISCVIOMMUState *s = opaque;
      uint32_t regb = addr & ~3;
      uint32_t busy = 0;
+    uint32_t old_fctl = 0;
      uint64_t val = 0;
      bool cy_inh = false;
@@ -2439,6 +2440,10 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, hwaddr addr, /* Track actionable MMIO write. */
      switch (regb) {
+    case RISCV_IOMMU_REG_FCTL:
+        old_fctl = riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_FCTL);
+        break;
+
      case RISCV_IOMMU_REG_DDTP:
      case RISCV_IOMMU_REG_DDTP + 4:
          process_fn = riscv_iommu_process_ddtp;
@@ -2515,6 +2520,13 @@ static MemTxResult riscv_iommu_mmio_write(void *opaque, 
hwaddr addr,
       */
      riscv_iommu_write_reg_val(s, &s->regs[addr], addr, size, data);
+ if (regb == RISCV_IOMMU_REG_FCTL && s->msi_enabled &&
+        (old_fctl & RISCV_IOMMU_FCTL_WSI) &&
+        !(riscv_iommu_reg_get32(s, RISCV_IOMMU_REG_FCTL) &
+          RISCV_IOMMU_FCTL_WSI)) {
+        s->msi_enabled(s);
+    }
+
      /* Busy flag update, MSB 4-byte register. */
      if (busy) {
          uint32_t rw = ldl_le_p(&s->regs[regb]);
diff --git a/hw/riscv/riscv-iommu.h b/hw/riscv/riscv-iommu.h
index 403d10b321..e9c520981a 100644
--- a/hw/riscv/riscv-iommu.h
+++ b/hw/riscv/riscv-iommu.h
@@ -61,6 +61,9 @@ struct RISCVIOMMUState {
      /* interrupt notifier */
      void (*notify)(RISCVIOMMUState *iommu, unsigned vector);
+ /* Optional platform callback when FCTL switches from WSI to MSI. */
+    void (*msi_enabled)(RISCVIOMMUState *iommu);
+
      /* IOMMU target address space */
      AddressSpace *target_as;
      MemoryRegion *target_mr;
diff --git a/tests/qtest/iommu-riscv-test.c b/tests/qtest/iommu-riscv-test.c
index 1cd753c4f2..0c05b3bd1b 100644
--- a/tests/qtest/iommu-riscv-test.c
+++ b/tests/qtest/iommu-riscv-test.c
@@ -25,6 +25,28 @@
  #define DBG_TRANSLATED_PA       0x90000000ull
  #define DBG_NESTED_GPA          0x200000ull
+#define RISCV_IOMMU_SYS_MSI_TARGET0 (QRIOMMU_SPACE_OFFS + 0x03000000ull)
+#define RISCV_IOMMU_SYS_MSI_TARGET1  (RISCV_IOMMU_SYS_MSI_TARGET0 + 4)
+#define RISCV_IOMMU_SYS_CQ_BASE      \
+    (QRIOMMU_SPACE_OFFS + QRIOMMU_CQ_BASE_ADDR)
+#define RISCV_IOMMU_SYS_MSI_DATA0    0x12345678
+#define RISCV_IOMMU_SYS_MSI_DATA1    0xa5a55a5a
+
+/* RISC-V virt machine PLIC, hart 0 M-mode context. */
+#define RISCV_PLIC_BASE             0x0c000000
+#define RISCV_PLIC_PENDING          (RISCV_PLIC_BASE + 0x1000)
+#define RISCV_PLIC_ENABLE           (RISCV_PLIC_BASE + 0x2000)
+#define RISCV_PLIC_THRESHOLD        (RISCV_PLIC_BASE + 0x200000)
+#define RISCV_PLIC_CLAIM            (RISCV_PLIC_THRESHOLD + 4)
+#define RISCV_IOMMU_SYS_IRQ_BASE    36
+
+#define PCI_TESTDEV_TEST             0
+#define PCI_TESTDEV_WIDTH            1
+#define PCI_TESTDEV_OFFSET           4
+#define PCI_TESTDEV_DATA             8
+#define PCI_TESTDEV_COUNT            12
+#define PCI_TESTDEV_NO_EVENTFD       0
+
  /* RISC-V virt machine PCI configuration */
  #define RISCV_GPEX_PIO_BASE        0x3000000
  #define RISCV_BUS_PIO_LIMIT        0x10000
@@ -451,6 +473,432 @@ static void test_riscv_iommu_dbg(gconstpointer data)
      riscv_iommu_test_teardown(&state);
  }
+static void riscv_iommu_sys_set_wsi(QTestState *qts, bool wsi)
+{
+    uint64_t base = VIRT_RISCV_IOMMU_BASE;
+    uint32_t fctl;
+
+    /* Feature changes require Off mode and all in-memory queues disabled. */
+    g_assert_cmphex(qtest_readq(qts, base + RISCV_IOMMU_REG_DDTP) &
+                    (RISCV_IOMMU_DDTP_MODE | RISCV_IOMMU_DDTP_BUSY),
+                    ==, RISCV_IOMMU_DDTP_MODE_OFF);
+    g_assert_cmphex(qtest_readl(qts, base + RISCV_IOMMU_REG_CQCSR) &
+                    (RISCV_IOMMU_CQCSR_CQEN | RISCV_IOMMU_CQCSR_CQON |
+                     RISCV_IOMMU_CQCSR_BUSY), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, base + RISCV_IOMMU_REG_FQCSR) &
+                    (RISCV_IOMMU_FQCSR_FQEN | RISCV_IOMMU_FQCSR_FQON |
+                     RISCV_IOMMU_FQCSR_BUSY), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, base + RISCV_IOMMU_REG_PQCSR) &
+                    (RISCV_IOMMU_PQCSR_PQEN | RISCV_IOMMU_PQCSR_PQON |
+                     RISCV_IOMMU_PQCSR_BUSY), ==, 0);
+
+    fctl = qtest_readl(qts, base + RISCV_IOMMU_REG_FCTL);
+    fctl = (fctl & ~RISCV_IOMMU_FCTL_WSI) |
+           (wsi ? RISCV_IOMMU_FCTL_WSI : 0);
+    qtest_writel(qts, base + RISCV_IOMMU_REG_FCTL, fctl);
+    g_assert_cmphex(qtest_readl(qts, base + RISCV_IOMMU_REG_FCTL) &
+                    RISCV_IOMMU_FCTL_WSI, ==, fctl & RISCV_IOMMU_FCTL_WSI);
+}
+
+static QTestState *riscv_iommu_sys_test_init(const char *extra_args)
+{
+    QTestState *qts;
+    uint64_t cap;
+    uint32_t igs;
+
+    if (!qtest_has_machine("virt")) {
+        g_test_skip("virt machine not available");
+        return NULL;
+    }
+
+    qts = qtest_initf("-machine virt,aia=none,acpi=off,iommu-sys=on "
+                      "-cpu max -smp 1 -m 512 -net none -nodefaults %s",
+                      extra_args ? extra_args : "");
+
+    cap = qtest_readq(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CAP);
+    igs = (cap & RISCV_IOMMU_CAP_IGS) >> 28;
+    g_assert_cmpuint(igs, <=, RISCV_IOMMU_CAP_IGS_BOTH);
+    g_assert_cmpuint(igs, !=, RISCV_IOMMU_CAP_IGS_WSI);
+
+    riscv_iommu_sys_set_wsi(qts, false);
+
+    return qts;
+}
+
+static QPCIDevice *riscv_iommu_sys_init_msi_sink(QTestState *qts,
+                                                  QGenericPCIBus *gbus,
+                                                  QPCIBar *bar,
+                                                  uint64_t *target,
+                                                  uint32_t *data)
+{
+    QPCIDevice *dev = NULL;
+    uint32_t offset;
+
+    qpci_init_generic(gbus, qts, NULL, false);
+    riscv_config_qpci_bus(gbus);
+    qpci_device_foreach(&gbus->bus, PCI_VENDOR_ID_REDHAT,
+                        PCI_DEVICE_ID_REDHAT_TEST, save_fn, &dev);
+    g_assert(dev);
+
+    qpci_device_enable(dev);
+    *bar = qpci_iomap(dev, 0, NULL);
+    g_assert_false(bar->is_io);
+
+    qpci_io_writeb(dev, *bar, PCI_TESTDEV_TEST, PCI_TESTDEV_NO_EVENTFD);
+    g_assert_cmpuint(qpci_io_readb(dev, *bar, PCI_TESTDEV_WIDTH), ==, 1);
+    offset = qpci_io_readl(dev, *bar, PCI_TESTDEV_OFFSET);
+    *data = qpci_io_readl(dev, *bar, PCI_TESTDEV_DATA);
+    *target = bar->addr + offset;
+    g_assert_cmpuint(qpci_io_readl(dev, *bar, PCI_TESTDEV_COUNT), ==, 0);
+
+    return dev;
+}
+
+static void riscv_iommu_sys_program_msi(QTestState *qts, unsigned vector,
+                                        uint64_t target, uint32_t data,
+                                        bool masked)
+{
+    uint64_t entry = VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_MSI_CONFIG +
+                     vector * PCI_MSIX_ENTRY_SIZE;
+
+    qtest_writeq(qts, entry + PCI_MSIX_ENTRY_LOWER_ADDR, target);
+    qtest_writel(qts, entry + PCI_MSIX_ENTRY_DATA, data);
+    qtest_writel(qts, entry + PCI_MSIX_ENTRY_VECTOR_CTRL,
+                 masked ? PCI_MSIX_ENTRY_CTRL_MASKBIT : 0);
+}
+
+static void riscv_iommu_sys_set_mask(QTestState *qts, unsigned vector,
+                                     bool masked)
+{
+    uint64_t entry = VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_MSI_CONFIG +
+                     vector * PCI_MSIX_ENTRY_SIZE;
+
+    qtest_writel(qts, entry + PCI_MSIX_ENTRY_VECTOR_CTRL,
+                 masked ? PCI_MSIX_ENTRY_CTRL_MASKBIT : 0);
+}
+
+static void riscv_iommu_sys_setup_cq(QTestState *qts)
+{
+    uint64_t cqb = (RISCV_IOMMU_SYS_CQ_BASE >> 12) << 10;
+    uint32_t cqcsr;
+
+    qtest_writeq(qts, RISCV_IOMMU_SYS_CQ_BASE, UINT64_MAX);
+    qtest_writeq(qts, RISCV_IOMMU_SYS_CQ_BASE + 8, 0);
+    qtest_writeq(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQB, cqb);
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQT, 0);
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR,
+                 RISCV_IOMMU_CQCSR_CQEN | RISCV_IOMMU_CQCSR_CIE);
+
+    cqcsr = qtest_readl(qts,
+                        VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR);
+    g_assert_cmphex(cqcsr & RISCV_IOMMU_CQCSR_CQON,
+                    ==, RISCV_IOMMU_CQCSR_CQON);
+}
+
+static void riscv_iommu_sys_trigger_cq_irq(QTestState *qts)
+{
+    uint32_t cqcsr;
+
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQT, 1);
+    cqcsr = qtest_readl(qts,
+                        VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR);
+    g_assert_cmphex(cqcsr & RISCV_IOMMU_CQCSR_CMD_ILL,
+                    ==, RISCV_IOMMU_CQCSR_CMD_ILL);
+}
+
+static void riscv_iommu_sys_rearm_cq_irq(QTestState *qts)
+{
+    uint32_t cqcsr;
+
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQT, 0);
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR,
+                 RISCV_IOMMU_CQCSR_CQEN | RISCV_IOMMU_CQCSR_CIE |
+                 RISCV_IOMMU_CQCSR_CMD_ILL);
+    cqcsr = qtest_readl(qts,
+                        VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR);
+    g_assert_cmphex(cqcsr & RISCV_IOMMU_CQCSR_CMD_ILL, ==, 0);
+
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_IPSR,
+                 RISCV_IOMMU_IPSR_CIP);
+    g_assert_cmphex(qtest_readl(qts, VIRT_RISCV_IOMMU_BASE +
+                               RISCV_IOMMU_REG_IPSR) & RISCV_IOMMU_IPSR_CIP,
+                    ==, 0);
+}
+
+static void test_riscv_iommu_sys_msi_unmasked(void)
+{
+    QTestState *qts = riscv_iommu_sys_test_init(NULL);
+
+    if (!qts) {
+        return;
+    }
+
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET0, 0);
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET0,
+                                RISCV_IOMMU_SYS_MSI_DATA0, false);
+    riscv_iommu_sys_setup_cq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0),
+                    ==, RISCV_IOMMU_SYS_MSI_DATA0);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_msi_masked(void)
+{
+    QGenericPCIBus gbus;
+    QPCIBar sink_bar;
+    QPCIDevice *sink;
+    QTestState *qts = riscv_iommu_sys_test_init("-device pci-testdev");
+    uint64_t sink_target;
+    uint32_t sink_data;
+
+    if (!qts) {
+        return;
+    }
+
+    sink = riscv_iommu_sys_init_msi_sink(qts, &gbus, &sink_bar,
+                                         &sink_target, &sink_data);
+    riscv_iommu_sys_program_msi(qts, 0, sink_target, sink_data, true);
+    riscv_iommu_sys_setup_cq(qts);
+
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 0);
+
+    /*
+     * Pending MSIs must not be exposed through the reserved
+     * MSI-X PBA MMIO region.
+     */
+    g_assert_cmphex(qtest_readl(qts, VIRT_RISCV_IOMMU_BASE + 0x400),
+                    ==, 0);
+
+    riscv_iommu_sys_rearm_cq_irq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 0);
+
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 1);
+
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 1);
+    g_free(sink);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_msi_config_update(void)
+{
+    QTestState *qts = riscv_iommu_sys_test_init(NULL);
+
+    if (!qts) {
+        return;
+    }
+
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET0, 0);
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET1, 0);
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET0,
+                                RISCV_IOMMU_SYS_MSI_DATA0, true);
+    riscv_iommu_sys_setup_cq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+
+    /* Updating a masked entry must not release the deferred message. */
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET1,
+                                RISCV_IOMMU_SYS_MSI_DATA1, true);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET1), ==, 0);
+
+    /* Release uses the current address and data, rather than a snapshot. */
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET1),
+                    ==, RISCV_IOMMU_SYS_MSI_DATA1);
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET1, 0);
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET1), ==, 0);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_msi_vector_isolation(void)
+{
+    QTestState *qts = riscv_iommu_sys_test_init(NULL);
+
+    if (!qts) {
+        return;
+    }
+
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET0, 0);
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET1, 0);
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET0,
+                                RISCV_IOMMU_SYS_MSI_DATA0, true);
+    riscv_iommu_sys_program_msi(qts, 1, RISCV_IOMMU_SYS_MSI_TARGET1,
+                                RISCV_IOMMU_SYS_MSI_DATA1, false);
+    riscv_iommu_sys_setup_cq(qts);
+
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET1), ==, 0);
+
+    riscv_iommu_sys_rearm_cq_irq(qts);
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_ICVEC, 1);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET1),
+                    ==, RISCV_IOMMU_SYS_MSI_DATA1);
+
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0),
+                    ==, RISCV_IOMMU_SYS_MSI_DATA0);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_msi_wsi_switch(const void *opaque)
+{
+    bool unmask_in_wsi = GPOINTER_TO_INT(opaque);
+    QGenericPCIBus gbus;
+    QPCIBar sink_bar;
+    QPCIDevice *sink;
+    QTestState *qts = riscv_iommu_sys_test_init("-device pci-testdev");
+    uint64_t sink_target;
+    uint32_t sink_data;
+
+    if (!qts) {
+        return;
+    }
+
+    g_assert_cmpuint((qtest_readq(qts, VIRT_RISCV_IOMMU_BASE +
+                                 RISCV_IOMMU_REG_CAP) &
+                      RISCV_IOMMU_CAP_IGS) >> 28,
+                     ==, RISCV_IOMMU_CAP_IGS_BOTH);
+    sink = riscv_iommu_sys_init_msi_sink(qts, &gbus, &sink_bar,
+                                         &sink_target, &sink_data);
+    riscv_iommu_sys_program_msi(qts, 0, sink_target, sink_data, true);
+    riscv_iommu_sys_setup_cq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 0);
+
+    /* Stop the CQ before changing FCTL, preserving the deferred message. */
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR, 0);
+    riscv_iommu_sys_set_wsi(qts, true);
+    if (unmask_in_wsi) {
+        riscv_iommu_sys_set_mask(qts, 0, false);
+    }
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 0);
+
+    riscv_iommu_sys_set_wsi(qts, false);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT),
+                     ==, unmask_in_wsi ? 1 : 0);
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 1);
+
+    /* Repeated mode changes and unmask writes must not replay the MSI. */
+    riscv_iommu_sys_set_wsi(qts, true);
+    riscv_iommu_sys_set_wsi(qts, false);
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 1);
+    g_free(sink);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_wsi_delivery(void)
+{
+    const unsigned vector = 1;
+    const unsigned irq = RISCV_IOMMU_SYS_IRQ_BASE + vector;
+    const uint32_t irq_bit = 1U << (irq % 32);
+    const unsigned irq_word = (irq / 32) * 4;
+    QGenericPCIBus gbus;
+    QPCIBar sink_bar;
+    QPCIDevice *sink;
+    QTestState *qts = riscv_iommu_sys_test_init("-device pci-testdev");
+    uint64_t sink_target;
+    uint32_t sink_data;
+
+    if (!qts) {
+        return;
+    }
+
+    sink = riscv_iommu_sys_init_msi_sink(qts, &gbus, &sink_bar,
+                                         &sink_target, &sink_data);
+    riscv_iommu_sys_set_wsi(qts, true);
+    /* The MSI vector mask must not suppress delivery through the wire. */
+    riscv_iommu_sys_program_msi(qts, vector, sink_target, sink_data, true);
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_ICVEC, vector);
+
+    qtest_writel(qts, RISCV_PLIC_BASE + irq * 4, 1);
+    qtest_writel(qts, RISCV_PLIC_ENABLE + irq_word, irq_bit);
+    qtest_writel(qts, RISCV_PLIC_THRESHOLD, 0);
+    g_assert_cmphex(qtest_readl(qts, RISCV_PLIC_PENDING + irq_word), ==, 0);
+    g_assert_cmpuint(qtest_readl(qts, RISCV_PLIC_CLAIM), ==, 0);
+
+    riscv_iommu_sys_setup_cq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    /* The PLIC latches the pulse after the device's wire returns low. */
+    g_assert_cmphex(qtest_readl(qts, RISCV_PLIC_PENDING + irq_word),
+                    ==, irq_bit);
+    g_assert_cmpuint(qtest_readl(qts, RISCV_PLIC_CLAIM), ==, irq);
+    g_assert_cmphex(qtest_readl(qts, RISCV_PLIC_PENDING + irq_word), ==, 0);
+    qtest_writel(qts, RISCV_PLIC_CLAIM, irq);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 0);
+
+    /* A WSI event must not become a deferred MSI after switching modes. */
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR, 0);
+    riscv_iommu_sys_set_wsi(qts, false);
+    riscv_iommu_sys_set_mask(qts, vector, false);
+    g_assert_cmpuint(qpci_io_readl(sink, sink_bar, PCI_TESTDEV_COUNT), ==, 0);
+    g_free(sink);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_msi_wsi_reset(void)
+{
+    QTestState *qts = riscv_iommu_sys_test_init(NULL);
+
+    if (!qts) {
+        return;
+    }
+
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET0, 0);
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET0,
+                                RISCV_IOMMU_SYS_MSI_DATA0, true);
+    riscv_iommu_sys_setup_cq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    qtest_writel(qts, VIRT_RISCV_IOMMU_BASE + RISCV_IOMMU_REG_CQCSR, 0);
+    riscv_iommu_sys_set_wsi(qts, true);
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+
+    qtest_system_reset(qts);
+    /* Reprogram valid MSI state without depending on table reset values. */
+    riscv_iommu_sys_set_wsi(qts, true);
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET0,
+                                RISCV_IOMMU_SYS_MSI_DATA1, false);
+    riscv_iommu_sys_set_wsi(qts, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    riscv_iommu_sys_set_mask(qts, 0, true);
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    qtest_quit(qts);
+}
+
+static void test_riscv_iommu_sys_msi_reset(void)
+{
+    QTestState *qts = riscv_iommu_sys_test_init(NULL);
+
+    if (!qts) {
+        return;
+    }
+
+    qtest_writel(qts, RISCV_IOMMU_SYS_MSI_TARGET0, 0);
+    riscv_iommu_sys_program_msi(qts, 0, RISCV_IOMMU_SYS_MSI_TARGET0,
+                                RISCV_IOMMU_SYS_MSI_DATA0, true);
+    riscv_iommu_sys_setup_cq(qts);
+    riscv_iommu_sys_trigger_cq_irq(qts);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+
+    qtest_system_reset(qts);
+    riscv_iommu_sys_set_mask(qts, 0, false);
+    g_assert_cmphex(qtest_readl(qts, RISCV_IOMMU_SYS_MSI_TARGET0), ==, 0);
+    qtest_quit(qts);
+}
+
  int main(int argc, char **argv)
  {
      g_test_init(&argc, &argv, NULL);
@@ -468,5 +916,26 @@ int main(int argc, char **argv)
qtest_add_data_func(path, &debug_tests[i], test_riscv_iommu_dbg);
      }
+
+    qtest_add_func("/riscv-iommu-sys/msi/unmasked",
+                   test_riscv_iommu_sys_msi_unmasked);
+    qtest_add_func("/riscv-iommu-sys/msi/masked",
+                   test_riscv_iommu_sys_msi_masked);
+    qtest_add_func("/riscv-iommu-sys/msi/config-update",
+                   test_riscv_iommu_sys_msi_config_update);
+    qtest_add_func("/riscv-iommu-sys/msi/vector-isolation",
+                   test_riscv_iommu_sys_msi_vector_isolation);
+    qtest_add_data_func("/riscv-iommu-sys/msi/wsi-switch-unmasked",
+                        GINT_TO_POINTER(true),
+                        test_riscv_iommu_sys_msi_wsi_switch);
+    qtest_add_data_func("/riscv-iommu-sys/msi/wsi-switch-masked",
+                        GINT_TO_POINTER(false),
+                        test_riscv_iommu_sys_msi_wsi_switch);
+    qtest_add_func("/riscv-iommu-sys/wsi/delivery",
+                   test_riscv_iommu_sys_wsi_delivery);
+    qtest_add_func("/riscv-iommu-sys/msi/wsi-reset",
+                   test_riscv_iommu_sys_msi_wsi_reset);
+    qtest_add_func("/riscv-iommu-sys/msi/reset",
+                   test_riscv_iommu_sys_msi_reset);
      return g_test_run();
  }


Reply via email to