From b918220b499486664eac9983e1fbbdc34e693049 Mon Sep 17 00:00:00 2001 From: Sebastien Boeuf Date: Wed, 2 Oct 2019 13:57:20 -0700 Subject: [PATCH] vmm: Support virtio-pci devices attached to a virtual IOMMU This commit is the glue between the virtio-pci devices attached to the vIOMMU, and the IORT ACPI table exposing them to the guest as sitting behind this vIOMMU. An important thing is the trait implementation provided to the virtio vrings for each device attached to the vIOMMU, as they need to perform proper address translation before they can access the buffers. Signed-off-by: Sebastien Boeuf --- pci/src/bus.rs | 4 + vmm/src/device_manager.rs | 212 +++++++++++++++++++++++++++++++------- vmm/src/vm.rs | 4 +- 3 files changed, 179 insertions(+), 41 deletions(-) diff --git a/pci/src/bus.rs b/pci/src/bus.rs index 391af7fb6..dcb32f8bb 100644 --- a/pci/src/bus.rs +++ b/pci/src/bus.rs @@ -114,6 +114,10 @@ impl PciBus { self.devices.push(device); Ok(()) } + + pub fn next_device_id(&self) -> u32 { + self.devices.len() as u32 + } } pub struct PciConfigIo { diff --git a/vmm/src/device_manager.rs b/vmm/src/device_manager.rs index 5d5c7e9ca..c67e9fc4b 100644 --- a/vmm/src/device_manager.rs +++ b/vmm/src/device_manager.rs @@ -44,7 +44,9 @@ use vm_memory::{Address, GuestMemoryMmap, GuestUsize}; #[cfg(feature = "pci_support")] use vm_virtio::transport::VirtioPciDevice; use vm_virtio::vhost_user::VhostUserConfig; -use vm_virtio::{VirtioSharedMemory, VirtioSharedMemoryList}; +#[cfg(feature = "pci_support")] +use vm_virtio::{DmaRemapping, VirtioIommuRemapping}; +use vm_virtio::{IommuMapping, VirtioSharedMemory, VirtioSharedMemoryList}; use vmm_sys_util::eventfd::EventFd; #[cfg(feature = "mmio_support")] @@ -92,6 +94,9 @@ pub enum DeviceManagerError { /// Cannot create virtio-vsock backend CreateVsockBackend(vm_virtio::vsock::VsockUnixError), + /// Cannot create virtio-iommu device + CreateVirtioIommu(io::Error), + /// Failed parsing disk image format DetectImageType(qcow::Error), @@ -285,6 +290,10 @@ pub struct DeviceManager { // Things to be added to the commandline (i.e. for virtio-mmio) cmdline_additions: Vec, + + // Virtual IOMMU ID along with the list of device IDs attached to the + // virtual IOMMU. This is useful for filling the ACPI IORT table. + virt_iommu: Option<(u32, Vec)>, } impl DeviceManager { @@ -399,7 +408,7 @@ impl DeviceManager { .map_err(DeviceManagerError::BusError)?; } - let mut virtio_devices: Vec> = Vec::new(); + let mut virtio_devices: Vec<(Box, bool)> = Vec::new(); // Create serial and virtio-console let console_writer: Option> = @@ -415,10 +424,12 @@ impl DeviceManager { let (col, row) = get_win_size(); let console_input = if let Some(writer) = console_writer { let (virtio_console_device, console_input) = - vm_virtio::Console::new(writer, col, row, false) + vm_virtio::Console::new(writer, col, row, vm_info.vm_cfg.console.iommu) .map_err(DeviceManagerError::CreateVirtioConsole)?; - virtio_devices - .push(Box::new(virtio_console_device) as Box); + virtio_devices.push(( + Box::new(virtio_console_device) as Box, + false, + )); Some(console_input) } else { None @@ -443,14 +454,55 @@ impl DeviceManager { #[allow(unused_mut)] let mut cmdline_additions = Vec::new(); + #[allow(unused_mut)] + let mut virt_iommu: Option<(u32, Vec)> = None; + if cfg!(feature = "pci_support") { #[cfg(feature = "pci_support")] { let pci_root = PciRoot::new(None); let mut pci_bus = PciBus::new(pci_root); - for device in virtio_devices { + let (iommu_mapping, iommu_id) = if vm_info.vm_cfg.iommu { + let (iommu_device, mapping) = + vm_virtio::Iommu::new().map_err(DeviceManagerError::CreateVirtioIommu)?; + + // We need to shift the device id since the 3 first bits + // are dedicated to the PCI function, and we know we don't + // do multifunction. Also, because we only support one PCI + // bus, the bus 0, we don't need to add anything to the + // global device ID. + let iommu_id = pci_bus.next_device_id() << 3; + + // Because we determined the virtio-iommu b/d/f, we have to + // add the device to the PCI topology now. Otherwise, the + // b/d/f won't match the virtio-iommu device as expected. DeviceManager::add_virtio_pci_device( + Box::new(iommu_device), + vm_info.memory, + allocator, + vm_info.vm_fd, + &mut pci_bus, + &mut buses, + &interrupt_info, + &None, + )?; + + (Some(mapping), Some(iommu_id)) + } else { + (None, None) + }; + + let mut iommu_attached_devices = Vec::new(); + + for (device, iommu_attached) in virtio_devices { + let mapping: &Option> = if iommu_attached { + &iommu_mapping + } else { + &None + }; + + let virtio_iommu_attach_dev = DeviceManager::add_virtio_pci_device( device, vm_info.memory, allocator, @@ -458,7 +510,16 @@ impl DeviceManager { &mut pci_bus, &mut buses, &interrupt_info, + mapping, )?; + + if let Some(dev_id) = virtio_iommu_attach_dev { + iommu_attached_devices.push(dev_id); + } + } + + if let Some(iommu_id) = iommu_id { + virt_iommu = Some((iommu_id, iommu_attached_devices)); } DeviceManager::add_vfio_devices( @@ -486,7 +547,7 @@ impl DeviceManager { } else if cfg!(feature = "mmio_support") { #[cfg(feature = "mmio_support")] { - for device in virtio_devices { + for (device, _) in virtio_devices { if let Some(addr) = allocator.allocate_mmio_addresses(None, MMIO_LEN, Some(MMIO_LEN)) { @@ -514,6 +575,7 @@ impl DeviceManager { ioapic, mmap_regions, cmdline_additions, + virt_iommu, }) } @@ -522,8 +584,8 @@ impl DeviceManager { allocator: &mut SystemAllocator, mut mem_slots: &mut u32, mmap_regions: &mut Vec<(*mut libc::c_void, usize)>, - ) -> DeviceManagerResult>> { - let mut devices: Vec> = Vec::new(); + ) -> DeviceManagerResult, bool)>> { + let mut devices: Vec<(Box, bool)> = Vec::new(); // Create "standard" virtio devices (net/block/rng) devices.append(&mut DeviceManager::make_virtio_block_devices(vm_info)?); @@ -564,7 +626,7 @@ impl DeviceManager { fn make_virtio_block_devices( vm_info: &VmInfo, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); if let Some(disk_list_cfg) = &vm_info.vm_cfg.disks { @@ -581,22 +643,30 @@ impl DeviceManager { let block = match image_type { ImageType::Raw => { let raw_img = vm_virtio::RawFile::new(raw_img); - let dev = - vm_virtio::Block::new(raw_img, disk_cfg.path.clone(), false, false) - .map_err(DeviceManagerError::CreateVirtioBlock)?; + let dev = vm_virtio::Block::new( + raw_img, + disk_cfg.path.clone(), + false, + disk_cfg.iommu, + ) + .map_err(DeviceManagerError::CreateVirtioBlock)?; Box::new(dev) as Box } ImageType::Qcow2 => { let qcow_img = QcowFile::from(raw_img) .map_err(DeviceManagerError::QcowDeviceCreate)?; - let dev = - vm_virtio::Block::new(qcow_img, disk_cfg.path.clone(), false, false) - .map_err(DeviceManagerError::CreateVirtioBlock)?; + let dev = vm_virtio::Block::new( + qcow_img, + disk_cfg.path.clone(), + false, + disk_cfg.iommu, + ) + .map_err(DeviceManagerError::CreateVirtioBlock)?; Box::new(dev) as Box } }; - devices.push(block); + devices.push((block, disk_cfg.iommu)); } } @@ -605,7 +675,7 @@ impl DeviceManager { fn make_virtio_net_devices( vm_info: &VmInfo, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add virtio-net if required @@ -613,14 +683,17 @@ impl DeviceManager { for net_cfg in net_list_cfg.iter() { let virtio_net_device = if let Some(ref tap_if_name) = net_cfg.tap { let tap = Tap::open_named(tap_if_name).map_err(DeviceManagerError::OpenTap)?; - vm_virtio::Net::new_with_tap(tap, Some(&net_cfg.mac), false) + vm_virtio::Net::new_with_tap(tap, Some(&net_cfg.mac), net_cfg.iommu) .map_err(DeviceManagerError::CreateVirtioNet)? } else { - vm_virtio::Net::new(net_cfg.ip, net_cfg.mask, Some(&net_cfg.mac), false) + vm_virtio::Net::new(net_cfg.ip, net_cfg.mask, Some(&net_cfg.mac), net_cfg.iommu) .map_err(DeviceManagerError::CreateVirtioNet)? }; - devices.push(Box::new(virtio_net_device) as Box); + devices.push(( + Box::new(virtio_net_device) as Box, + net_cfg.iommu, + )); } } @@ -629,14 +702,17 @@ impl DeviceManager { fn make_virtio_rng_devices( vm_info: &VmInfo, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add virtio-rng if required if let Some(rng_path) = vm_info.vm_cfg.rng.src.to_str() { - let virtio_rng_device = vm_virtio::Rng::new(rng_path, false) + let virtio_rng_device = vm_virtio::Rng::new(rng_path, vm_info.vm_cfg.rng.iommu) .map_err(DeviceManagerError::CreateVirtioRng)?; - devices.push(Box::new(virtio_rng_device) as Box); + devices.push(( + Box::new(virtio_rng_device) as Box, + false, + )); } Ok(devices) @@ -647,7 +723,7 @@ impl DeviceManager { allocator: &mut SystemAllocator, mem_slots: &mut u32, mmap_regions: &mut Vec<(*mut libc::c_void, usize)>, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add virtio-fs if required if let Some(fs_list_cfg) = &vm_info.vm_cfg.fs { @@ -718,7 +794,10 @@ impl DeviceManager { ) .map_err(DeviceManagerError::CreateVirtioFs)?; - devices.push(Box::new(virtio_fs_device) as Box); + devices.push(( + Box::new(virtio_fs_device) as Box, + false, + )); } } } @@ -731,7 +810,7 @@ impl DeviceManager { allocator: &mut SystemAllocator, mem_slots: &mut u32, mmap_regions: &mut Vec<(*mut libc::c_void, usize)>, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add virtio-pmem if required if let Some(pmem_list_cfg) = &vm_info.vm_cfg.pmem { @@ -789,10 +868,13 @@ impl DeviceManager { *mem_slots += 1; let virtio_pmem_device = - vm_virtio::Pmem::new(file, pmem_guest_addr, size as GuestUsize, false) + vm_virtio::Pmem::new(file, pmem_guest_addr, size as GuestUsize, pmem_cfg.iommu) .map_err(DeviceManagerError::CreateVirtioPmem)?; - devices.push(Box::new(virtio_pmem_device) as Box); + devices.push(( + Box::new(virtio_pmem_device) as Box, + false, + )); } } @@ -801,7 +883,7 @@ impl DeviceManager { fn make_virtio_vhost_user_net_devices( vm_info: &VmInfo, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add vhost-user-net if required if let Some(vhost_user_net_list_cfg) = &vm_info.vm_cfg.vhost_user_net { @@ -815,7 +897,10 @@ impl DeviceManager { vm_virtio::vhost_user::Net::new(vhost_user_net_cfg.mac, vu_cfg) .map_err(DeviceManagerError::CreateVhostUserNet)?; - devices.push(Box::new(vhost_user_net_device) as Box); + devices.push(( + Box::new(vhost_user_net_device) as Box, + false, + )); } } @@ -824,7 +909,7 @@ impl DeviceManager { fn make_virtio_vhost_user_blk_devices( vm_info: &VmInfo, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add vhost-user-blk if required if let Some(vhost_user_blk_list_cfg) = &vm_info.vm_cfg.vhost_user_blk { @@ -838,7 +923,10 @@ impl DeviceManager { vm_virtio::vhost_user::Blk::new(vhost_user_blk_cfg.wce, vu_cfg) .map_err(DeviceManagerError::CreateVhostUserBlk)?; - devices.push(Box::new(vhost_user_blk_device) as Box); + devices.push(( + Box::new(vhost_user_blk_device) as Box, + false, + )); } } @@ -847,7 +935,7 @@ impl DeviceManager { fn make_virtio_vsock_devices( vm_info: &VmInfo, - ) -> DeviceManagerResult>> { + ) -> DeviceManagerResult, bool)>> { let mut devices = Vec::new(); // Add vsock if required if let Some(vsock_list_cfg) = &vm_info.vm_cfg.vsock { @@ -860,10 +948,13 @@ impl DeviceManager { vm_virtio::vsock::VsockUnixBackend::new(vsock_cfg.cid, socket_path.to_string()) .map_err(DeviceManagerError::CreateVsockBackend)?; - let vsock_device = vm_virtio::Vsock::new(vsock_cfg.cid, backend, false) + let vsock_device = vm_virtio::Vsock::new(vsock_cfg.cid, backend, vsock_cfg.iommu) .map_err(DeviceManagerError::CreateVirtioVsock)?; - devices.push(Box::new(vsock_device) as Box); + devices.push(( + Box::new(vsock_device) as Box, + false, + )); } } @@ -925,6 +1016,7 @@ impl DeviceManager { } #[cfg(feature = "pci_support")] + #[allow(clippy::too_many_arguments)] fn add_virtio_pci_device( virtio_device: Box, memory: &Arc>, @@ -933,7 +1025,8 @@ impl DeviceManager { pci: &mut PciBus, buses: &mut BusInfo, interrupt_info: &InterruptInfo, - ) -> DeviceManagerResult<()> { + iommu_mapping: &Option>, + ) -> DeviceManagerResult> { let msix_num = if interrupt_info._msi_capable { // Allows support for one MSI-X vector per queue. It also adds 1 // as we need to take into account the dedicated vector to notify @@ -943,8 +1036,35 @@ impl DeviceManager { 0 }; + // We need to shift the device id since the 3 first bits are dedicated + // to the PCI function, and we know we don't do multifunction. + // Also, because we only support one PCI bus, the bus 0, we don't need + // to add anything to the global device ID. + let dev_id = pci.next_device_id() << 3; + + // Create the callback from the implementation of the DmaRemapping + // trait. The point with the callback is to simplify the code as we + // know about the device ID from this point. + let iommu_mapping_cb: Option> = + if let Some(mapping) = iommu_mapping { + let mapping_clone = mapping.clone(); + Some(Arc::new(Box::new(move |addr: u64| { + mapping_clone.translate(dev_id, addr).map_err(|e| { + std::io::Error::new( + std::io::ErrorKind::Other, + format!( + "failed to translate addr 0x{:x} for device 00:{:02x}.0 {}", + addr, dev_id, e + ), + ) + }) + }) as VirtioIommuRemapping)) + } else { + None + }; + let mut virtio_pci_device = - VirtioPciDevice::new(memory.clone(), virtio_device, msix_num, None) + VirtioPciDevice::new(memory.clone(), virtio_device, msix_num, iommu_mapping_cb) .map_err(DeviceManagerError::VirtioDevice)?; let bars = virtio_pci_device @@ -1037,7 +1157,13 @@ impl DeviceManager { ) .map_err(DeviceManagerError::AddPciDevice)?; - Ok(()) + let ret = if iommu_mapping.is_some() { + Some(dev_id) + } else { + None + }; + + Ok(ret) } #[allow(clippy::too_many_arguments)] @@ -1116,6 +1242,14 @@ impl DeviceManager { pub fn cmdline_additions(&self) -> &[String] { self.cmdline_additions.as_slice() } + + pub fn virt_iommu(&self) -> Option<(u32, &[u32])> { + if let Some((iommu_id, dev_ids)) = self.virt_iommu.as_ref() { + Some((*iommu_id, dev_ids.as_slice())) + } else { + None + } + } } impl Drop for DeviceManager { diff --git a/vmm/src/vm.rs b/vmm/src/vm.rs index ddaea86da..b08decfd0 100755 --- a/vmm/src/vm.rs +++ b/vmm/src/vm.rs @@ -745,7 +745,7 @@ impl Vm { Some(hdr), self.config.serial.mode != ConsoleOutputMode::Off, end_of_range, - None, + self.devices.virt_iommu(), ) .map_err(|_| Error::CmdLine)?; @@ -766,7 +766,7 @@ impl Vm { None, self.config.serial.mode != ConsoleOutputMode::Off, end_of_range, - None, + self.devices.virt_iommu(), ) .map_err(|_| Error::CmdLine)?;