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
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:
- Guest programs a valid MSI address/data entry and sets the vector mask bit.
- An IOMMU event invokes
riscv_iommu_sysdev_send_MSI(). - The function ignores vector control and performs the memory write immediately.
- No effective pending-message state records the masked event.
- 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.
- use qtest to program one MSI table entry with a traceable address/data pair.
- Set the entry's vector-control mask bit and trigger the corresponding IOMMU notification.
- Assert that no MSI memory write occurs and inspect any implementation-visible pending state.
- Clear the mask and verify exactly one deferred MSI is sent.
- 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
- QEMU source baseline: ff1d2d19
- RISC-V IOMMU MSI configuration-table definition: https://github.com/riscv-non-isa/riscv-iommu/blob/main/src/iommu_registers.adoc
- QEMU RISC-V IOMMU documentation source: https://gitlab.com/qemu-project/qemu/-/blob/master/docs/specs/riscv-iommu.rst
Additional information
- Source baseline: QEMU
masteratff1d2d19d7e24893e2012d879f8e73077e17b9bd(checked 2026-09-05).