On Mon, 2026-11-02 at 16:14 +0800, 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]>

Thanks!

Applied to riscv-to-apply.next

Alistair

> ---
> 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