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