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