Skip to content

hw-iommu: unmap notifiers on the SMMUv3 - #343

Merged
tamnd merged 1 commit into
mainfrom
smmuv3-notifiers
Oct 14, 2026
Merged

tamnd merged 1 commit into
mainfrom
smmuv3-notifiers

Conversation

@tamnd

@tamnd tamnd commented Oct 14, 2026 •

Copy link
Copy Markdown
Owner

Part of #9.

What

IOMMU notifiers on the SMMUv3, on the notifier API from #330, as in QEMU 11.1's hw/arm/smmuv3.c and smmu-common.c.

  • The IOMMU region of each device behind the SMMU now has a notifier list. smmuv3_notify_flag_changed() is ported: a notifier asking for MAP or DEVIOTLB_UNMAP events is refused with QEMU's message, and the device goes on or off devices_with_notifiers as its first notifier comes or its last one goes.
  • The TLB invalidation commands send unmap events, as smmuv3_inv_notifiers_iova(), smmuv3_notify_iova() and smmu_inv_notifiers_all() do:
    • TLBI_NH_VA, TLBI_NH_VAA and TLBI_S2_IPA unmap their range. Without a range (TG = 0) that is one page of the device's granule, and only when the ASID and VMID match its configuration. A range is split into the same powers of two as the IOTLB invalidation.
    • With nested translation a stage 2 invalidation unmaps nothing, since only stage 1 addresses are IOVAs.
    • TLBI_NH_ASID, TLBI_NSNH_ALL, TLBI_NH_ALL without stage 2, and TLBI_S12_VMALL unmap the whole range of each notifier. TLBI_NH_ALL with stage 2 sends nothing, as in QEMU.
  • The stream ID of a device comes from its bus number, which the PCI side holds, so the stream IDs of the devices with notifiers are worked out before the SMMU state is locked. The unmaps go out once the lock is dropped and before the CMD_SYNC interrupt, the same way intel-iommu defers its events.

Replay

There is no SMMUv3 replay, as in QEMU. hw/arm/smmuv3.c has no replay callback. The only callers of memory_region_iommu_replay() are VFIO and vhost-vdpa, and both register MAP notifiers, which the SMMUv3 refuses. So the default IommuOps::replay of #330, which does nothing, is the right one here.

What it changes for guests

Nothing in ruvm registers an unmap-only notifier behind the SMMU yet: vfio-pci is on q35 only, and it needs MAP anyway. So this is the device model side, tested at the unit level. To check that the translation path still works, the root port boot from #336 ran on QEMU 11.1 and on this build: an Alpine 6.12 guest with -M virt,iommu=smmuv3,gic-version=3 -cpu max -m 1G -smp 1 and a virtio-blk-pci with iommu_platform=on behind a pcie-root-port. It takes the md5 of /dev/vda, writes a sector, drops caches, reads it back and takes the md5 again.

Boot QEMU 11.1 this build
DT md5 95934ad2... then 269d53a2..., sector read back the same
ACPI (edk2) md5 95934ad2... then 269d53a2..., sector read back the same, and the same IORT bytes

The host image md5 after the run was 269d53a2fae97c0c7de47a8a3d6776b2 on all four boots. The guest's PCI addresses differ by the default NIC (a virtio-net at 00:01.0), which ruvm does not add. The ruvm boots used a release build of this diff on e7c7db9; #342, which came in after, migrates the ACPI GED and does not touch hw-iommu.

Not done here

  • RMR, ATS, secure state and VMState come next.
  • Accelerated nested mode (accel=on, CMDQV) needs host SMMU hardware we do not have, so it comes last.

Tests

  • notifiers_take_unmap_only: MAP and DEVIOTLB_UNMAP notifiers are refused with QEMU's messages and leave nothing on the list. An UNMAP notifier goes on the list and comes off it again.
  • stage1_invalidations_unmap_for_the_notifiers: one page for a matching ASID and nothing for another, a range split into its powers of two, whole-range unmaps for TLBI_NH_ASID and TLBI_NSNH_ALL, nothing for CFGI_STE, and nothing once the notifier is gone.
  • stage2_invalidations_unmap_for_the_notifiers: one page for a matching VMID and nothing for another, nothing for TLBI_NH_ALL with stage 2, a whole-range unmap for TLBI_S12_VMALL, and with nesting nothing for TLBI_S2_IPA but a page for TLBI_NH_VA.
  • On 8fb1fee plus this diff, on Linux x86_64: cargo fmt --check -p ruvm-hw-iommu; clippy -D warnings on hw-iommu, machine-arm and system; cargo check --workspace --all-targets; cargo test -p ruvm-system --lib (113 passed); the tests of hw-iommu, mem, machine-arm, system, hw-pci and hw-virtio, and the linux-user x86 suite (user-common, linux-user, jit-x86_64, jit-core), 770 passed and 0 failed in all; the provenance, unsafe-audit, layers and style xtasks; cargo check --target x86_64-pc-windows-msvc on hw-iommu, machine-arm and system with no warnings.

