From 120d21cd685266966090ce8d6a7407407adc0feb Mon Sep 17 00:00:00 2001 From: tamnd <1218621+tamnd@users.noreply.github.com> Date: Wed, 14 Oct 2026 23:53:18 +0700 Subject: [PATCH] machine-arm: take iommu_platform on the virtio-mmio devices of virt [skip ci] --- crates/machine-arm/src/virt.rs | 44 ++++++++++++++++++++++++++------ crates/machine-arm/tests/virt.rs | 21 +++++++++++++++ crates/system/src/arm/devices.rs | 16 ++++++++---- 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/crates/machine-arm/src/virt.rs b/crates/machine-arm/src/virt.rs index 0cde95b..6b50284 100644 --- a/crates/machine-arm/src/virt.rs +++ b/crates/machine-arm/src/virt.rs @@ -49,12 +49,13 @@ //! [`VirtMachine::new`] builds the board and loads the kernel. Plug virtio devices with //! [`VirtMachine::attach_virtio`] (virtio-mmio) or [`VirtMachine::attach_virtio_pci`] (a PCI //! function on the root bus), and root ports with [`VirtMachine::attach_pcie_root_port`] and -//! functions behind them with [`VirtMachine::attach_virtio_pci_on`], then call [`VirtMachine::machine_done`], which finishes and -//! loads the device tree and resets the board. [`VirtMachine::create_vcpus`] then makes the -//! vCPUs on a [`Jit`], wired to the GIC, and resets them. On a guest reset request -//! ([`VirtMachine::take_request`]) the runner calls [`VirtMachine::system_reset`] and, on each -//! vCPU's thread, [`VirtMachine::reset_cpu`]. The runner also runs the timers of -//! [`VirtMachine::clock`]: the generic timers arm their deadlines there. +//! functions behind them with [`VirtMachine::attach_virtio_pci_on`], then call +//! [`VirtMachine::machine_done`], which finishes and loads the device tree and resets the +//! board. [`VirtMachine::create_vcpus`] then makes the vCPUs on a [`Jit`], wired to the GIC, +//! and resets them. On a guest reset request ([`VirtMachine::take_request`]) the runner calls +//! [`VirtMachine::system_reset`] and, on each vCPU's thread, [`VirtMachine::reset_cpu`]. The +//! runner also runs the timers of [`VirtMachine::clock`]: the generic timers arm their +//! deadlines there. //! //! # Not modelled yet //! @@ -1690,11 +1691,20 @@ impl VirtMachine { /// does: the guest probes the transports from the top, so the first device plugged is /// found first. Returns the transport index. pub fn attach_virtio(&self, class: Box) -> Result { + self.attach_virtio_with(class, false) + } + + /// [`VirtMachine::attach_virtio`] with the `iommu_platform` property of the virtio device. + pub fn attach_virtio_with( + &self, + class: Box, + iommu_platform: bool, + ) -> Result { let Some(index) = (0..self.virtio.len()).rev().find(|&i| !self.virtio[i].is_plugged()) else { return Err("No 'virtio-bus' bus found for device".to_string()); }; - self.attach_virtio_at(index, class, VIRTIO_MMIO_FORCE_LEGACY_DEFAULT)?; + self.attach_virtio_at_with(index, class, VIRTIO_MMIO_FORCE_LEGACY_DEFAULT, iommu_platform)?; Ok(index) } @@ -1705,6 +1715,21 @@ impl VirtMachine { index: usize, class: Box, force_legacy: bool, + ) -> Result<(), String> { + self.attach_virtio_at_with(index, class, force_legacy, false) + } + + /// [`VirtMachine::attach_virtio_at`] with the `iommu_platform` property of the virtio + /// device. A virtio-mmio transport has no IOMMU in front of it, so as in + /// `virtio_bus_device_plugged()` without `get_dma_as` the device offers + /// `VIRTIO_F_IOMMU_PLATFORM` and does its DMA in system memory. A legacy transport only + /// shows the low 32 feature bits, so the guest does not see it there. + pub fn attach_virtio_at_with( + &self, + index: usize, + class: Box, + force_legacy: bool, + iommu_platform: bool, ) -> Result<(), String> { let force_legacy = force_legacy && self.virtio_force_legacy; if self.done { @@ -1718,7 +1743,10 @@ impl VirtMachine { } let memory: Arc = Arc::new(WeakGuestMemory(Arc::downgrade(&self.memory_as))); - let backend = VirtioBackend::new(class, memory).map_err(err)?; + let mut backend = VirtioBackend::new(class, memory).map_err(err)?; + if iommu_platform { + backend.vdev_mut().set_host_feature(VIRTIO_F_IOMMU_PLATFORM, true); + } let t = VirtioMmio::new(Some(backend), force_legacy).map_err(err)?; t.irq().connect(slot.gsi.clone()); *slot.transport.write().unwrap_or_else(PoisonError::into_inner) = Arc::new(t); diff --git a/crates/machine-arm/tests/virt.rs b/crates/machine-arm/tests/virt.rs index 67f7308..85e469a 100644 --- a/crates/machine-arm/tests/virt.rs +++ b/crates/machine-arm/tests/virt.rs @@ -779,6 +779,27 @@ fn virtio_plugging() { ); } +#[test] +fn virtio_mmio_iommu_platform() { + let rng = + || Box::new(VirtioRng::new(Box::new(RandomFile::default()), VirtioRngConf::default())); + let mut m = VirtMachine::new(VirtConfig::default()).unwrap(); + assert_eq!(m.attach_virtio_with(rng(), true).unwrap(), 31); + m.attach_virtio_at_with(30, rng(), false, true).unwrap(); + m.attach_virtio_at_with(29, rng(), false, false).unwrap(); + m.machine_done().unwrap(); + // The high word of DEVICE_FEATURES: VIRTIO_F_IOMMU_PLATFORM is bit 33. The legacy + // transport shows nothing there, as in QEMU. + let high = |i: u64| { + let base = VIRT_MMIO + i * 0x200; + w(&m, base + 0x14, 4, 1); + r32(&m, base + 0x10) + }; + assert_eq!(high(31), 0); + assert_eq!(high(30) & 3, 3); + assert_eq!(high(29) & 3, 1); +} + #[test] fn pcie_host() { let rng = diff --git a/crates/system/src/arm/devices.rs b/crates/system/src/arm/devices.rs index 377642f..dc47404 100644 --- a/crates/system/src/arm/devices.rs +++ b/crates/system/src/arm/devices.rs @@ -15,7 +15,8 @@ //! the root bus `pcie.0` (`virtio-*-pci`) or on a virtio-mmio transport (`virtio-*-device`). //! The other virtio types and `loader` fail with "... is not supported with this machine by //! ruvm yet". The PCI types take `addr`, `bus=pcie.0`, `disable-legacy`, -//! `disable-modern` and `vectors`; the virtio-mmio ones take `bus=virtio-mmio-bus.`. +//! `disable-modern` and `vectors`; the virtio-mmio ones take `bus=virtio-mmio-bus.`. Both +//! take `iommu_platform`. //! On virt, `-device arm-smmuv3,primary-bus=pcie.0` is the SMMUv3 on the platform bus that //! translates the functions plugged after it. //! On virt, `-device pcie-root-port` with an `id` puts a root port on `pcie.0`, and the PCI @@ -119,7 +120,8 @@ pub(crate) struct Plug { devfn: Option, /// `bus=virtio-mmio-bus.` of a virtio-mmio device. mmio_bus: Option, - /// `iommu_platform` of a PCI function: its DMA goes through the SMMU. + /// `iommu_platform`: the DMA of a PCI function goes through the SMMU. A virtio-mmio + /// device only offers `VIRTIO_F_IOMMU_PLATFORM`, since no IOMMU is in front of it. iommu_platform: bool, /// The properties of a virtio-iommu. iommu: Option, @@ -280,7 +282,7 @@ fn plan_virtio( "disable-modern" if pci => { plug.props.disable_modern = prop_bool(k, v).map_err(|_| bad_value(k, v))?; } - "iommu_platform" if pci => { + "iommu_platform" => { plug.iommu_platform = prop_bool(k, v).map_err(|_| bad_value(k, v))?; } "vectors" if pci => { @@ -933,12 +935,14 @@ pub(crate) fn plug( Some(VirtioHandle::Pci(dev)) } (Target::Virt(board), Transport::Mmio, Some(n)) => { - board.attach_virtio_at(n, class, VIRTIO_MMIO_FORCE_LEGACY_DEFAULT).map_err(at)?; + board + .attach_virtio_at_with(n, class, VIRTIO_MMIO_FORCE_LEGACY_DEFAULT, iommu) + .map_err(at)?; board.virtio_transport(n).map(VirtioHandle::Mmio) } (Target::Virt(board), Transport::Mmio, None) => { let n = board - .attach_virtio(class) + .attach_virtio_with(class, iommu) .map_err(|e| at(format!("{e} '{}'", plug.typename)))?; board.virtio_transport(n).map(VirtioHandle::Mmio) } @@ -1049,6 +1053,8 @@ mod tests { assert_eq!(p.virtio[0].devfn, Some(0xff)); let p = plan(V, &[], &[dev("virtio-rng-pci,iommu_platform=on")]).unwrap(); assert!(p.virtio[0].iommu_platform); + let p = plan(V, &[], &[dev("virtio-rng-device,iommu_platform=on")]).unwrap(); + assert!(p.virtio[0].iommu_platform); let err = |args: &[&str]| { let devs: Vec<_> = args.iter().map(|a| dev(a)).collect();