@@ -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 ;
@@ -977,4 +1063,191 @@ pub(crate) mod tests {
9771063 assert ! ( !new_vector. enabled. load( Ordering :: Acquire ) ) ;
9781064 }
9791065 }
1066+
1067+ fn new_virtio_pci_device ( vm : & Arc < Vm > ) -> Arc < Mutex < VirtioPciDevice > > {
1068+ let dummy = Arc :: new ( Mutex :: new ( DummyDevice :: new ( ) ) ) ;
1069+ let msi_vectors = Arc :: new ( Vm :: create_msix_group ( vm. clone ( ) , 0 , 2 ) . unwrap ( ) ) ;
1070+ Arc :: new ( Mutex :: new (
1071+ VirtioPciDevice :: new (
1072+ "dummy" . to_string ( ) ,
1073+ vm. guest_memory ( ) . clone ( ) ,
1074+ dummy,
1075+ msi_vectors,
1076+ PciBdf :: new ( 0 , 0 , 1 , 0 ) . into ( ) ,
1077+ true ,
1078+ None ,
1079+ )
1080+ . unwrap ( ) ,
1081+ ) )
1082+ }
1083+
1084+ #[ cfg( target_arch = "aarch64" ) ]
1085+ #[ test]
1086+ fn test_device_relocation_no_io_on_arm ( ) {
1087+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1088+ let vm = Arc :: new ( vm) ;
1089+ let old_base = 0x42 ;
1090+ let new_base = 0x84 ;
1091+ let len = 0x1312000 ;
1092+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1093+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1094+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1095+
1096+ vm. move_bar ( old_base, new_base, len, pci_dev, PciBarRegionType :: IoRegion )
1097+ . unwrap_err ( ) ;
1098+ }
1099+
1100+ #[ test]
1101+ fn test_device_relocation_bad_ranges ( ) {
1102+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1103+ let vm = Arc :: new ( vm) ;
1104+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1105+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1106+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1107+
1108+ // Old region would overflow
1109+ vm. move_bar ( 0 , 0x12 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1110+ . unwrap_err ( ) ;
1111+ // New region would overflow
1112+ vm. move_bar ( 0x13 , 0 , u64:: MAX , pci_dev, PciBarRegionType :: IoRegion )
1113+ . unwrap_err ( ) ;
1114+ }
1115+
1116+ #[ test]
1117+ fn test_device_relocation_old_region_not_allocated ( ) {
1118+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1119+ let vm = Arc :: new ( vm) ;
1120+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1121+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1122+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1123+
1124+ let err = vm
1125+ . move_bar (
1126+ 0x12 ,
1127+ 0x13 ,
1128+ 0x42 ,
1129+ pci_dev,
1130+ PciBarRegionType :: Memory32BitRegion ,
1131+ )
1132+ . unwrap_err ( ) ;
1133+ assert_eq ! ( format!( "{err}" ) , "pci: failed deallocating old MMIO range" ) ;
1134+ }
1135+
1136+ #[ test]
1137+ fn test_device_relocation_new_region_allocated ( ) {
1138+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1139+ let vm = Arc :: new ( vm) ;
1140+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1141+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1142+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1143+
1144+ // Allocate old range and add it to bus
1145+ let old_base = vm
1146+ . common
1147+ . resource_allocator
1148+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1149+ . unwrap ( ) ;
1150+ vm. common
1151+ . mmio_bus
1152+ . insert ( virtio_dev. clone ( ) , 0x1000 , 0x1000 )
1153+ . unwrap ( ) ;
1154+
1155+ // Also allocate new region. This should cause relocation to fail
1156+ let new_base = vm
1157+ . common
1158+ . resource_allocator
1159+ . allocate_32bit_mmio_memory ( 0x1000 , 0x1000 , AllocPolicy :: FirstMatch )
1160+ . unwrap ( ) ;
1161+
1162+ let err = vm
1163+ . move_bar (
1164+ old_base,
1165+ new_base,
1166+ 0x1000 ,
1167+ pci_dev,
1168+ PciBarRegionType :: Memory32BitRegion ,
1169+ )
1170+ . unwrap_err ( ) ;
1171+ assert_eq ! ( format!( "{err}" ) , "pci: failed allocating new MMIO range" ) ;
1172+ }
1173+
1174+ #[ cfg( target_arch = "x86_64" ) ]
1175+ #[ test]
1176+ fn test_device_relocation_io_device ( ) {
1177+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1178+ let vm = Arc :: new ( vm) ;
1179+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1180+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1181+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1182+
1183+ let err = vm
1184+ . move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1185+ . unwrap_err ( ) ;
1186+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1187+
1188+ // If, instead, we add the device in the PIO bus, everything should work fine
1189+ vm. pio_bus
1190+ . insert ( virtio_dev. clone ( ) , 0x12 , 0x42000 )
1191+ . unwrap ( ) ;
1192+
1193+ vm. move_bar ( 0x12 , 0x13 , 0x42000 , pci_dev, PciBarRegionType :: IoRegion )
1194+ . unwrap ( )
1195+ }
1196+
1197+ #[ test]
1198+ fn test_device_relocation_mmio_device ( ) {
1199+ let ( _, vm) = setup_vm_with_memory ( mib_to_bytes ( 128 ) ) ;
1200+ let vm = Arc :: new ( vm) ;
1201+
1202+ let virtio_dev = new_virtio_pci_device ( & vm) ;
1203+ let mut virtio_dev_locked = virtio_dev. lock ( ) . unwrap ( ) ;
1204+ let pci_dev = virtio_dev_locked. deref_mut ( ) ;
1205+
1206+ let old_base = vm
1207+ . common
1208+ . resource_allocator
1209+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1210+ . unwrap ( ) ;
1211+
1212+ let err = vm
1213+ . move_bar (
1214+ old_base,
1215+ old_base + 0x8000 ,
1216+ 0x8000 ,
1217+ pci_dev,
1218+ PciBarRegionType :: Memory64BitRegion ,
1219+ )
1220+ . unwrap_err ( ) ;
1221+ assert_eq ! ( format!( "{err}" ) , "bus_error: MissingAddressRange" ) ;
1222+
1223+ // Need to reset the allocator here. Erroring out left it to a limbo state (old range is
1224+ // deallocated, new range is allocated).
1225+ vm. common
1226+ . resource_allocator
1227+ . mmio64_memory
1228+ . lock ( )
1229+ . unwrap ( )
1230+ . free ( & RangeInclusive :: new ( old_base + 0x8000 , old_base + 0x8000 + 0x8000 - 1 ) . unwrap ( ) )
1231+ . unwrap ( ) ;
1232+ vm. common
1233+ . resource_allocator
1234+ . allocate_64bit_mmio_memory ( 0x8000 , 0x1000 , AllocPolicy :: FirstMatch )
1235+ . unwrap ( ) ;
1236+
1237+ // If we add the device to the MMIO bus, everything should work fine
1238+ vm. common
1239+ . mmio_bus
1240+ . insert ( virtio_dev. clone ( ) , old_base, 0x8000 )
1241+ . unwrap ( ) ;
1242+
1243+ println ! ( "old base: {old_base:#x}" ) ;
1244+ vm. move_bar (
1245+ old_base,
1246+ old_base + 0x8000 ,
1247+ 0x8000 ,
1248+ pci_dev,
1249+ PciBarRegionType :: Memory64BitRegion ,
1250+ )
1251+ . unwrap ( ) ;
1252+ }
9801253}
0 commit comments