Skip to content

Commit e9409aa

Browse files
committed
vm: implement DeviceRelocation for Vm
Implement PCI device relocation logic for Vm type. Up until now, we were only implementing it in a dummy way. Signed-off-by: Babis Chalios <[email protected]>
1 parent c15bc25 commit e9409aa

File tree

1 file changed

+287
-8
lines changed

1 file changed

+287
-8
lines changed

src/vmm/src/vstate/vm.rs

Lines changed: 287 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,19 @@ use kvm_bindings::{
1818
KVM_IRQ_ROUTING_IRQCHIP, KVM_IRQ_ROUTING_MSI, KVM_MEM_LOG_DIRTY_PAGES, KVM_MSI_VALID_DEVID,
1919
KvmIrqRouting, kvm_irq_routing_entry, kvm_userspace_memory_region,
2020
};
21-
use kvm_ioctls::VmFd;
21+
use kvm_ioctls::{IoEventAddress, NoDatamatch, VmFd};
2222
use log::debug;
23-
use pci::DeviceRelocation;
23+
#[cfg(target_arch = "aarch64")]
24+
use log::error;
25+
use pci::{DeviceRelocation, PciBarRegionType};
2426
use serde::{Deserialize, Serialize};
27+
use vm_allocator::RangeInclusive;
2528
use vm_device::interrupt::{InterruptSourceGroup, MsiIrqSourceConfig};
2629
use vmm_sys_util::errno;
2730
use vmm_sys_util::eventfd::EventFd;
2831

