Skip to content

Commit a541725

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 df9ddc9 commit a541725

File tree

6 files changed

+126
-32
lines changed

6 files changed

+126
-32
lines changed

src/vmm/src/builder.rs

+7
Original file line numberDiff line numberDiff line change
@@ -236,6 +236,8 @@ fn create_vmm_and_vcpus(
236236
vcpus_handles: Vec::new(),
237237
vcpus_exit_evt,
238238
#[cfg(target_arch = "x86_64")]
239+
vcpu_config: None,
240+
#[cfg(target_arch = "x86_64")]
239241
seccomp_filters,
240242
resource_allocator,
241243
mmio_device_manager,
@@ -858,6 +860,9 @@ pub fn configure_system_for_boot(
858860
cpu_config,
859861
};
860862

863+
#[cfg(target_arch = "x86_64")]
864+
vmm.attach_vcpu_config(vcpu_config.clone());
865+
861866
// Configure vCPUs with normalizing and setting the generated CPU configuration.
862867
for vcpu in vcpus.iter_mut() {
863868
vcpu.kvm_vcpu
@@ -1259,6 +1264,8 @@ pub mod tests {
12591264
vcpus_handles: Vec::new(),
12601265
vcpus_exit_evt,
12611266
#[cfg(target_arch = "x86_64")]
1267+
vcpu_config: None,
1268+
#[cfg(target_arch = "x86_64")]
12621269
seccomp_filters: crate::seccomp_filters::get_empty_filters(),
12631270
resource_allocator,
12641271
mmio_device_manager,

src/vmm/src/lib.rs

+35-6
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,9 @@ pub struct Vmm {
320320
vcpus_handles: Vec<VcpuHandle>,
321321
// Used by Vcpus and devices to initiate teardown; Vmm should never write here.
322322
vcpus_exit_evt: EventFd,
323+
// Used to configure kvm vcpus during hotplugging.
324+
#[cfg(target_arch = "x86_64")]
325+
vcpu_config: Option<VcpuConfig>,
323326
// seccomp_filters are only needed in VMM for hotplugging vCPUS.
324327
#[cfg(target_arch = "x86_64")]
325328
seccomp_filters: BpfThreadMap,
@@ -411,23 +414,32 @@ impl Vmm {
411414
pub fn resume_vm(&mut self) -> Result<(), VmmError> {
412415
self.mmio_device_manager.kick_devices();
413416

414-
// Send the events.
415-
self.vcpus_handles
417+
self.resume_vcpu_threads(0)?;
418+
419+
self.instance_info.state = VmState::Running;
420+
Ok(())
421+
}
422+
423+
/// Resume vCPU threads
424+
fn resume_vcpu_threads(&mut self, start_idx: usize) -> Result<(), VmmError> {
425+
if start_idx >= self.vcpus_handles.len() {
426+
return Err(VmmError::VcpuMessage);
427+
}
428+
429+
self.vcpus_handles[start_idx..]
416430
.iter()
417431
.try_for_each(|handle| handle.send_event(VcpuEvent::Resume))
418432
.map_err(|_| VmmError::VcpuMessage)?;
419433

420434
// Check the responses.
421-
if self
422-
.vcpus_handles
435+
if self.vcpus_handles[start_idx..]
423436
.iter()
424437
.map(|handle| handle.response_receiver().recv_timeout(RECV_TIMEOUT_SEC))
425438
.any(|response| !matches!(response, Ok(VcpuResponse::Resumed)))
426439
{
427440
return Err(VmmError::VcpuMessage);
428441
}
429442

430-
self.instance_info.state = VmState::Running;
431443
Ok(())
432444
}
433445

@@ -615,6 +627,9 @@ impl Vmm {
615627
return Err(HotplugVcpuError::VcpuCountTooHigh);
616628
}
617629

630+
if let Some(kvm_config) = self.vcpu_config.as_mut() {
631+
kvm_config.vcpu_count += config.add;
632+
}
618633
// Create and start new vcpus
619634
let mut vcpus = Vec::with_capacity(config.add.into());
620635

@@ -629,8 +644,13 @@ impl Vmm {
629644
.vcpus_exit_evt
630645
.try_clone()
631646
.map_err(HotplugVcpuError::EventFd)?;
632-
let vcpu =
647+
let mut vcpu =
633648
Vcpu::new(cpu_idx, &self.vm, exit_evt).map_err(HotplugVcpuError::VcpuCreate)?;
649+
if let Some(kvm_config) = self.vcpu_config.as_ref() {
650+
vcpu.kvm_vcpu.hotplug_configure(kvm_config)?;
651+
} else {
652+
return Err(HotplugVcpuError::RestoredFromSnapshot);
653+
}
634654
locked_container.cpu_devices[cpu_idx as usize].inserting = true;
635655
vcpus.push(vcpu);
636656
}
@@ -659,6 +679,8 @@ impl Vmm {
659679
huge_pages: None,
660680
};
661681

682+
self.resume_vcpu_threads(start_idx.into())?;
683+
662684
self.acpi_device_manager.notify_cpu_container()?;
663685

664686
Ok(new_machine_config)
@@ -871,6 +893,13 @@ impl Vmm {
871893
}
872894
}
873895

896+
/// Add the vcpu configuration used during boot to the VMM. This is required as part of the
897+
/// hotplugging process, to correctly configure KVM vCPUs.
898+
#[cfg(target_arch = "x86_64")]
899+
pub fn attach_vcpu_config(&mut self, vcpu_config: VcpuConfig) {
900+
self.vcpu_config = Some(vcpu_config)
901+
}
902+
874903
/// Signals Vmm to stop and exit.
875904
pub fn stop(&mut self, exit_code: FcExitCode) {
876905
// To avoid cycles, all teardown paths take the following route:

src/vmm/src/rpc_interface.rs

+18
Original file line numberDiff line numberDiff line change
@@ -2149,15 +2149,31 @@ mod tests {
21492149
#[test]
21502150
#[cfg(target_arch = "x86_64")]
21512151
fn test_runtime_hotplug_vcpu() {
2152+
use std::collections::BTreeMap;
2153+
2154+
use crate::cpu_config::x86_64::cpuid::Cpuid;
2155+
21522156
// Case 1. Valid input
21532157
let mut vmm = default_vmm();
2158+
let cpuid = Cpuid::try_from(vmm.vm.supported_cpuid().clone()).unwrap();
2159+
let vcpu_config = crate::VcpuConfig {
2160+
vcpu_count: 0,
2161+
smt: false,
2162+
cpu_config: crate::cpu_config::templates::CpuConfiguration {
2163+
cpuid,
2164+
msrs: BTreeMap::new(),
2165+
},
2166+
};
2167+
2168+
vmm.attach_vcpu_config(vcpu_config.clone());
21542169
let config = HotplugVcpuConfig { add: 4 };
21552170
let result = vmm.hotplug_vcpus(config);
21562171
assert_eq!(vmm.vcpus_handles.len(), 4);
21572172
result.unwrap();
21582173

21592174
// Case 2. Vcpu count too low
21602175
let mut vmm = default_vmm();
2176+
vmm.attach_vcpu_config(vcpu_config.clone());
21612177
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
21622178
assert_eq!(vmm.vcpus_handles.len(), 1);
21632179
let config = HotplugVcpuConfig { add: 0 };
@@ -2167,6 +2183,7 @@ mod tests {
21672183

21682184
// Case 3. Vcpu count too high
21692185
let mut vmm = default_vmm();
2186+
vmm.attach_vcpu_config(vcpu_config.clone());
21702187
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 1 }).unwrap();
21712188
assert_eq!(vmm.vcpus_handles.len(), 1);
21722189
let config = HotplugVcpuConfig { add: 33 };
@@ -2176,6 +2193,7 @@ mod tests {
21762193

21772194
// Case 4. Attempted overflow of vcpus
21782195
let mut vmm = default_vmm();
2196+
vmm.attach_vcpu_config(vcpu_config.clone());
21792197
vmm.hotplug_vcpus(HotplugVcpuConfig { add: 2 }).unwrap();
21802198
assert_eq!(vmm.vcpus_handles.len(), 2);
21812199
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::BTreeMap;
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};
@@ -178,22 +178,17 @@ impl KvmVcpu {
178178
})
179179
}
180180

181-
/// Configures a x86_64 specific vcpu for booting Linux and should be called once per vcpu.
182-
///
181+
/// General configuration - common for both boot and hotplugged CPUs.
182+
/// Normalizes and sets the CPUID in KVM and creates KVM MSRs
183183
/// # Arguments
184-
///
185-
/// * `guest_mem` - The guest memory used by this microvm.
186-
/// * `kernel_start_addr` - Offset from `guest_mem` at which the kernel starts.
187-
/// * `vcpu_config` - The vCPU configuration.
188-
/// * `cpuid` - The capabilities exposed by this vCPU.
189-
pub fn configure(
184+
/// * vcpu_config - The configuration for the vCPUs.
185+
/// * msrs - The MSRs currently present.
186+
fn configure_common(
190187
&mut self,
191-
guest_mem: &GuestMemoryMmap,
192-
kernel_start_addr: GuestAddress,
193188
vcpu_config: &VcpuConfig,
194-
) -> Result<(), KvmVcpuConfigureError> {
189+
msrs: std::collections::BTreeMap<u32, u64>,
190+
) -> Result<(Vec<kvm_msr_entry>, CpuId), KvmVcpuConfigureError> {
195191
let mut cpuid = vcpu_config.cpu_config.cpuid.clone();
196-
197192
// Apply machine specific changes to CPUID.
198193
cpuid.normalize(
199194
// The index of the current logical CPU in the range [0..cpu_count].
@@ -212,6 +207,35 @@ impl KvmVcpu {
212207
.set_cpuid2(&kvm_cpuid)
213208
.map_err(KvmVcpuConfigureError::SetCpuid)?;
214209

210+
// // Clone MSR entries that are modified by CPU template from `VcpuConfig`.
211+
212+
let kvm_msrs = msrs
213+
.clone()
214+
.into_iter()
215+
.map(|entry| kvm_bindings::kvm_msr_entry {
216+
index: entry.0,
217+
data: entry.1,
218+
..Default::default()
219+
})
220+
.collect::<Vec<_>>();
221+
222+
Ok((kvm_msrs, kvm_cpuid))
223+
}
224+
225+
/// Configures a x86_64 specific vcpu for booting Linux and should be called once per vcpu.
226+
///
227+
/// # Arguments
228+
///
229+
/// * `guest_mem` - The guest memory used by this microvm.
230+
/// * `kernel_start_addr` - Offset from `guest_mem` at which the kernel starts.
231+
/// * `vcpu_config` - The vCPU configuration.
232+
/// * `cpuid` - The capabilities exposed by this vCPU.
233+
pub fn configure(
234+
&mut self,
235+
guest_mem: &GuestMemoryMmap,
236+
kernel_start_addr: GuestAddress,
237+
vcpu_config: &VcpuConfig,
238+
) -> Result<(), KvmVcpuConfigureError> {
215239
// Clone MSR entries that are modified by CPU template from `VcpuConfig`.
216240
let mut msrs = vcpu_config.cpu_config.msrs.clone();
217241
self.msrs_to_save.extend(msrs.keys());
@@ -221,6 +245,8 @@ impl KvmVcpu {
221245
msrs.insert(entry.index, entry.data);
222246
});
223247

248+
let (kvm_msrs, kvm_cpuid) = self.configure_common(vcpu_config, msrs)?;
249+
224250
// TODO - Add/amend MSRs for vCPUs based on cpu_config
225251
// By this point the Guest CPUID is established. Some CPU features require MSRs
226252
// to configure and interact with those features. If a MSR is writable from
@@ -239,15 +265,6 @@ impl KvmVcpu {
239265
// save is `architectural MSRs` + `MSRs inferred through CPUID` + `other
240266
// MSRs defined by the template`
241267

242-
let kvm_msrs = msrs
243-
.into_iter()
244-
.map(|entry| kvm_bindings::kvm_msr_entry {
245-
index: entry.0,
246-
data: entry.1,
247-
..Default::default()
248-
})
249-
.collect::<Vec<_>>();
250-
251268
crate::arch::x86_64::msr::set_msrs(&self.fd, &kvm_msrs)?;
252269
crate::arch::x86_64::regs::setup_regs(&self.fd, kernel_start_addr.raw_value())?;
253270
crate::arch::x86_64::regs::setup_fpu(&self.fd)?;
@@ -257,6 +274,25 @@ impl KvmVcpu {
257274
Ok(())
258275
}
259276

277+
/// Configures an x86_64 cpu that has been hotplugged post-boot. Called once per hotplugged
278+
/// vcpu.
279+
///
280+
/// # Arguments
281+
///
282+
/// * `vcpu_config` - The config to be applied to the vCPU, defined at boot time.
283+
pub fn hotplug_configure(
284+
&mut self,
285+
vcpu_config: &VcpuConfig,
286+
) -> Result<(), KvmVcpuConfigureError> {
287+
let msrs = vcpu_config.cpu_config.msrs.clone();
288+
let (kvm_msrs, _) = self.configure_common(vcpu_config, msrs)?;
289+
290+
crate::arch::x86_64::msr::set_msrs(&self.fd, &kvm_msrs)?;
291+
crate::arch::x86_64::interrupts::set_lint(&self.fd)?;
292+
293+
Ok(())
294+
}
295+
260296
/// Sets a Port Mapped IO bus for this vcpu.
261297
pub fn set_pio_bus(&mut self, pio_bus: crate::devices::Bus) {
262298
self.peripherals.pio_bus = Some(pio_bus);

0 commit comments

Comments
 (0)