@@ -122,11 +122,17 @@ use device_manager::resources::ResourceAllocator;
122122use devices:: acpi:: vmgenid:: VmGenIdError ;
123123use event_manager:: { EventManager as BaseEventManager , EventOps , Events , MutEventSubscriber } ;
124124use seccompiler:: BpfProgram ;
125+ #[ cfg( target_arch = "x86_64" ) ]
126+ use seccompiler:: BpfThreadMap ;
125127use userfaultfd:: Uffd ;
126128use utils:: epoll:: EventSet ;
127129use utils:: eventfd:: EventFd ;
128130use utils:: terminal:: Terminal ;
129131use utils:: u64_to_usize;
132+ #[ cfg( target_arch = "x86_64" ) ]
133+ use vmm_config:: hotplug:: { HotplugVcpuConfig , HotplugVcpuError } ;
134+ #[ cfg( target_arch = "x86_64" ) ]
135+ use vmm_config:: machine_config:: { MachineConfigUpdate , MAX_SUPPORTED_VCPUS } ;
130136use vstate:: vcpu:: { self , KvmVcpuConfigureError , StartThreadedError , VcpuSendEventError } ;
131137
132138use crate :: arch:: DeviceType ;
@@ -314,6 +320,9 @@ pub struct Vmm {
314320 vcpus_handles : Vec < VcpuHandle > ,
315321 // Used by Vcpus and devices to initiate teardown; Vmm should never write here.
316322 vcpus_exit_evt : EventFd ,
323+ // seccomp_filters are only needed in VMM for hotplugging vCPUS.
324+ #[ cfg( target_arch = "x86_64" ) ]
325+ seccomp_filters : BpfThreadMap ,
317326
318327 // Allocator for guest resources
319328 resource_allocator : ResourceAllocator ,
@@ -594,6 +603,66 @@ impl Vmm {
594603 Ok ( cpu_configs)
595604 }
596605
606+ /// Adds new vCPUs to VMM.
607+ #[ cfg( target_arch = "x86_64" ) ]
608+ pub fn hotplug_vcpus (
609+ & mut self ,
610+ config : HotplugVcpuConfig ,
611+ ) -> Result < MachineConfigUpdate , HotplugVcpuError > {
612+ use crate :: logger:: IncMetric ;
613+ if config. vcpu_count < 1 {
614+ return Err ( HotplugVcpuError :: VcpuCountTooLow ) ;
615+ } else if self
616+ . vcpus_handles
617+ . len ( )
618+ . checked_add ( config. vcpu_count . into ( ) )
619+ . ok_or ( HotplugVcpuError :: VcpuCountTooHigh ) ?
620+ > MAX_SUPPORTED_VCPUS . into ( )
621+ {
622+ return Err ( HotplugVcpuError :: VcpuCountTooHigh ) ;
623+ }
624+
625+ // Create and start new vcpus
626+ let mut vcpus = Vec :: with_capacity ( config. vcpu_count . into ( ) ) ;
627+
628+ #[ allow( clippy:: cast_possible_truncation) ]
629+ let start_idx = self . vcpus_handles . len ( ) . try_into ( ) . unwrap ( ) ;
630+ for cpu_idx in start_idx..( start_idx + config. vcpu_count ) {
631+ let exit_evt = self
632+ . vcpus_exit_evt
633+ . try_clone ( )
634+ . map_err ( HotplugVcpuError :: EventFd ) ?;
635+ let vcpu =
636+ Vcpu :: new ( cpu_idx, & self . vm , exit_evt) . map_err ( HotplugVcpuError :: VcpuCreate ) ?;
637+ vcpus. push ( vcpu) ;
638+ }
639+
640+ self . start_vcpus (
641+ vcpus,
642+ self . seccomp_filters
643+ . get ( "vcpu" )
644+ . ok_or_else ( || HotplugVcpuError :: MissingSeccompFilters ( "vcpu" . to_string ( ) ) ) ?
645+ . clone ( ) ,
646+ )
647+ . map_err ( HotplugVcpuError :: VcpuStart ) ?;
648+
649+ #[ allow( clippy:: cast_lossless) ]
650+ METRICS . hotplug . vcpus_added . add ( config. vcpu_count . into ( ) ) ;
651+
652+ // Update VM config to reflect new CPUs added
653+ #[ allow( clippy:: cast_possible_truncation) ]
654+ let new_machine_config = MachineConfigUpdate {
655+ vcpu_count : Some ( self . vcpus_handles . len ( ) as u8 ) ,
656+ mem_size_mib : None ,
657+ smt : None ,
658+ cpu_template : None ,
659+ track_dirty_pages : None ,
660+ huge_pages : None ,
661+ } ;
662+
663+ Ok ( new_machine_config)
664+ }
665+
597666 /// Retrieves the KVM dirty bitmap for each of the guest's memory regions.
598667 pub fn reset_dirty_bitmap ( & self ) {
599668 self . guest_memory
0 commit comments