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