@@ -600,6 +600,92 @@ impl Vmm {
600600 Ok ( cpu_configs)
601601 }
602602
603+ /// Adds new vCPUs to VMM.
604+ #[ cfg( target_arch = "x86_64" ) ]
605+ pub fn hotplug_vcpus (
606+ & mut self ,
607+ config : HotplugVcpuConfig ,
608+ ) -> Result < MachineConfigUpdate , HotplugVcpuError > {
609+ use crate :: logger:: IncMetric ;
610+ if config. add < 1 {
611+ return Err ( HotplugVcpuError :: VcpuCountTooLow ) ;
612+ } else if self
613+ . vcpus_handles
614+ . len ( )
615+ . checked_add ( config. add . into ( ) )
616+ . ok_or ( HotplugVcpuError :: VcpuCountTooHigh ) ?
617+ > MAX_SUPPORTED_VCPUS . into ( )
618+ {
619+ return Err ( HotplugVcpuError :: VcpuCountTooHigh ) ;
620+ }
621+
622+ if let Some ( kvm_config) = self . vcpu_config . as_mut ( ) {
623+ kvm_config. vcpu_count += config. add ;
624+ }
625+ // Create and start new vcpus
626+ let mut vcpus = Vec :: with_capacity ( config. add . into ( ) ) ;
627+
628+ #[ allow( clippy:: cast_possible_truncation) ]
629+ let start_idx = self . vcpus_handles . len ( ) . try_into ( ) . unwrap ( ) ;
630+ if let Some ( devices:: BusDevice :: CpuContainer ( cont) ) =
631+ self . get_bus_device ( DeviceType :: CpuContainer , "CpuContainer" )
632+ {
633+ let mut locked_container = cont. lock ( ) . expect ( "Poisoned lock" ) ;
634+ for cpu_idx in start_idx..( start_idx + config. add ) {
635+ let exit_evt = self
636+ . vcpus_exit_evt
637+ . try_clone ( )
638+ . map_err ( HotplugVcpuError :: EventFd ) ?;
639+ let mut vcpu =
640+ Vcpu :: new ( cpu_idx, & self . vm , exit_evt) . map_err ( HotplugVcpuError :: VcpuCreate ) ?;
641+ if let Some ( kvm_config) = self . vcpu_config . as_ref ( ) {
642+ vcpu. kvm_vcpu . hotplug_configure ( kvm_config) ?;
643+ } else {
644+ return Err ( HotplugVcpuError :: RestoredFromSnapshot ) ;
645+ }
646+ locked_container. cpu_devices [ cpu_idx as usize ] . inserting = true ;
647+ vcpus. push ( vcpu) ;
648+ }
649+ }
650+
651+ self . start_vcpus (
652+ vcpus,
653+ self . seccomp_filters
654+ . get ( "vcpu" )
655+ . ok_or_else ( || HotplugVcpuError :: MissingSeccompFilters ( "vcpu" . to_string ( ) ) ) ?
656+ . clone ( ) ,
657+ )
658+ . map_err ( HotplugVcpuError :: VcpuStart ) ?;
659+
660+ #[ allow( clippy:: cast_lossless) ]
661+ METRICS . hotplug . vcpus_added . add ( config. add . into ( ) ) ;
662+
663+ // Update VM config to reflect new CPUs added
664+ #[ allow( clippy:: cast_possible_truncation) ]
665+ let new_machine_config = MachineConfigUpdate {
666+ vcpu_count : Some ( self . vcpus_handles . len ( ) as u8 ) ,
667+ mem_size_mib : None ,
668+ smt : None ,
669+ cpu_template : None ,
670+ track_dirty_pages : None ,
671+ huge_pages : None ,
672+ } ;
673+
674+ self . resume_vcpu_threads ( start_idx. into ( ) ) ?;
675+
676+ self . acpi_device_manager . notify_cpu_container ( ) ?;
677+
678+ #[ cfg( test) ]
679+ if let Some ( devices:: BusDevice :: BootTimer ( timer) ) =
680+ self . get_bus_device ( DeviceType :: BootTimer , "BootTimer" )
681+ {
682+ let mut locked_timer = timer. lock ( ) . expect ( "Poisoned lock" ) ;
683+ locked_timer. start_ts = utils:: time:: TimestampUs :: default ( ) ;
684+ }
685+
686+ Ok ( new_machine_config)
687+ }
688+
603689 /// Retrieves the KVM dirty bitmap for each of the guest's memory regions.
604690 pub fn reset_dirty_bitmap ( & self ) {
605691 self . guest_memory
0 commit comments