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();
}