@@ -20,13 +20,15 @@ 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:: { InterruptSourceGroup , MsiIrqSourceConfig } ;
2627use vmm_sys_util:: errno;
2728use vmm_sys_util:: eventfd:: EventFd ;
2829
2930pub use crate :: arch:: { ArchVm as Vm , ArchVmError , VmState } ;
31+ use crate :: devices:: virtio:: transport:: pci:: device:: VirtioPciDevice ;
3032use crate :: logger:: info;
3133use crate :: persist:: CreateSnapshotError ;
3234use crate :: snapshot:: Persist ;
@@ -622,23 +624,90 @@ impl Vm {
622624impl 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+ if let Some ( virtio_pci_dev) = pci_dev. as_any_mut ( ) . downcast_mut :: < VirtioPciDevice > ( ) {
682+ if virtio_pci_dev. config_bar_addr ( ) == new_base {
683+ virtio_pci_dev. unregister_notification_ioevent ( self ) ?;
684+ }
685+
686+ virtio_pci_dev. move_bar ( old_base, new_base) ?;
687+
688+ if virtio_pci_dev. config_bar_addr ( ) == new_base {
689+ virtio_pci_dev. register_notification_ioevent ( self ) ?;
690+ }
691+ } else {
692+ pci_dev. move_bar ( old_base, new_base) ?;
693+ }
694+
695+ Ok ( ( ) )
632696 }
633697}
634698
635699#[ cfg( test) ]
636700pub ( crate ) mod tests {
701+ use std:: ops:: DerefMut ;
702+
703+ use pci:: PciBdf ;
704+ use vm_allocator:: AllocPolicy ;
637705 use vm_device:: interrupt:: { InterruptSourceConfig , LegacyIrqSourceConfig } ;
638706 use vm_memory:: GuestAddress ;
639707 use vm_memory:: mmap:: MmapRegionBuilder ;
640708
641709 use super :: * ;
710+ use crate :: device_manager:: mmio:: tests:: DummyDevice ;
642711 use crate :: test_utils:: single_region_mem_raw;
643712 use crate :: utils:: mib_to_bytes;
644713 use crate :: vstate:: kvm:: Kvm ;
@@ -977,4 +1046,189 @@ pub(crate) mod tests {
9771046 assert ! ( !new_vector. enabled. load( Ordering :: Acquire ) ) ;
9781047 }
9791048 }
1049+
1050+ fn new_virtio_pci_device ( vm : & Arc < Vm > ) -> Arc < Mutex < VirtioPciDevice > > {
1051+ let dummy = Arc :: new ( Mutex :: new ( DummyDevice :: new ( ) ) ) ;
1052+ let msi_vectors = Arc :: new ( Vm :: create_msix_group ( vm. clone ( ) , 0 , 2 ) . unwrap ( ) ) ;
1053+ Arc :: new ( Mutex :: new (
1054+ VirtioPciDevice :: new (
1055+ "dummy" . to_string ( ) ,
1056+ vm. guest_memory ( ) . clone ( ) ,
1057+ dummy,
1058+ msi_vectors,
1059+ PciBdf :: new ( 0 , 0 , 1 , 0 ) . into ( ) ,
1060+ )
1061+ . unwrap ( ) ,
1062+ ) )
1063+ }
1064+
1065+ #[ cfg( target_arch = "aarch64" ) ]
1066+ #[ test]
1067+ fn test_device_relocation_no_io_on_arm ( ) {
1068+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1069+ let vm = Arc :: new ( vm) ;
1070+ let old_base = 0x42 ;
1071+ let new_base = 0x84 ;
1072+ let len = 0x1312000 ;
1073+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1074+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1075+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1076+
1077+ vm. move_bar ( old_base, new_base, len, pci_dev, PciBarRegionType :: IoRegion )
1078+ . unwrap_err ( ) ;
1079+ }
1080+
1081+ #[ test]
1082+ fn test_device_relocation_bad_ranges ( ) {
1083+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1084+ let vm = Arc :: new ( vm) ;
1085+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1086+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1087+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1088+
1089+ // Old region would overflow
1090+ vm. move_bar ( 0 , 0x12 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1091+ . unwrap_err ( ) ;
1092+ // New region would overflow
1093+ vm. move_bar ( 0x13 , 0 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1094+ . unwrap_err ( ) ;
1095+ }
1096+
1097+ #[ test]
1098+ fn test_device_relocation_old_region_not_allocated ( ) {
1099+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1100+ let vm = Arc :: new ( vm) ;
1101+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1102+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1103+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1104+
1105+ let err = vm
1106+ . move_bar (
1107+ 0x12 ,
1108+ 0x13 ,
1109+ 0x42 ,
1110+ pci_dev,
1111+ PciBarRegionType :: Memory32BitRegion ,
1112+ )
1113+ . unwrap_err ( ) ;
1114+ assert_eq ! ( format!( "{err}" ) , "pci: failed deallocating old MMIO range" ) ;
1115+ }
1116+
1117+ #[ test]
1118+ fn test_device_relocation_new_region_allocated ( ) {
1119+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1120+ let vm = Arc :: new ( vm) ;
1121+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1122+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1123+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1124+
1125+ // Allocate old range and add it to bus
1126+ let old_base = vm
1127+ . common
1128+ . resource_allocator
1129+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1130+ . unwrap ( ) ;
1131+ vm. common
1132+ . mmio_bus
1133+ . insert ( virtio_dev. clone ( ) , 0x1000 , 0x1000 )
1134+ . unwrap ( ) ;
1135+
1136+ // Also allocate new region. This should cause relocation to fail
1137+ let new_base = vm
1138+ . common
1139+ . resource_allocator
1140+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1141+ . unwrap ( ) ;
1142+
1143+ let err = vm
1144+ . move_bar (
1145+ old_base,
1146+ new_base,
1147+ 0x1000 ,
1148+ pci_dev,
1149+ PciBarRegionType :: Memory32BitRegion ,
1150+ )
1151+ . unwrap_err ( ) ;
1152+ assert_eq ! ( format!( "{err}" ) , "pci: failed allocating new MMIO range" ) ;
1153+ }
1154+
1155+ #[ cfg( target_arch = "x86_64" ) ]
1156+ #[ test]
1157+ fn test_device_relocation_io_device ( ) {
1158+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1159+ let vm = Arc :: new ( vm) ;
1160+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1161+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1162+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1163+
1164+ let err = vm
1165+ . move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1166+ . unwrap_err ( ) ;
1167+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1168+
1169+ // If, instead, we add the device in the PIO bus, everything should work fine
1170+ vm. pio_bus
1171+ . insert ( virtio_dev. clone ( ) , 0x12 , 0x42000 )
1172+ . unwrap ( ) ;
1173+
1174+ vm. move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1175+ . unwrap ( )
1176+ }
1177+
1178+ #[ test]
1179+ fn test_device_relocation_mmio_device ( ) {
1180+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1181+ let vm = Arc :: new ( vm) ;
1182+
1183+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1184+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1185+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1186+
1187+ let old_base = vm
1188+ . common
1189+ . resource_allocator
1190+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1191+ . unwrap ( ) ;
1192+
1193+ let err = vm
1194+ . move_bar (
1195+ old_base,
1196+ old_base + 0x8000 ,
1197+ 0x8000 ,
1198+ pci_dev,
1199+ PciBarRegionType :: Memory64BitRegion ,
1200+ )
1201+ . unwrap_err ( ) ;
1202+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1203+
1204+ // Need to reset the allocator here. Erroring out left it to a limbo state (old range is
1205+ // deallocated, new range is allocated).
1206+ vm. common
1207+ . resource_allocator
1208+ . mmio64_memory
1209+ . lock ( )
1210+ . unwrap ( )
1211+ . free ( & RangeInclusive :: new ( old_base + 0x8000 , old_base + 0x8000 + 0x8000 - 1 ) . unwrap ( ) )
1212+ . unwrap ( ) ;
1213+ vm. common
1214+ . resource_allocator
1215+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1216+ . unwrap ( ) ;
1217+
1218+ // If we add the device to the MMIO bus, everything should work fine
1219+ vm. common
1220+ . mmio_bus
1221+ . insert ( virtio_dev. clone ( ) , old_base, 0x8000 )
1222+ . unwrap ( ) ;
1223+
1224+ println ! ( "old base: {old_base:#x}" ) ;
1225+ vm. move_bar (
1226+ old_base,
1227+ old_base + 0x8000 ,
1228+ 0x8000 ,
1229+ pci_dev,
1230+ PciBarRegionType :: Memory64BitRegion ,
1231+ )
1232+ . unwrap ( ) ;
1233+ }
9801234}
0 commit comments