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