@@ -124,11 +124,17 @@ use device_manager::resources::ResourceAllocator;
124124use devices:: acpi:: vmgenid:: VmGenIdError ;
125125use event_manager:: { EventManager as BaseEventManager , EventOps , Events , MutEventSubscriber } ;
126126use seccompiler:: BpfProgram ;
127+ #[ cfg( target_arch = "x86_64" ) ]
128+ use seccompiler:: BpfThreadMap ;
127129use userfaultfd:: Uffd ;
128130use utils:: epoll:: EventSet ;
129131use utils:: eventfd:: EventFd ;
130132use utils:: terminal:: Terminal ;
131133use utils:: u64_to_usize;
134+ #[ cfg( target_arch = "x86_64" ) ]
135+ use vmm_config:: hotplug:: { HotplugVcpuConfig , HotplugVcpuError } ;
136+ #[ cfg( target_arch = "x86_64" ) ]
137+ use vmm_config:: machine_config:: { MachineConfigUpdate , MAX_SUPPORTED_VCPUS } ;
132138use vstate:: vcpu:: { self , KvmVcpuConfigureError , StartThreadedError , VcpuSendEventError } ;
133139
134140use crate :: arch:: DeviceType ;
@@ -317,6 +323,9 @@ pub struct Vmm {
317323 vcpus_handles : Vec < VcpuHandle > ,
318324 // Used by Vcpus and devices to initiate teardown; Vmm should never write here.
319325 vcpus_exit_evt : EventFd ,
326+ // seccomp_filters are only needed in VMM for hotplugging vCPUS.
327+ #[ cfg( target_arch = "x86_64" ) ]
328+ seccomp_filters : BpfThreadMap ,
320329
321330 // Allocator for guest resrouces
322331 resource_allocator : ResourceAllocator ,
@@ -600,6 +609,66 @@ impl Vmm {
600609 Ok ( cpu_configs)
601610 }
602611
612+ /// Adds new vCPUs to VMM.
613+ #[ cfg( target_arch = "x86_64" ) ]
614+ pub fn hotplug_vcpus (
615+ & mut self ,
616+ config : HotplugVcpuConfig ,
617+ ) -> Result < MachineConfigUpdate , HotplugVcpuError > {
618+ use crate :: logger:: IncMetric ;
619+ if config. vcpu_count < 1 {
620+ return Err ( HotplugVcpuError :: VcpuCountTooLow ) ;
621+ } else if self
622+ . vcpus_handles
623+ . len ( )
624+ . checked_add ( config. vcpu_count . into ( ) )
625+ . ok_or ( HotplugVcpuError :: VcpuCountTooHigh ) ?
626+ > MAX_SUPPORTED_VCPUS . into ( )
627+ {
628+ return Err ( HotplugVcpuError :: VcpuCountTooHigh ) ;
629+ }
630+
631+ // Create and start new vcpus
632+ let mut vcpus = Vec :: with_capacity ( config. vcpu_count . into ( ) ) ;
633+
634+ #[ allow( clippy:: cast_possible_truncation) ]
635+ let start_idx = self . vcpus_handles . len ( ) . try_into ( ) . unwrap ( ) ;
636+ for cpu_idx in start_idx..( start_idx + config. vcpu_count ) {
637+ let exit_evt = self
638+ . vcpus_exit_evt
639+ . try_clone ( )
640+ . map_err ( HotplugVcpuError :: EventFd ) ?;
641+ let vcpu =
642+ Vcpu :: new ( cpu_idx, & self . vm , exit_evt) . map_err ( HotplugVcpuError :: VcpuCreate ) ?;
643+ vcpus. push ( vcpu) ;
644+ }
645+
646+ self . start_vcpus (
647+ vcpus,
648+ self . seccomp_filters
649+ . get ( "vcpu" )
650+ . ok_or_else ( || HotplugVcpuError :: MissingSeccompFilters ( "vcpu" . to_string ( ) ) ) ?
651+ . clone ( ) ,
652+ )
653+ . map_err ( HotplugVcpuError :: VcpuStart ) ?;
654+
655+ #[ allow( clippy:: cast_lossless) ]
656+ METRICS . hotplug . vcpus_added . add ( config. vcpu_count . into ( ) ) ;
657+
658+ // Update VM config to reflect new CPUs added
659+ #[ allow( clippy:: cast_possible_truncation) ]
660+ let new_machine_config = MachineConfigUpdate {
661+ vcpu_count : Some ( self . vcpus_handles . len ( ) as u8 ) ,
662+ mem_size_mib : None ,
663+ smt : None ,
664+ cpu_template : None ,
665+ track_dirty_pages : None ,
666+ huge_pages : None ,
667+ } ;
668+
669+ Ok ( new_machine_config)
670+ }
671+
603672 /// Retrieves the KVM dirty bitmap for each of the guest's memory regions.
604673 pub fn reset_dirty_bitmap ( & self ) {
605674 self . guest_memory
0 commit comments