Skip to content

Commit 872521e

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 035bf40 commit 872521e

File tree

1 file changed

+281
-8
lines changed

1 file changed

+281
-8
lines changed

src/vmm/src/vstate/vm.rs

Lines changed: 281 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,17 @@ 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+
use pci::{DeviceRelocation, PciBarRegionType};
2424
use serde::{Deserialize, Serialize};
25+
use vm_allocator::RangeInclusive;
2526
use vm_device::interrupt::{InterruptSourceGroup, MsiIrqSourceConfig};
2627
use vmm_sys_util::errno;
2728
use vmm_sys_util::eventfd::EventFd;
2829

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

635716
#[cfg(test)]
636717
pub(crate) mod tests {
718+
use std::ops::DerefMut;
719+
720+
use pci::PciBdf;
721+
use vm_allocator::AllocPolicy;
637722
use vm_device::interrupt::{InterruptSourceConfig, LegacyIrqSourceConfig};
638723
use vm_memory::GuestAddress;
639724
use vm_memory::mmap::MmapRegionBuilder;
640725

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

0 commit comments

Comments
 (0)