@@ -18,15 +18,17 @@ use kvm_bindings::{
1818 KVM_IRQ_ROUTING_IRQCHIP , KVM_IRQ_ROUTING_MSI , KVM_MEM_LOG_DIRTY_PAGES , KVM_MSI_VALID_DEVID ,
1919 KvmIrqRouting , kvm_irq_routing_entry, kvm_userspace_memory_region,
2020} ;
21- use kvm_ioctls:: VmFd ;
21+ use kvm_ioctls:: { IoEventAddress , NoDatamatch , VmFd } ;
2222use log:: debug;
23- use pci:: DeviceRelocation ;
23+ use pci:: { DeviceRelocation , PciBarRegionType } ;
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,107 @@ 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+ let any_dev = pci_dev. as_any_mut ( ) ;
682+ if let Some ( virtio_pci_dev) = any_dev. downcast_ref :: < VirtioPciDevice > ( ) {
683+ let bar_addr = virtio_pci_dev. config_bar_addr ( ) ;
684+ if bar_addr == new_base {
685+ for ( i, queue_evt) in virtio_pci_dev
686+ . virtio_device ( )
687+ . lock ( )
688+ . expect ( "Poisoned lock" )
689+ . queue_events ( )
690+ . iter ( )
691+ . enumerate ( )
692+ {
693+ const NOTIFICATION_BAR_OFFSET : u64 = 0x6000 ;
694+ const NOTIFY_OFF_MULTIPLIER : u64 = 4 ;
695+ let notify_base = old_base + NOTIFICATION_BAR_OFFSET ;
696+ let io_addr =
697+ IoEventAddress :: Mmio ( notify_base + i as u64 * NOTIFY_OFF_MULTIPLIER ) ;
698+ self . common
699+ . fd
700+ . unregister_ioevent ( queue_evt, & io_addr, NoDatamatch ) ?;
701+
702+ let notify_base = new_base + NOTIFICATION_BAR_OFFSET ;
703+ let io_addr =
704+ IoEventAddress :: Mmio ( notify_base + i as u64 * NOTIFY_OFF_MULTIPLIER ) ;
705+ self . common
706+ . fd
707+ . register_ioevent ( queue_evt, & io_addr, NoDatamatch ) ?;
708+ }
709+ }
710+ }
711+
712+ pci_dev. move_bar ( old_base, new_base)
632713 }
633714}
634715
635716#[ cfg( test) ]
636717pub ( crate ) mod tests {
718+ use std:: ops:: DerefMut ;
719+
720+ use pci:: PciBdf ;
721+ use vm_allocator:: AllocPolicy ;
637722 use vm_device:: interrupt:: { InterruptSourceConfig , LegacyIrqSourceConfig } ;
638723 use vm_memory:: GuestAddress ;
639724 use vm_memory:: mmap:: MmapRegionBuilder ;
640725
641726 use super :: * ;
727+ use crate :: device_manager:: mmio:: tests:: DummyDevice ;
642728 use crate :: test_utils:: single_region_mem_raw;
643729 use crate :: utils:: mib_to_bytes;
644730 use crate :: vstate:: kvm:: Kvm ;
@@ -969,4 +1055,191 @@ pub(crate) mod tests {
9691055 assert ! ( !new_vector. enabled. load( Ordering :: Acquire ) ) ;
9701056 }
9711057 }
1058+
1059+ fn new_virtio_pci_device ( vm : & Arc < Vm > ) -> Arc < Mutex < VirtioPciDevice > > {
1060+ let dummy = Arc :: new ( Mutex :: new ( DummyDevice :: new ( ) ) ) ;
1061+ let msi_vectors = Arc :: new ( Vm :: create_msix_group ( vm. clone ( ) , 0 , 2 ) . unwrap ( ) ) ;
1062+ Arc :: new ( Mutex :: new (
1063+ VirtioPciDevice :: new (
1064+ "dummy" . to_string ( ) ,
1065+ vm. guest_memory ( ) . clone ( ) ,
1066+ dummy,
1067+ msi_vectors,
1068+ PciBdf :: new ( 0 , 0 , 1 , 0 ) . into ( ) ,
1069+ true ,
1070+ None ,
1071+ )
1072+ . unwrap ( ) ,
1073+ ) )
1074+ }
1075+
1076+ #[ cfg( target_arch = "aarch64" ) ]
1077+ #[ test]
1078+ fn test_device_relocation_no_io_on_arm ( ) {
1079+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1080+ let vm = Arc :: new ( vm) ;
1081+ let old_base = 0x42 ;
1082+ let new_base = 0x84 ;
1083+ let len = 0x1312000 ;
1084+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1085+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1086+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1087+
1088+ vm. move_bar ( old_base, new_base, len, pci_dev, PciBarRegionType :: IoRegion )
1089+ . unwrap_err ( ) ;
1090+ }
1091+
1092+ #[ test]
1093+ fn test_device_relocation_bad_ranges ( ) {
1094+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1095+ let vm = Arc :: new ( vm) ;
1096+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1097+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1098+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1099+
1100+ // Old region would overflow
1101+ vm. move_bar ( 0 , 0x12 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1102+ . unwrap_err ( ) ;
1103+ // New region would overflow
1104+ vm. move_bar ( 0x13 , 0 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1105+ . unwrap_err ( ) ;
1106+ }
1107+
1108+ #[ test]
1109+ fn test_device_relocation_old_region_not_allocated ( ) {
1110+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1111+ let vm = Arc :: new ( vm) ;
1112+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1113+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1114+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1115+
1116+ let err = vm
1117+ . move_bar (
1118+ 0x12 ,
1119+ 0x13 ,
1120+ 0x42 ,
1121+ pci_dev,
1122+ PciBarRegionType :: Memory32BitRegion ,
1123+ )
1124+ . unwrap_err ( ) ;
1125+ assert_eq ! ( format!( "{err}" ) , "pci: failed deallocating old MMIO range" ) ;
1126+ }
1127+
1128+ #[ test]
1129+ fn test_device_relocation_new_region_allocated ( ) {
1130+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1131+ let vm = Arc :: new ( vm) ;
1132+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1133+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1134+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1135+
1136+ // Allocate old range and add it to bus
1137+ let old_base = vm
1138+ . common
1139+ . resource_allocator
1140+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1141+ . unwrap ( ) ;
1142+ vm. common
1143+ . mmio_bus
1144+ . insert ( virtio_dev. clone ( ) , 0x1000 , 0x1000 )
1145+ . unwrap ( ) ;
1146+
1147+ // Also allocate new region. This should cause relocation to fail
1148+ let new_base = vm
1149+ . common
1150+ . resource_allocator
1151+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1152+ . unwrap ( ) ;
1153+
1154+ let err = vm
1155+ . move_bar (
1156+ old_base,
1157+ new_base,
1158+ 0x1000 ,
1159+ pci_dev,
1160+ PciBarRegionType :: Memory32BitRegion ,
1161+ )
1162+ . unwrap_err ( ) ;
1163+ assert_eq ! ( format!( "{err}" ) , "pci: failed allocating new MMIO range" ) ;
1164+ }
1165+
1166+ #[ cfg( target_arch = "x86_64" ) ]
1167+ #[ test]
1168+ fn test_device_relocation_io_device ( ) {
1169+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1170+ let vm = Arc :: new ( vm) ;
1171+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1172+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1173+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1174+
1175+ let err = vm
1176+ . move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1177+ . unwrap_err ( ) ;
1178+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1179+
1180+ // If, instead, we add the device in the PIO bus, everything should work fine
1181+ vm. pio_bus
1182+ . insert ( virtio_dev. clone ( ) , 0x12 , 0x42000 )
1183+ . unwrap ( ) ;
1184+
1185+ vm. move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1186+ . unwrap ( )
1187+ }
1188+
1189+ #[ test]
1190+ fn test_device_relocation_mmio_device ( ) {
1191+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1192+ let vm = Arc :: new ( vm) ;
1193+
1194+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1195+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1196+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1197+
1198+ let old_base = vm
1199+ . common
1200+ . resource_allocator
1201+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1202+ . unwrap ( ) ;
1203+
1204+ let err = vm
1205+ . move_bar (
1206+ old_base,
1207+ old_base + 0x8000 ,
1208+ 0x8000 ,
1209+ pci_dev,
1210+ PciBarRegionType :: Memory64BitRegion ,
1211+ )
1212+ . unwrap_err ( ) ;
1213+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1214+
1215+ // Need to reset the allocator here. Erroring out left it to a limbo state (old range is
1216+ // deallocated, new range is allocated).
1217+ vm. common
1218+ . resource_allocator
1219+ . mmio64_memory
1220+ . lock ( )
1221+ . unwrap ( )
1222+ . free ( & RangeInclusive :: new ( old_base + 0x8000 , old_base + 0x8000 + 0x8000 - 1 ) . unwrap ( ) )
1223+ . unwrap ( ) ;
1224+ vm. common
1225+ . resource_allocator
1226+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1227+ . unwrap ( ) ;
1228+
1229+ // If we add the device to the MMIO bus, everything should work fine
1230+ vm. common
1231+ . mmio_bus
1232+ . insert ( virtio_dev. clone ( ) , old_base, 0x8000 )
1233+ . unwrap ( ) ;
1234+
1235+ println ! ( "old base: {old_base:#x}" ) ;
1236+ vm. move_bar (
1237+ old_base,
1238+ old_base + 0x8000 ,
1239+ 0x8000 ,
1240+ pci_dev,
1241+ PciBarRegionType :: Memory64BitRegion ,
1242+ )
1243+ . unwrap ( ) ;
1244+ }
9721245}
0 commit comments