hw/riscv/iommu-sys: MSI configuration-table masks are ignored

The source excerpts below are from QEMU master at ff1d2d19d7e24893e2012d879f8e73077e17b9bd.

Host environment

  • Operating system: Linux
  • OS/kernel version: 6.0
  • Architecture: x86
  • QEMU flavor: qemu-system-riscv64
  • QEMU version: qemu.git ff1d2d19d7e24893e2012d879f8e73077e17b9bd

Emulated/Virtualized environment

  • Operating system: RISC-V guest using the platform RISC-V IOMMU in MSI mode
  • Architecture: RISC-V
  • Accelerator: TCG
  • Relevant CPU/device configuration: Program an MSI table entry, set its vector mask, trigger an IOMMU interrupt, then unmask

Description of problem

Background and terminology

riscv-iommu-sys is QEMU's platform-device wrapper around the RISC-V IOMMU core. It exposes a link to the interrupt controller and an MSI-X-shaped table/PBA region. A QOM link property is optional unless realize validates it, and an MSI-X table entry's vector-control mask and pending bit array have behavior, not just storage layout.

Affected code path

The affected implementation locations are:

hw/riscv/riscv-iommu-sys.c: MSI table/PBA handlers and riscv_iommu_sysdev_send_MSI()

The wrapper allocates the RISC-V IOMMU MSI configuration table using QEMU's MSI-X entry-layout constants and also maps pending-bit storage. riscv_iommu_sysdev_send_MSI() reads only address/data and always sends; table writes do not detect an unmask transition.

Current source evidence

hw/riscv/riscv-iommu-sys.c:87

 87 | 
 88 | static uint64_t msix_pba_mmio_read(void *opaque, hwaddr addr,
 89 |                                    unsigned size)
 90 | {
 91 |     RISCVIOMMUStateSys *s = opaque;
 92 | 
 93 |     return pci_get_long(s->msix_pba + addr);
 94 | }
 95 | 
 96 | static void msix_pba_mmio_write(void *opaque, hwaddr addr,
 97 |                                 uint64_t val, unsigned size)
 98 | {
 99 | }