2932
pub use crate::arch::{ArchVm as Vm, ArchVmError, VmState};
33+
use crate::devices::virtio::transport::pci::device::VirtioPciDevice;
3034
use crate::logger::info;
3135
use crate::persist::CreateSnapshotError;
3236
use crate::snapshot::Persist;
@@ -622,23 +626,107 @@ impl Vm {
622626
impl DeviceRelocation for Vm {
623627
fn move_bar(
624628
&self,
625-
_old_base: u64,
626-
_new_base: u64,
627-
_len: u64,
628-
_pci_dev: &mut dyn pci::PciDevice,
629-
_region_type: pci::PciBarRegionType,
629+
old_base: u64,
630+
new_base: u64,
631+
len: u64,
632+
pci_dev: &mut dyn pci::PciDevice,
633+
region_type: pci::PciBarRegionType,
630634
) -> Result<(), std::io::Error> {
631-
todo!()
635+
debug!("pci: moving BAR from {old_base:#x}:{len:#x} to {new_base:#x}:{len:#x}");
636+
match region_type {
637+
PciBarRegionType::IoRegion => {
638+
#[cfg(target_arch = "x86_64")]
639+
// We do not allocate IO addresses, we just hard-code them, no need to handle
640+
// (re)allocations. Just update PIO bus
641+
self.pio_bus
642+
.update_range(old_base, len, new_base, len)
643+
.map_err(std::io::Error::other)?;
644+
645+
#[cfg(target_arch = "aarch64")]
646+
return Err(std::io::Error::other(
647+
"pci: IO regions not supported on Aarch64",
648+
));
649+
}
650+
PciBarRegionType::Memory32BitRegion | PciBarRegionType::Memory64BitRegion => {
651+
let old_range =
652+
RangeInclusive::new(old_base, old_base + len - 1).map_err(|_| {
653+
std::io::Error::other("pci: invalid old range for device relocation")
654+
})?;
655+
let allocator = if region_type == PciBarRegionType::Memory32BitRegion {
656+
&self.common.resource_allocator.mmio32_memory
657+
} else {
658+
&self.common.resource_allocator.mmio64_memory
659+
};
660+
661+
allocator
662+
.lock()
663+
.expect("Poisoned lock")
664+
.free(&old_range)
665+
.map_err(|_| {
666+
std::io::Error::other("pci: failed deallocating old MMIO range")
667+
})?;
668+
669+
allocator
670+
.lock()
671+
.unwrap()
672+
.allocate(len, len, vm_allocator::AllocPolicy::ExactMatch(new_base))
673+
.map_err(|_| std::io::Error::other("pci: failed allocating new MMIO range"))?;
674+
675+
// Update MMIO bus
676+
self.common
677+
.mmio_bus
678+
.update_range(old_base, len, new_base, len)
679+
.map_err(std::io::Error::other)?;
680+
}
681+
}
682+
683+
let any_dev = pci_dev.as_any_mut();
684+
if let Some(virtio_pci_dev) = any_dev.downcast_ref::<VirtioPciDevice>() {
685+
let bar_addr = virtio_pci_dev.config_bar_addr();
686+
if bar_addr == new_base {
687+
for (i, queue_evt) in virtio_pci_dev
688+
.virtio_device()
689+
.lock()
690+
.expect("Poisoned lock")
691+
.queue_events()
692+
.iter()
693+
.enumerate()
694+
{
695+
const NOTIFICATION_BAR_OFFSET: u64 = 0x6000;
696+
const NOTIFY_OFF_MULTIPLIER: u64 = 4;
697+
let notify_base = old_base + NOTIFICATION_BAR_OFFSET;
698+
let io_addr =
699+
IoEventAddress::Mmio(notify_base + i as u64 * NOTIFY_OFF_MULTIPLIER);
700+
self.common
701+
.fd
702+
.unregister_ioevent(queue_evt, &io_addr, NoDatamatch)?;
703+
704+
let notify_base = new_base + NOTIFICATION_BAR_OFFSET;
705+
let io_addr =
706+
IoEventAddress::Mmio(notify_base + i as u64 * NOTIFY_OFF_MULTIPLIER);
707+
self.common
708+
.fd
709+
.register_ioevent(queue_evt, &io_addr, NoDatamatch)?;
710+
}
711+
}
712+
}
713+
714+
pci_dev.move_bar(old_base, new_base)
632715
}
633716
}
634717

635718
#[cfg(test)]
636719
pub(crate) mod tests {
720+
use std::ops::DerefMut;
721+
722+
use pci::PciBdf;
723+
use vm_allocator::AllocPolicy;
637724
use vm_device::interrupt::{InterruptSourceConfig, LegacyIrqSourceConfig};
638725
use vm_memory::GuestAddress;
639726
use vm_memory::mmap::MmapRegionBuilder;
640727

641728
use super::*;
729+
use crate::device_manager::mmio::tests::DummyDevice;
642730
use crate::test_utils::single_region_mem_raw;
643731
use crate::utils::mib_to_bytes;
644732
use crate::vstate::kvm::Kvm;
@@ -969,4 +1057,195 @@ pub(crate) mod tests {
9691057
assert!(!new_vector.enabled.load(Ordering::Acquire));
9701058
}
9711059
}
1060+
1061+
fn new_virtio_pci_device(vm: &Arc<Vm>) -> Arc<Mutex<VirtioPciDevice>> {
1062+
let dummy = Arc::new(Mutex::new(DummyDevice::new()));
1063+
let msi_vectors = Arc::new(Vm::create_msix_group(vm.clone(), 0, 2).unwrap());
1064+
Arc::new(Mutex::new(
1065+
VirtioPciDevice::new(
1066+
"dummy".to_string(),
1067+
vm.guest_memory().clone(),
1068+
dummy,
1069+
msi_vectors,
1070+
PciBdf::new(0, 0, 1, 0).into(),
1071+
true,
1072+
None,
1073+
)
1074+
.unwrap(),
1075+
))
1076+
}
1077+
1078+
#[cfg(target_arch = "aarch64")]
1079+
#[test]
1080+
fn test_device_relocation_no_io_on_arm() {
1081+
let (_, vm) = setup_vm_with_memory(mib_to_bytes(128));
1082+
let vm = Arc::new(vm);
1083+
let old_base = 0x42;
1084+
let new_base = 0x84;
1085+
let len = 0x1312000;
1086+
let mut pci_dev = new_virtio_pci_device(&vm);
1087+
1088+
vm.move_bar(
1089+
old_base,
1090+
new_base,
1091+
len,
1092+
&mut pci_dev,
1093+
PciBarRegionType::IoRegion,
1094+
)
1095+
.unwrap_err();
1096+
}
1097+
1098+
#[test]
1099+
fn test_device_relocation_bad_ranges() {
1100+
let (_, vm) = setup_vm_with_memory(mib_to_bytes(128));
1101+
let vm = Arc::new(vm);
1102+
let virtio_dev = new_virtio_pci_device(&vm);
1103+
let mut virtio_dev_locked = virtio_dev.lock().unwrap();
1104+
let pci_dev = virtio_dev_locked.deref_mut();
1105+
1106+
// Old region would overflow
1107+
vm.move_bar(0, 0x12, u64::MAX, pci_dev, PciBarRegionType::IoRegion)
1108+
.unwrap_err();
1109+
// New region would overflow
1110+
vm.move_bar(0x13, 0, u64::MAX, pci_dev, PciBarRegionType::IoRegion)
1111+
.unwrap_err();
1112+
}
1113+
1114+
#[test]
1115+
fn test_device_relocation_old_region_not_allocated() {
1116+
let (_, vm) = setup_vm_with_memory(mib_to_bytes(128));
1117+
let vm = Arc::new(vm);
1118+
let virtio_dev = new_virtio_pci_device(&vm);
1119+
let mut virtio_dev_locked = virtio_dev.lock().unwrap();
1120+
let pci_dev = virtio_dev_locked.deref_mut();
1121+
1122+
let err = vm
1123+
.move_bar(
1124+
0x12,
1125+
0x13,
1126+
0x42,
1127+
pci_dev,
1128+
PciBarRegionType::Memory32BitRegion,
1129+
)
1130+
.unwrap_err();
1131+
assert_eq!(format!("{err}"), "pci: failed deallocating old MMIO range");
1132+
}
1133+
1134+
#[test]
1135+
fn test_device_relocation_new_region_allocated() {
1136+
let (_, vm) = setup_vm_with_memory(mib_to_bytes(128));
1137+
let vm = Arc::new(vm);
1138+
let virtio_dev = new_virtio_pci_device(&vm);
1139+
let mut virtio_dev_locked = virtio_dev.lock().unwrap();
1140+
let pci_dev = virtio_dev_locked.deref_mut();
1141+
1142+
// Allocate old range and add it to bus
1143+
let old_base = vm
1144+
.common
1145+
.resource_allocator
1146+
.allocate_32bit_mmio_memory(0x1000, 0x1000, AllocPolicy::FirstMatch)
1147+
.unwrap();
1148+
vm.common
1149+
.mmio_bus
1150+
.insert(virtio_dev.clone(), 0x1000, 0x1000)
1151+
.unwrap();
1152+
1153+
// Also allocate new region. This should cause relocation to fail
1154+
let new_base = vm
1155+
.common
1156+
.resource_allocator
1157+
.allocate_32bit_mmio_memory(0x1000, 0x1000, AllocPolicy::FirstMatch)
1158+
.unwrap();
1159+
1160+
let err = vm
1161+
.move_bar(
1162+
old_base,
1163+
new_base,
1164+
0x1000,
1165+
pci_dev,
1166+
PciBarRegionType::Memory32BitRegion,
1167+
)
1168+
.unwrap_err();
1169+
assert_eq!(format!("{err}"), "pci: failed allocating new MMIO range");
1170+
}
1171+
1172+
#[cfg(target_arch = "x86_64")]
1173+
#[test]
1174+
fn test_device_relocation_io_device() {
1175+
let (_, vm) = setup_vm_with_memory(mib_to_bytes(128));
1176+
let vm = Arc::new(vm);
1177+
let virtio_dev = new_virtio_pci_device(&vm);
1178+
let mut virtio_dev_locked = virtio_dev.lock().unwrap();
1179+
let pci_dev = virtio_dev_locked.deref_mut();
1180+
1181+
let err = vm
1182+
.move_bar(0x12, 0x13, 0x42000, pci_dev, PciBarRegionType::IoRegion)
1183+
.unwrap_err();
1184+
assert_eq!(format!("{err}"), "bus_error: MissingAddressRange");
1185+
1186+
// If, instead, we add the device in the PIO bus, everything should work fine
1187+
vm.pio_bus
1188+
.insert(virtio_dev.clone(), 0x12, 0x42000)
1189+
.unwrap();
1190+
1191+
vm.move_bar(0x12, 0x13, 0x42000, pci_dev, PciBarRegionType::IoRegion)
1192+
.unwrap()
1193+
}
1194+
1195+
#[test]
1196+
fn test_device_relocation_mmio_device() {
1197+
let (_, vm) = setup_vm_with_memory(mib_to_bytes(128));
1198+
let vm = Arc::new(vm);
1199+
1200+
let virtio_dev = new_virtio_pci_device(&vm);
1201+
let mut virtio_dev_locked = virtio_dev.lock().unwrap();
1202+
let pci_dev = virtio_dev_locked.deref_mut();
1203+
1204+
let old_base = vm
1205+
.common
1206+
.resource_allocator
1207+
.allocate_64bit_mmio_memory(0x8000, 0x1000, AllocPolicy::FirstMatch)
1208+
.unwrap();
1209+
1210+
let err = vm
1211+
.move_bar(
1212+
old_base,
1213+
old_base + 0x8000,
1214+
0x8000,
1215+
pci_dev,
1216+
PciBarRegionType::Memory64BitRegion,
1217+
)
1218+
.unwrap_err();
1219+
assert_eq!(format!("{err}"), "bus_error: MissingAddressRange");
1220+
1221+
// Need to reset the allocator here. Erroring out left it to a limbo state (old range is
1222+
// deallocated, new range is allocated).
1223+
vm.common
1224+
.resource_allocator
1225+
.mmio64_memory
1226+
.lock()
1227+
.unwrap()
1228+
.free(&RangeInclusive::new(old_base + 0x8000, old_base + 0x8000 + 0x8000 - 1).unwrap())
1229+
.unwrap();
1230+
vm.common
1231+
.resource_allocator
1232+
.allocate_64bit_mmio_memory(0x8000, 0x1000, AllocPolicy::FirstMatch)
1233+
.unwrap();
1234+
1235+
// If we add the device to the MMIO bus, everything should work fine
1236+
vm.common
1237+
.mmio_bus
1238+
.insert(virtio_dev.clone(), old_base, 0x8000)
1239+
.unwrap();
1240+
1241+
println!("old base: {old_base:#x}");
1242+
vm.move_bar(
1243+
old_base,
1244+
old_base + 0x8000,
1245+
0x8000,
1246+
pci_dev,
1247+
PciBarRegionType::Memory64BitRegion,
1248+
)
1249+
.unwrap();
1250+
}
9721251
}

0 commit comments

Comments
 (0)