Skip to content

Commit 9cbec99

Browse files
committed
Configure KVM vCPUS and resume new threads after creation
Properly configure KVM vCPUs so that the configuration is normalized and matches that of already existing KVM vCPUs. Signed-off-by: James Curtis <[email protected]>
1 parent 4df4135 commit 9cbec99

File tree

6 files changed

+125
-33
lines changed

6 files changed

+125
-33
lines changed

src/vmm/src/builder.rs

+7
Original file line numberDiff line numberDiff line change
@@ -243,6 +243,8 @@ fn create_vmm_and_vcpus(
243243
vcpus_handles: Vec::new(),
244244
vcpus_exit_evt,
245245
#[cfg(target_arch = "x86_64")]
246+
vcpu_config: None,
247+
#[cfg(target_arch = "x86_64")]
246248
seccomp_filters,
247249
resource_allocator,
248250
mmio_device_manager,
@@ -860,6 +862,9 @@ pub fn configure_system_for_boot(
860862
cpu_config,
861863
};
862864

865+
#[cfg(target_arch = "x86_64")]
866+
vmm.attach_vcpu_config(vcpu_config.clone());
867+
863868
// Configure vCPUs with normalizing and setting the generated CPU configuration.
864869
for vcpu in vcpus.iter_mut() {
865870
vcpu.kvm_vcpu
@@ -1258,6 +1263,8 @@ pub mod tests {
12581263
vcpus_handles: Vec::new(),
12591264
vcpus_exit_evt,
12601265
#[cfg(target_arch = "x86_64")]
1266+
vcpu_config: None,
1267+
#[cfg(target_arch = "x86_64")]
12611268
seccomp_filters: crate::seccomp_filters::get_empty_filters(),
12621269
resource_allocator,
12631270
mmio_device_manager,

src/vmm/src/lib.rs

+35-6
Original file line numberDiff line numberDiff line change
@@ -324,6 +324,9 @@ pub struct Vmm {
324324
vcpus_handles: Vec<VcpuHandle>,
325325
// Used by Vcpus and devices to initiate teardown; Vmm should never write here.
326326
vcpus_exit_evt: EventFd,
327+
// Used to configure kvm vcpus during hotplugging.
328+
#[cfg(target_arch = "x86_64")]
329+
vcpu_config: Option<VcpuConfig>,
327330
// seccomp_filters are only needed in VMM for hotplugging vCPUS.
328331
#[cfg(target_arch = "x86_64")]
329332
seccomp_filters: BpfThreadMap,
@@ -416,23 +419,32 @@ impl Vmm {
416419
pub fn resume_vm(&mut self) -> Result<(), VmmError> {
417420
self.mmio_device_manager.kick_devices();
418421

419-
// Send the events.
420-
self.vcpus_handles
422+
self.resume_vcpu_threads(0)?;
423+
424+
self.instance_info.state = VmState::Running;
425+
Ok(())
426+
}
427+
428+
/// Resume vCPU threads
429+
fn resume_vcpu_threads(&mut self, start_idx: usize) -> Result<(), VmmError> {
430+
if start_idx >= self.vcpus_handles.len() {
431+
return Err(VmmError::VcpuMessage);
432+
}
433+
434+
self.vcpus_handles[start_idx..]
421435
.iter()
422436
.try_for_each(|handle| handle.send_event(VcpuEvent::Resume))
423437
.map_err(|_| VmmError::VcpuMessage)?;
424438

425439
// Check the responses.
426-
if self
427-
.vcpus_handles
440+
if self.vcpus_handles[start_idx..]
428441
.iter()
429442
.map(|handle| handle.response_receiver().recv_timeout(RECV_TIMEOUT_SEC))
430443
.any(|response| !matches!(response, Ok(VcpuResponse::Resumed)))
431444
{
432445
return Err(VmmError::VcpuMessage);
433446
}
434447

435-
self.instance_info.state = VmState::Running;
436448
Ok(())
437449
}
438450

@@ -622,6 +634,9 @@ impl Vmm {
622634
return Err(HotplugVcpuError::VcpuCountTooHigh);
623635
}
624636

637+
if let Some(kvm_config) = self.vcpu_config.as_mut() {
638+
kvm_config.vcpu_count += config.add;
639+
}
625640
// Create and start new vcpus
626641
let mut vcpus = Vec::with_capacity(config.add.into());
627642

@@ -636,8 +651,13 @@ impl Vmm {
636651
.vcpus_exit_evt
637652
.try_clone()
638653
.map_err(HotplugVcpuError::EventFd)?;
639-
let vcpu =
654+
let mut vcpu =
640655
Vcpu::new(cpu_idx, &self.vm, exit_evt).map_err(HotplugVcpuError::VcpuCreate)?;
656+
if let Some(kvm_config) = self.vcpu_config.as_ref() {
657+
vcpu.kvm_vcpu.hotplug_configure(kvm_config)?;
658+
} else {
659+
return Err(HotplugVcpuError::RestoredFromSnapshot);
660+
}
641661
locked_container.cpu_devices[cpu_idx as usize].inserting = true;
642662
vcpus.push(vcpu);
643663
}
@@ -666,6 +686,8 @@ impl Vmm {
666686
huge_pages: None,
667687
};
668688

689+
self.resume_vcpu_threads(start_idx.into())?;
690+
669691
self.acpi_device_manager.notify_cpu_container()?;
670692

671693
Ok(new_machine_config)
@@ -878,6 +900,13 @@ impl Vmm {
878900
}
879901
}
880902

903+
/// Add the vcpu configuration used during boot to the VMM. This is required as part of the
904+
/// hotplugging process, to correctly configure KVM vCPUs.
905+
#[cfg(target_arch = "x86_64")]
906+
pub fn attach_vcpu_config(&mut self, vcpu_config: VcpuConfig) {
907+
self.vcpu_config = Some(vcpu_config)
908+
}
909+
881910
/// Signals Vmm to stop and exit.
882911
pub fn stop(&mut self, exit_code: FcExitCode) {
883912
// To avoid cycles, all teardown paths take the following route:

src/vmm/src/rpc_interface.rs

+17-1
Original file line numberDiff line numberDiff line change
@@ -909,7 +909,7 @@ mod tests {
909909
#[cfg(target_arch = "x86_64")]
910910
use crate::builder::tests::default_vmm;
911911
use crate::cpu_config::templates::test_utils::build_test_template;
912-
use crate::cpu_config::templates::{CpuTemplateType, StaticCpuTemplate};
912+
use crate::cpu_config::templates::{CpuConfiguration, CpuTemplateType, StaticCpuTemplate};
913913
use crate::devices::virtio::balloon::{BalloonConfig, BalloonError};
914914
use crate::devices::virtio::block::CacheType;
915915
use crate::devices::virtio::rng::EntropyError;
@@ -2149,15 +2149,29 @@ mod tests {
21492149
#[test]
21502150
#[cfg(target_arch = "x86_64")]
21512151
fn test_runtime_hotplug_vcpu() {
2152+
use crate::cpu_config::x86_64::cpuid::Cpuid;
2153+
21522154
// Case 1. Valid input
21532155
let mut vmm = default_vmm();
2156+
let cpuid = Cpuid::try_from(vmm.vm.supported_cpuid().clone()).unwrap();
2157+
let vcpu_config = crate::VcpuConfig {
2158+
vcpu_count: 0,
2159+
smt: false,
2160+
cpu_config: CpuConfiguration {
2161+
cpuid,
2162+
msrs: std::collections::HashMap::new(),
2163+
},
2164+
};
2165+
2166+
vmm.attach_vcpu_config(vcpu_config.clone());
21542167
let config = HotplugVcpuConfig { add: 4 };
21552168
let result = vmm.hotplug_vcpus(config);
21562169
assert_eq!(vmm.vcpus_handles.len(), 4);
21572170
result.unwrap();
21582171

21592172
// Case 2. Vcpu count too low
21602173
let mut vmm = default_vmm();
2174+
vmm.attach_vcpu_config(vcpu_config.clone());
21612175
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
21622176
assert_eq!(vmm.vcpus_handles.len(), 1);
21632177
let config = HotplugVcpuConfig { add: 0 };
@@ -2167,6 +2181,7 @@ mod tests {
21672181

21682182
// Case 3. Vcpu count too high
21692183
let mut vmm = default_vmm();
2184+
vmm.attach_vcpu_config(vcpu_config.clone());
21702185
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
21712186
assert_eq!(vmm.vcpus_handles.len(), 1);
21722187
let config = HotplugVcpuConfig { add: 33 };
@@ -2176,6 +2191,7 @@ mod tests {
21762191

21772192
// Case 4. Attempted overflow of vcpus
21782193
let mut vmm = default_vmm();
2194+
vmm.attach_vcpu_config(vcpu_config.clone());
21792195
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 2 }).unwrap();
21802196
assert_eq!(vmm.vcpus_handles.len(), 2);
21812197
let config = HotplugVcpuConfig { add: 255 };

src/vmm/src/vmm_config/hotplug.rs

+6-2
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,8 @@ use std::io;
55

66
use serde::{Deserialize, Serialize};
77

8-
use crate::vstate::vcpu::VcpuError;
9-
use crate::StartVcpusError;
8+
use crate::vstate::vcpu::{KvmVcpuConfigureError, VcpuError};
9+
use crate::{StartVcpusError, VmmError};
1010
/// Unifying enum for all types of hotplug request configs.
1111
/// Currently only Vcpus may be hotplugged.
1212
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
@@ -34,10 +34,14 @@ pub enum HotplugVcpuError {
3434
EventFd(#[from] io::Error),
3535
/// Error creating the vcpu: {0}
3636
VcpuCreate(VcpuError),
37+
/// Error configuring KVM vcpu: {0}
38+
VcpuConfigure(#[from] KvmVcpuConfigureError),
3739
/// Failed to start vCPUs
3840
VcpuStart(StartVcpusError),
3941
/// No seccomp filter for thread category: {0}
4042
MissingSeccompFilters(String),
43+
/// Error resuming VM: {0}
44+
VmResume(#[from] VmmError),
4145
/// Cannot hotplug vCPUs after restoring from snapshot
4246
RestoredFromSnapshot,
4347
}

src/vmm/src/vstate/vcpu/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ pub enum VcpuError {
6363
}
6464

6565
/// Encapsulates configuration parameters for the guest vCPUS.
66-
#[derive(Debug)]
66+
#[derive(Debug, Clone)]
6767
pub struct VcpuConfig {
6868
/// Number of guest VCPUs.
6969
pub vcpu_count: u8,

src/vmm/src/vstate/vcpu/x86_64.rs

+59-23
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ use std::collections::{HashMap, HashSet};
99
use std::fmt::Debug;
1010

1111
use kvm_bindings::{
12-
kvm_debugregs, kvm_lapic_state, kvm_mp_state, kvm_regs, kvm_sregs, kvm_vcpu_events, kvm_xcrs,
13-
kvm_xsave, CpuId, Msrs, KVM_MAX_CPUID_ENTRIES, KVM_MAX_MSR_ENTRIES,
12+
kvm_debugregs, kvm_lapic_state, kvm_mp_state, kvm_msr_entry, kvm_regs, kvm_sregs,
13+
kvm_vcpu_events, kvm_xcrs, kvm_xsave, CpuId, Msrs, KVM_MAX_CPUID_ENTRIES, KVM_MAX_MSR_ENTRIES,
1414
};
1515
use kvm_ioctls::{VcpuExit, VcpuFd};
1616
use log::{error, warn};
@@ -165,22 +165,17 @@ impl KvmVcpu {
165165
})
166166
}
167167

168-
/// Configures a x86_64 specific vcpu for booting Linux and should be called once per vcpu.
169-
///
168+
/// General configuration - common for both boot and hotplugged CPUs.
169+
/// Normalizes and sets the CPUID in KVM and creates KVM MSRs
170170
/// # Arguments
171-
///
172-
/// * `guest_mem` - The guest memory used by this microvm.
173-
/// * `kernel_start_addr` - Offset from `guest_mem` at which the kernel starts.
174-
/// * `vcpu_config` - The vCPU configuration.
175-
/// * `cpuid` - The capabilities exposed by this vCPU.
176-
pub fn configure(
171+
/// * vcpu_config - The configuration for the vCPUs.
172+
/// * msrs - The MSRs currently present.
173+
fn configure_common(
177174
&mut self,
178-
guest_mem: &GuestMemoryMmap,
179-
kernel_start_addr: GuestAddress,
180175
vcpu_config: &VcpuConfig,
181-
) -> Result<(), KvmVcpuConfigureError> {
176+
msrs: HashMap<u32, u64>,
177+
) -> Result<(Vec<kvm_msr_entry>, CpuId), KvmVcpuConfigureError> {
182178
let mut cpuid = vcpu_config.cpu_config.cpuid.clone();
183-
184179
// Apply machine specific changes to CPUID.
185180
cpuid.normalize(
186181
// The index of the current logical CPU in the range [0..cpu_count].
@@ -199,6 +194,35 @@ impl KvmVcpu {
199194
.set_cpuid2(&kvm_cpuid)
200195
.map_err(KvmVcpuConfigureError::SetCpuid)?;
201196

197+
// // Clone MSR entries that are modified by CPU template from `VcpuConfig`.
198+
199+
let kvm_msrs = msrs
200+
.clone()
201+
.into_iter()
202+
.map(|entry| kvm_bindings::kvm_msr_entry {
203+
index: entry.0,
204+
data: entry.1,
205+
..Default::default()
206+
})
207+
.collect::<Vec<_>>();
208+
209+
Ok((kvm_msrs, kvm_cpuid))
210+
}
211+
212+
/// Configures a x86_64 specific vcpu for booting Linux and should be called once per vcpu.
213+
///
214+
/// # Arguments
215+
///
216+
/// * `guest_mem` - The guest memory used by this microvm.
217+
/// * `kernel_start_addr` - Offset from `guest_mem` at which the kernel starts.
218+
/// * `vcpu_config` - The vCPU configuration.
219+
/// * `cpuid` - The capabilities exposed by this vCPU.
220+
pub fn configure(
221+
&mut self,
222+
guest_mem: &GuestMemoryMmap,
223+
kernel_start_addr: GuestAddress,
224+
vcpu_config: &VcpuConfig,
225+
) -> Result<(), KvmVcpuConfigureError> {
202226
// Clone MSR entries that are modified by CPU template from `VcpuConfig`.
203227
let mut msrs = vcpu_config.cpu_config.msrs.clone();
204228
self.msrs_to_save.extend(msrs.keys());
@@ -208,6 +232,8 @@ impl KvmVcpu {
208232
msrs.insert(entry.index, entry.data);
209233
});
210234

235+
let (kvm_msrs, kvm_cpuid) = self.configure_common(vcpu_config, msrs)?;
236+
211237
// TODO - Add/amend MSRs for vCPUs based on cpu_config
212238
// By this point the Guest CPUID is established. Some CPU features require MSRs
213239
// to configure and interact with those features. If a MSR is writable from
@@ -226,15 +252,6 @@ impl KvmVcpu {
226252
// save is `architectural MSRs` + `MSRs inferred through CPUID` + `other
227253
// MSRs defined by the template`
228254

229-
let kvm_msrs = msrs
230-
.into_iter()
231-
.map(|entry| kvm_bindings::kvm_msr_entry {
232-
index: entry.0,
233-
data: entry.1,
234-
..Default::default()
235-
})
236-
.collect::<Vec<_>>();
237-
238255
crate::arch::x86_64::msr::set_msrs(&self.fd, &kvm_msrs)?;
239256
crate::arch::x86_64::regs::setup_regs(&self.fd, kernel_start_addr.raw_value())?;
240257
crate::arch::x86_64::regs::setup_fpu(&self.fd)?;
@@ -244,6 +261,25 @@ impl KvmVcpu {
244261
Ok(())
245262
}
246263

264+
/// Configures an x86_64 cpu that has been hotplugged post-boot. Called once per hotplugged
265+
/// vcpu.
266+
///
267+
/// # Arguments
268+
///
269+
/// * `vcpu_config` - The config to be applied to the vCPU, defined at boot time.
270+
pub fn hotplug_configure(
271+
&mut self,
272+
vcpu_config: &VcpuConfig,
273+
) -> Result<(), KvmVcpuConfigureError> {
274+
let msrs = vcpu_config.cpu_config.msrs.clone();
275+
let (kvm_msrs, _) = self.configure_common(vcpu_config, msrs)?;
276+
277+
crate::arch::x86_64::msr::set_msrs(&self.fd, &kvm_msrs)?;
278+
crate::arch::x86_64::interrupts::set_lint(&self.fd)?;
279+
280+
Ok(())
281+
}
282+
247283
/// Sets a Port Mapped IO bus for this vcpu.
248284
pub fn set_pio_bus(&mut self, pio_bus: crate::devices::Bus) {
249285
self.peripherals.pio_bus = Some(pio_bus);

0 commit comments

Comments
 (0)