100 | 
101 | static const MemoryRegionOps msix_pba_mmio_ops = {
102 |     .read = msix_pba_mmio_read,
103 |     .write = msix_pba_mmio_write,
104 |     .endianness = DEVICE_LITTLE_ENDIAN,
105 |     .valid = {
106 |         .min_access_size = 4,
107 |         .max_access_size = 8,
108 |     },
109 |     .impl = {
110 |         .max_access_size = 4,
111 |     },
112 | };
113 | 
114 | static void riscv_iommu_sysdev_init_msi(RISCVIOMMUStateSys *s,
115 |                                         uint32_t n_vectors)
116 | {
117 |     RISCVIOMMUState *iommu = &s->iommu;
118 |     uint32_t table_size = n_vectors * PCI_MSIX_ENTRY_SIZE;
119 |     uint32_t table_offset = RISCV_IOMMU_REG_MSI_CONFIG;
120 |     uint32_t pba_size = QEMU_ALIGN_UP(n_vectors, 64) / 8;
121 |     uint32_t pba_offset = RISCV_IOMMU_REG_MSI_CONFIG + 256;
122 | 

hw/riscv/riscv-iommu-sys.c:136

136 | 
137 | static void riscv_iommu_sysdev_send_MSI(RISCVIOMMUStateSys *s,
138 |                                         uint32_t vector)
139 | {
140 |     uint8_t *table_entry = s->msix_table + vector * PCI_MSIX_ENTRY_SIZE;
141 |     uint64_t msi_addr = pci_get_quad(table_entry + PCI_MSIX_ENTRY_LOWER_ADDR);
142 |     uint32_t msi_data = pci_get_long(table_entry + PCI_MSIX_ENTRY_DATA);
143 |     MemTxResult result;
144 | 
145 |     address_space_stl_le(&address_space_memory, msi_addr,
146 |                          msi_data, MEMTXATTRS_UNSPECIFIED, &result);
147 | 
148 |     if (result == MEMTX_OK) {
149 |         trace_riscv_iommu_sys_msi_sent(vector, msi_addr, msi_data, result);
150 |     } else {
151 |         /* Record an access fault error in the fault queue */
152 |         struct riscv_iommu_fq_record ev = { 0 };
153 |         RISCVIOMMUState *iommu = &s->iommu;
154 | 
155 |         ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_CAUSE,
156 |                            RISCV_IOMMU_FQ_CAUSE_MSI_WR_FAULT);
157 |         ev.hdr = set_field(ev.hdr, RISCV_IOMMU_FQ_HDR_TTYPE,
158 |                            RISCV_IOMMU_FQ_TTYPE_UADDR_WR);
159 |         riscv_iommu_fault(iommu, &ev);
160 |     }
161 | }
162 | 
163 | static void riscv_iommu_sysdev_notify(RISCVIOMMUState *iommu,
164 |                                       unsigned vector)
165 | {
166 |     RISCVIOMMUStateSys *s = container_of(iommu, RISCVIOMMUStateSys, iommu);
167 |     uint32_t fctl =  riscv_iommu_reg_get32(iommu, RISCV_IOMMU_REG_FCTL);
168 | 
169 |     if (fctl & RISCV_IOMMU_FCTL_WSI) {
170 |         qemu_irq_pulse(s->irqs[vector]);
171 |         trace_riscv_iommu_sys_irq_sent(vector);
172 |         return;
173 |     }
174 | 
175 |     riscv_iommu_sysdev_send_MSI(s, vector);
176 | }
177 | 
178 | static void riscv_iommu_sys_realize(DeviceState *dev, Error **errp)
179 | {

Root cause analysis

The RISC-V IOMMU specification directly defines msi_vec_ctl.M: while it is one, the IOMMU is prohibited from sending that vector's message, and pending messages are generated after software clears it. Current code stores the vector-control word but never reads it in the send path, and it keeps no effective pending-message transition. The mapped PBA remaining empty is additional implementation evidence, but the bug does not depend on claiming that PCI MSI-X PBA semantics are part of the platform-device ABI.

The reasoning, step by step:

  1. Guest programs a valid MSI address/data entry and sets the vector mask bit.
  2. An IOMMU event invokes riscv_iommu_sysdev_send_MSI().
  3. The function ignores vector control and performs the memory write immediately.
  4. No effective pending-message state records the masked event.
  5. Unmasking cannot deliver a deferred event because table writes have no transition logic.

Worked example and boundary cases

Set vector M=1, generate one event, and observe whether an MSI is emitted. Clear M afterward: a pending event should be delivered then, not while masked. Use the RISC-V IOMMU MSI-vector rule as the oracle; the use of QEMU PCI-style table constants does not by itself import the entire PCI MSI-X contract.

Conditions needed to reach the problem

  • The platform IOMMU uses MSI rather than wired interrupt mode.
  • The guest masks one of the exposed vectors.
  • An event for that vector occurs while masked.

Expected or potential effect

  • The guest receives an interrupt it explicitly masked.
  • A masked event is not deferred to the later unmask transition.
  • Reset/migration cannot preserve pending-vector behavior that is never represented.

Expected behavior

A masked vector must not be sent immediately; it must be recorded pending and delivered after unmasking.

Steps to reproduce

The following validation design covers the expected and current-code results.

  1. use qtest to program one MSI table entry with a traceable address/data pair.
  2. Set the entry's vector-control mask bit and trigger the corresponding IOMMU notification.
  3. Assert that no MSI memory write occurs and inspect any implementation-visible pending state.
  4. Clear the mask and verify exactly one deferred MSI is sent.
  5. Repeat unmasked, across reset, and across migration if the device is migratable.

Expected result: A masked vector is not sent; its pending message is generated after the mask is cleared. Unmasked vectors are sent immediately.

Current behavior: The send path ignores vector control and no pending-to-unmask transition exists.

Suggested fix direction

Check msi_vec_ctl.M before every send, record pending state instead of sending while masked, and detect the 1->0 table-write transition to consume and send a pending message once. If QEMU retains the exposed PBA storage, keep it coherent with that internal state; define reset and VMState behavior consistently with the device's migration contract.

The fix should be covered by the following focused regression checks:

  • Masked, unmasked, and mask-to-unmask delivery.
  • Multiple events while masked follow the RISC-V IOMMU pending-message rule.
  • Per-vector independence across all five vectors.
  • Reset and migration state where supported.
  • Conformance with the RISC-V IOMMU mask rule independent of PCI MSI-X terminology.

References

Additional information

  • Source baseline: QEMU master at ff1d2d19d7e24893e2012d879f8e73077e17b9bd (checked 2026-09-05).