@tamnd tamnd added kind/feature It should do something it does not. area/devices Device models that are not virtio. labels Oct 14, 2026
@tamnd
tamnd merged commit 259922a into main Oct 14, 2026
@tamnd
tamnd deleted the smmuv3-notifiers branch October 14, 2026 18:25
tamnd added a commit that referenced this pull request Oct 15, 2026
…handler (#345)

Part of #9.

vfio-pci now resets a device the way QEMU 11.1 does when the device has
no FLR. `vfio_pci_reset()` tries FLR first. Without it, it tries a bus
or slot reset through `VFIO_DEVICE_PCI_HOT_RESET`, and falls back to the
PM reset of `VFIO_DEVICE_RESET` only when nothing else worked. On a
system reset, `vfio_device_reset_handler()` runs before any device is
reset and resets together the devices in use that share a bus.

What is in it:

- ruvm-sys: `VFIO_DEVICE_GET_PCI_HOT_RESET_INFO`, which reports how many
devices a hot reset takes along even when it fails with `ENOSPC`, and
`VFIO_DEVICE_PCI_HOT_RESET` with the group descriptors as proof of
ownership. The group descriptor is now reachable through `AsFd`.
- vfio-pci:
- `vfio_pci_pre_reset()` and `vfio_pci_post_reset()`. The first stops
interrupts and DMA, clears INTx Disable and brings the device to D0. The
second turns INTx back on and clears the BARs.
- `vfio_pci_compute_needs_reset()` and `vfio_legacy_pci_hot_reset()`,
with QEMU's errors for a device with no reset mechanism and for a
dependent group that is not owned.
- A list of the realized devices, so a reset can find the other devices
in use that it takes along.
- q35: early reset hooks, which run before any device is reset. That is
where QEMU's `qemu_register_reset()` handlers run, since the system bus
reset is registered only after every `-device` is realized. The first
vfio-pci registers the vfio reset handler there.
- vfio-pci no longer resets the device at realize. QEMU doesn't, and the
first system reset, which `machine_done()` runs once every device is
plugged, resets it on both the TCG and KVM paths. ruvm has no
`device_add`, so there is no hotplug path that would need its own reset
after realize.

Checked:

On server2, on c64ade1, the branch with main up to #343 merged in:

- clippy with `-D warnings` and `--all-targets` for ruvm-mem,
ruvm-hw-iommu, ruvm-sys, ruvm-hw-pci, ruvm-hw-vfio, ruvm-machine-x86,
ruvm-machine-arm and ruvm-system.
- clippy for x86_64-pc-windows-gnu and aarch64-apple-darwin on the same
crates.
- tests of ruvm-mem, ruvm-hw-iommu, ruvm-sys, ruvm-hw-pci and
ruvm-hw-vfio, and the ruvm-machine-x86, ruvm-machine-arm and ruvm-system
libs (40, 36 and 113). A new ruvm-sys test decodes the dependent devices
of a hot reset info reply. ruvm-machine-x86 has 40 tests, not 43 as at
ce49dd2, because #340 and #342 removed three.
- xtask unsafe-audit, provenance, layers and style.
- An L1 compare on the release build of c64ade1. L1 is QEMU 11.1 with an
e1000e behind a pcie-root-port at 01:00.0, bound to vfio-pci. L2 is run
with `-device vfio-pci,host=0000:01:00.0,addr=2` under qtest and strace,
as QEMU 11.1, ruvm and QEMU 11.1 again, one at a time. qtest sets RAL0
of the e1000e, resets the system through 0xcf9, waits until BAR0 reads
0, maps BAR0 again and reads RAL0 and RAH0. The runs waited until the
host load was under 16 and ran at a host load between 15 and 21.

The ioctls on the device descriptor are the same in all three, at start
and again after the 0xcf9 reset:

| Step | Ioctl | Result |
| --- | --- | --- |
| reset handler | GET_PCI_HOT_RESET_INFO, no room | ENOSPC |
| | GET_PCI_HOT_RESET_INFO | 0, no other device in use, so nothing to do
|
| device reset | GET_PCI_HOT_RESET_INFO, no room | ENOSPC |
| | GET_PCI_HOT_RESET_INFO | 0 |
| | PCI_HOT_RESET | ENOTTY |
| | DEVICE_RESET | 0 |

All the answers are the same in the three runs. RAL0 reads 0x12005452,
reads back 0xa1b2c3d4 after the write, and reads 0x12005452 again after
the reset, with RAH0 0x80005634. So the device really was reset. ruvm
took two BAR0 polls to see the reset where QEMU took one.

Before this change, ruvm reset the device at realize too, which added
four calls at start (ENOSPC, 0, ENOTTY, 0) before the sequence above.
The compare that found it ran on 919a7c1.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/devices Device models that are not virtio. kind/feature It should do something it does not.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant