@@ -20,15 +20,17 @@ use kvm_bindings::{
2020} ;
2121use kvm_ioctls:: VmFd ;
2222use log:: debug;
23- use pci:: DeviceRelocation ;
23+ use pci:: { DeviceRelocation , PciBarRegionType , PciDevice } ;
2424use serde:: { Deserialize , Serialize } ;
25+ use vm_allocator:: RangeInclusive ;
2526use vm_device:: interrupt:: {
2627 InterruptIndex , InterruptSourceConfig , InterruptSourceGroup , MsiIrqSourceConfig ,
2728} ;
2829use vmm_sys_util:: errno;
2930use vmm_sys_util:: eventfd:: EventFd ;
3031
3132pub use crate :: arch:: { ArchVm as Vm , ArchVmError , VmState } ;
33+ use crate :: devices:: virtio:: transport:: pci:: device:: VirtioPciDevice ;
3234use crate :: logger:: info;
3335use crate :: persist:: CreateSnapshotError ;
3436use crate :: snapshot:: Persist ;
@@ -616,23 +618,90 @@ impl Vm {
616618impl DeviceRelocation for Vm {
617619 fn move_bar (
618620 & self ,
619- _old_base : u64 ,
620- _new_base : u64 ,
621- _len : u64 ,
622- _pci_dev : & mut dyn pci:: PciDevice ,
623- _region_type : pci:: PciBarRegionType ,
621+ old_base : u64 ,
622+ new_base : u64 ,
623+ len : u64 ,
624+ pci_dev : & mut dyn pci:: PciDevice ,
625+ region_type : pci:: PciBarRegionType ,
624626 ) -> Result < ( ) , std:: io:: Error > {
625- todo ! ( )
627+ debug ! ( "pci: moving BAR from {old_base:#x}:{len:#x} to {new_base:#x}:{len:#x}" ) ;
628+ match region_type {
629+ PciBarRegionType :: IoRegion => {
630+ #[ cfg( target_arch = "x86_64" ) ]
631+ // We do not allocate IO addresses, we just hard-code them, no need to handle
632+ // (re)allocations. Just update PIO bus
633+ self . pio_bus
634+ . update_range ( old_base, len, new_base, len)
635+ . map_err ( std:: io:: Error :: other) ?;
636+
637+ #[ cfg( target_arch = "aarch64" ) ]
638+ return Err ( std:: io:: Error :: other (
639+ "pci: IO regions not supported on Aarch64" ,
640+ ) ) ;
641+ }
642+ PciBarRegionType :: Memory32BitRegion | PciBarRegionType :: Memory64BitRegion => {
643+ let old_range =
644+ RangeInclusive :: new ( old_base, old_base + len - 1 ) . map_err ( |_| {
645+ std:: io:: Error :: other ( "pci: invalid old range for device relocation" )
646+ } ) ?;
647+ let allocator = if region_type == PciBarRegionType :: Memory32BitRegion {
648+ & self . common . resource_allocator . mmio32_memory
649+ } else {
650+ & self . common . resource_allocator . mmio64_memory
651+ } ;
652+
653+ allocator
654+ . lock ( )
655+ . expect ( "Poisoned lock" )
656+ . free ( & old_range)
657+ . map_err ( |_| {
658+ std:: io:: Error :: other ( "pci: failed deallocating old MMIO range" )
659+ } ) ?;
660+
661+ allocator
662+ . lock ( )
663+ . unwrap ( )
664+ . allocate ( len, len, vm_allocator:: AllocPolicy :: ExactMatch ( new_base) )
665+ . map_err ( |_| std:: io:: Error :: other ( "pci: failed allocating new MMIO range" ) ) ?;
666+
667+ // Update MMIO bus
668+ self . common
669+ . mmio_bus
670+ . update_range ( old_base, len, new_base, len)
671+ . map_err ( std:: io:: Error :: other) ?;
672+ }
673+ }
674+
675+ if let Some ( virtio_pci_dev) = pci_dev. as_any_mut ( ) . downcast_mut :: < VirtioPciDevice > ( ) {
676+ if virtio_pci_dev. config_bar_addr ( ) == new_base {
677+ virtio_pci_dev. unregister_notification_ioevent ( self ) ?;
678+ }
679+
680+ virtio_pci_dev. move_bar ( old_base, new_base) ?;
681+
682+ if virtio_pci_dev. config_bar_addr ( ) == new_base {
683+ virtio_pci_dev. register_notification_ioevent ( self ) ?;
684+ }
685+ } else {
686+ pci_dev. move_bar ( old_base, new_base) ?;
687+ }
688+
689+ Ok ( ( ) )
626690 }
627691}
628692
629693#[ cfg( test) ]
630694pub ( crate ) mod tests {
695+ use std:: ops:: DerefMut ;
696+
697+ use pci:: PciBdf ;
698+ use vm_allocator:: AllocPolicy ;
631699 use vm_device:: interrupt:: { InterruptSourceConfig , LegacyIrqSourceConfig } ;
632700 use vm_memory:: GuestAddress ;
633701 use vm_memory:: mmap:: MmapRegionBuilder ;
634702
635703 use super :: * ;
704+ use crate :: device_manager:: mmio:: tests:: DummyDevice ;
636705 use crate :: test_utils:: single_region_mem_raw;
637706 use crate :: utils:: mib_to_bytes;
638707 use crate :: vstate:: kvm:: Kvm ;
@@ -971,4 +1040,189 @@ pub(crate) mod tests {
9711040 assert ! ( !new_vector. enabled. load( Ordering :: Acquire ) ) ;
9721041 }
9731042 }
1043+
1044+ fn new_virtio_pci_device ( vm : & Arc < Vm > ) -> Arc < Mutex < VirtioPciDevice > > {
1045+ let dummy = Arc :: new ( Mutex :: new ( DummyDevice :: new ( ) ) ) ;
1046+ let msi_vectors = Arc :: new ( Vm :: create_msix_group ( vm. clone ( ) , 0 , 2 ) . unwrap ( ) ) ;
1047+ Arc :: new ( Mutex :: new (
1048+ VirtioPciDevice :: new (
1049+ "dummy" . to_string ( ) ,
1050+ vm. guest_memory ( ) . clone ( ) ,
1051+ dummy,
1052+ msi_vectors,
1053+ PciBdf :: new ( 0 , 0 , 1 , 0 ) . into ( ) ,
1054+ )
1055+ . unwrap ( ) ,
1056+ ) )
1057+ }
1058+
1059+ #[ cfg( target_arch = "aarch64" ) ]
1060+ #[ test]
1061+ fn test_device_relocation_no_io_on_arm ( ) {
1062+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1063+ let vm = Arc :: new ( vm) ;
1064+ let old_base = 0x42 ;
1065+ let new_base = 0x84 ;
1066+ let len = 0x1312000 ;
1067+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1068+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1069+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1070+
1071+ vm. move_bar ( old_base, new_base, len, pci_dev, PciBarRegionType :: IoRegion )
1072+ . unwrap_err ( ) ;
1073+ }
1074+
1075+ #[ test]
1076+ fn test_device_relocation_bad_ranges ( ) {
1077+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1078+ let vm = Arc :: new ( vm) ;
1079+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1080+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1081+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1082+
1083+ // Old region would overflow
1084+ vm. move_bar ( 0 , 0x12 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1085+ . unwrap_err ( ) ;
1086+ // New region would overflow
1087+ vm. move_bar ( 0x13 , 0 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1088+ . unwrap_err ( ) ;
1089+ }
1090+
1091+ #[ test]
1092+ fn test_device_relocation_old_region_not_allocated ( ) {
1093+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1094+ let vm = Arc :: new ( vm) ;
1095+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1096+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1097+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1098+
1099+ let err = vm
1100+ . move_bar (
1101+ 0x12 ,
1102+ 0x13 ,
1103+ 0x42 ,
1104+ pci_dev,
1105+ PciBarRegionType :: Memory32BitRegion ,
1106+ )
1107+ . unwrap_err ( ) ;
1108+ assert_eq ! ( format!( "{err}" ) , "pci: failed deallocating old MMIO range" ) ;
1109+ }
1110+
1111+ #[ test]
1112+ fn test_device_relocation_new_region_allocated ( ) {
1113+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1114+ let vm = Arc :: new ( vm) ;
1115+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1116+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1117+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1118+
1119+ // Allocate old range and add it to bus
1120+ let old_base = vm
1121+ . common
1122+ . resource_allocator
1123+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1124+ . unwrap ( ) ;
1125+ vm. common
1126+ . mmio_bus
1127+ . insert ( virtio_dev. clone ( ) , 0x1000 , 0x1000 )
1128+ . unwrap ( ) ;
1129+
1130+ // Also allocate new region. This should cause relocation to fail
1131+ let new_base = vm
1132+ . common
1133+ . resource_allocator
1134+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1135+ . unwrap ( ) ;
1136+
1137+ let err = vm
1138+ . move_bar (
1139+ old_base,
1140+ new_base,
1141+ 0x1000 ,
1142+ pci_dev,
1143+ PciBarRegionType :: Memory32BitRegion ,
1144+ )
1145+ . unwrap_err ( ) ;
1146+ assert_eq ! ( format!( "{err}" ) , "pci: failed allocating new MMIO range" ) ;
1147+ }
1148+
1149+ #[ cfg( target_arch = "x86_64" ) ]
1150+ #[ test]
1151+ fn test_device_relocation_io_device ( ) {
1152+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1153+ let vm = Arc :: new ( vm) ;
1154+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1155+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1156+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1157+
1158+ let err = vm
1159+ . move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1160+ . unwrap_err ( ) ;
1161+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1162+
1163+ // If, instead, we add the device in the PIO bus, everything should work fine
1164+ vm. pio_bus
1165+ . insert ( virtio_dev. clone ( ) , 0x12 , 0x42000 )
1166+ . unwrap ( ) ;
1167+
1168+ vm. move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1169+ . unwrap ( )
1170+ }
1171+
1172+ #[ test]
1173+ fn test_device_relocation_mmio_device ( ) {
1174+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1175+ let vm = Arc :: new ( vm) ;
1176+
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 old_base = vm
1182+ . common
1183+ . resource_allocator
1184+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1185+ . unwrap ( ) ;
1186+
1187+ let err = vm
1188+ . move_bar (
1189+ old_base,
1190+ old_base + 0x8000 ,
1191+ 0x8000 ,
1192+ pci_dev,
1193+ PciBarRegionType :: Memory64BitRegion ,
1194+ )
1195+ . unwrap_err ( ) ;
1196+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1197+
1198+ // Need to reset the allocator here. Erroring out left it to a limbo state (old range is
1199+ // deallocated, new range is allocated).
1200+ vm. common
1201+ . resource_allocator
1202+ . mmio64_memory
1203+ . lock ( )
1204+ . unwrap ( )
1205+ . free ( & RangeInclusive :: new ( old_base + 0x8000 , old_base + 0x8000 + 0x8000 - 1 ) . unwrap ( ) )
1206+ . unwrap ( ) ;
1207+ vm. common
1208+ . resource_allocator
1209+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1210+ . unwrap ( ) ;
1211+
1212+ // If we add the device to the MMIO bus, everything should work fine
1213+ vm. common
1214+ . mmio_bus
1215+ . insert ( virtio_dev. clone ( ) , old_base, 0x8000 )
1216+ . unwrap ( ) ;
1217+
1218+ println ! ( "old base: {old_base:#x}" ) ;
1219+ vm. move_bar (
1220+ old_base,
1221+ old_base + 0x8000 ,
1222+ 0x8000 ,
1223+ pci_dev,
1224+ PciBarRegionType :: Memory64BitRegion ,
1225+ )
1226+ . unwrap ( ) ;
1227+ }
9741228}
0 commit comments