Skip to content

Commit 7aa4ea9

Browse files
committed
Refactor PVTime design
- Moved most PVTime logic into pvtime.rs - Fixed includes for only aarch64 - PVTime only uses base_addr now - Made clippy happy Signed-off-by: Dakshin Devanand <[email protected]>
1 parent 2b79bca commit 7aa4ea9

File tree

4 files changed

+98
-89
lines changed

4 files changed

+98
-89
lines changed

Diff for: src/vmm/src/arch/aarch64/pvtime/mod.rs renamed to src/vmm/src/arch/aarch64/pvtime.rs

+56-34
Original file line numberDiff line numberDiff line change
@@ -1,56 +1,55 @@
11
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
3-
use std::collections::HashMap;
4-
53
use displaydoc::Display;
64
use kvm_bindings::{KVM_ARM_VCPU_PVTIME_CTRL, KVM_ARM_VCPU_PVTIME_IPA};
5+
use kvm_ioctls::VcpuFd;
76
use serde::{Deserialize, Serialize};
87
use thiserror::Error;
98
use vm_memory::GuestAddress;
109

10+
use crate::Vcpu;
1111
use crate::device_manager::resources::ResourceAllocator;
1212
use crate::snapshot::Persist;
1313

1414
/// 64 bytes due to alignment requirement in 3.1 of https://www.kernel.org/doc/html/v5.8/virt/kvm/devices/vcpu.html#attribute-kvm-arm-vcpu-pvtime-ipa
1515
pub const STEALTIME_STRUCT_MEM_SIZE: u64 = 64;
1616

1717
/// Represent PVTime device for ARM
18-
/// TODO: Decide whether we want to keep the hashmap OR the base IPA
1918
#[derive(Debug)]
2019
pub struct PVTime {
21-
/// Maps vCPU index to IPA location of stolen_time struct as defined in DEN0057A
22-
steal_time_regions: HashMap<u8, u64>,
20+
/// Number of vCPUs
21+
vcpu_count: u8,
2322
/// The base IPA of the shared memory region
24-
base_ipa: u64,
23+
base_ipa: GuestAddress,
2524
}
2625

2726
/// Errors associated with PVTime operations
2827
#[derive(Debug, Error, Display, PartialEq, Eq)]
2928
pub enum PVTimeError {
3029
/// Failed to allocate memory region: {0}
31-
AllocationFailed(String),
30+
AllocationFailed(vm_allocator::Error),
3231
/// Invalid VCPU ID: {0}
3332
InvalidVcpuIndex(u8),
34-
/// Error while setting or getting device attributes for vCPU: {0}, {1}, {2}
35-
DeviceAttribute(kvm_ioctls::Error, bool, u32),
33+
/// Error while setting or getting device attributes for vCPU: {0}
34+
DeviceAttribute(kvm_ioctls::Error),
3635
}
3736

3837
impl PVTime {
39-
/// Create a new PVTime device given a base addr
40-
/// - Assumes total shared memory region from base addr is already allocated
41-
fn from_base(base_addr: GuestAddress, vcpu_count: u8) -> Self {
42-
let base_ipa: u64 = base_addr.0;
43-
44-
// Now we need to store the base IPA for each vCPU's steal_time struct.
45-
let mut steal_time_regions = HashMap::new();
46-
for i in 0..vcpu_count {
47-
let ipa = base_ipa + (i as u64 * STEALTIME_STRUCT_MEM_SIZE);
48-
steal_time_regions.insert(i, ipa);
38+
/// Helper function to get the IPA of the steal_time region for a given vCPU
39+
fn get_steal_time_region_addr(&self, vcpu_index: u8) -> Result<GuestAddress, PVTimeError> {
40+
if vcpu_index >= self.vcpu_count {
41+
return Err(PVTimeError::InvalidVcpuIndex(vcpu_index));
4942
}
43+
Ok(GuestAddress(
44+
self.base_ipa.0 + (vcpu_index as u64 * STEALTIME_STRUCT_MEM_SIZE),
45+
))
46+
}
5047

51-
// Return the PVTime device with the steal_time region IPAs mapped to vCPU indices.
48+
/// Create a new PVTime device given a base addr
49+
/// - Assumes total shared memory region from base addr is already allocated
50+
fn from_base(base_ipa: GuestAddress, vcpu_count: u8) -> Self {
5251
PVTime {
53-
steal_time_regions,
52+
vcpu_count,
5453
base_ipa,
5554
}
5655
}
@@ -65,41 +64,61 @@ impl PVTime {
6564
resource_allocator
6665
.allocate_system_memory(
6766
STEALTIME_STRUCT_MEM_SIZE * vcpu_count as u64,
68-
64,
67+
STEALTIME_STRUCT_MEM_SIZE,
6968
vm_allocator::AllocPolicy::LastMatch,
7069
)
71-
.map_err(|e| PVTimeError::AllocationFailed(e.to_string()))?,
70+
.map_err(PVTimeError::AllocationFailed)?,
7271
);
73-
7472
Ok(Self::from_base(base_ipa, vcpu_count))
7573
}
7674

75+
/// Check if PVTime is supported on vcpu
76+
pub fn is_supported(vcpu_fd: &VcpuFd) -> bool {
77+
// Check if pvtime is enabled
78+
let pvtime_device_attr = kvm_bindings::kvm_device_attr {
79+
group: kvm_bindings::KVM_ARM_VCPU_PVTIME_CTRL,
80+
attr: kvm_bindings::KVM_ARM_VCPU_PVTIME_IPA as u64,
81+
addr: 0,
82+
flags: 0,
83+
};
84+
85+
// Use kvm_has_device_attr to check if PVTime is supported
86+
vcpu_fd.has_device_attr(&pvtime_device_attr).is_ok()
87+
}
88+
7789
/// Register a vCPU with its pre-allocated steal time region
78-
pub fn register_vcpu(
90+
fn register_vcpu(
7991
&self,
8092
vcpu_index: u8,
8193
vcpu_fd: &kvm_ioctls::VcpuFd,
8294
) -> Result<(), PVTimeError> {
8395
// Get IPA of the steal_time region for this vCPU
84-
let ipa = self
85-
.steal_time_regions
86-
.get(&vcpu_index)
87-
.ok_or(PVTimeError::InvalidVcpuIndex(vcpu_index))?;
96+
let ipa = self.get_steal_time_region_addr(vcpu_index)?;
8897

8998
// Use KVM syscall (kvm_set_device_attr) to register the vCPU with the steal_time region
9099
let vcpu_device_attr = kvm_bindings::kvm_device_attr {
91100
group: KVM_ARM_VCPU_PVTIME_CTRL,
92101
attr: KVM_ARM_VCPU_PVTIME_IPA as u64,
93-
addr: ipa as *const u64 as u64, // userspace address of attr data
102+
addr: &ipa.0 as *const u64 as u64, // userspace address of attr data
94103
flags: 0,
95104
};
96105

97106
vcpu_fd
98107
.set_device_attr(&vcpu_device_attr)
99-
.map_err(|err| PVTimeError::DeviceAttribute(err, true, KVM_ARM_VCPU_PVTIME_CTRL))?;
108+
.map_err(PVTimeError::DeviceAttribute)?;
100109

101110
Ok(())
102111
}
112+
113+
/// Register all vCPUs with their pre-allocated steal time regions
114+
pub fn register_all_vcpus(&self, vcpus: &mut [Vcpu]) -> Result<(), PVTimeError> {
115+
// Register the vcpu with the pvtime device to map its steal time region
116+
for (i, vcpu) in vcpus.iter().enumerate() {
117+
#[allow(clippy::cast_possible_truncation)] // We know vcpu_count is u8 according to VcpuConfig
118+
self.register_vcpu(i as u8, &vcpu.kvm_vcpu.fd)?;
119+
}
120+
Ok(())
121+
}
103122
}
104123

105124
/// Logic to save/restore the state of a PVTime device
@@ -109,9 +128,12 @@ pub struct PVTimeState {
109128
pub base_ipa: u64,
110129
}
111130

131+
/// Arguments to restore a PVTime device from PVTimeState
112132
#[derive(Debug)]
113133
pub struct PVTimeConstructorArgs<'a> {
134+
/// For steal_time shared memory region
114135
pub resource_allocator: &'a mut ResourceAllocator,
136+
/// Number of vCPUs (should be consistent with pre-snapshot state)
115137
pub vcpu_count: u8,
116138
}
117139

@@ -123,7 +145,7 @@ impl<'a> Persist<'a> for PVTime {
123145
/// Save base IPA of PVTime device for persistence
124146
fn save(&self) -> Self::State {
125147
PVTimeState {
126-
base_ipa: self.base_ipa,
148+
base_ipa: self.base_ipa.0,
127149
}
128150
}
129151

@@ -136,10 +158,10 @@ impl<'a> Persist<'a> for PVTime {
136158
.resource_allocator
137159
.allocate_system_memory(
138160
STEALTIME_STRUCT_MEM_SIZE * constructor_args.vcpu_count as u64,
139-
64,
161+
STEALTIME_STRUCT_MEM_SIZE,
140162
vm_allocator::AllocPolicy::ExactMatch(state.base_ipa),
141163
)
142-
.map_err(|e| PVTimeError::AllocationFailed(e.to_string()))?;
164+
.map_err(PVTimeError::AllocationFailed)?;
143165
Ok(Self::from_base(
144166
GuestAddress(state.base_ipa),
145167
constructor_args.vcpu_count,

Diff for: src/vmm/src/builder.rs

+36-53
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ use vm_superio::Rtc;
1919
use vm_superio::Serial;
2020
use vmm_sys_util::eventfd::EventFd;
2121

22+
#[cfg(target_arch = "aarch64")]
2223
use crate::arch::aarch64::pvtime::{PVTime, PVTimeConstructorArgs, PVTimeError};
2324
use crate::arch::{ConfigurationError, configure_system_for_boot, load_kernel};
2425
#[cfg(target_arch = "aarch64")]
@@ -70,9 +71,6 @@ pub enum StartMicrovmError {
7071
AttachBlockDevice(io::Error),
7172
/// Unable to attach the VMGenID device: {0}
7273
AttachVmgenidDevice(kvm_ioctls::Error),
73-
/// PVTime not supported: {0}
74-
#[cfg(target_arch = "aarch64")]
75-
PVTimeNotSupported(kvm_ioctls::Error),
7674
/// System configuration error: {0}
7775
ConfigureSystem(#[from] ConfigurationError),
7876
/// Failed to create guest config: {0}
@@ -195,6 +193,7 @@ fn create_vmm_and_vcpus(
195193
#[cfg(target_arch = "x86_64")]
196194
pio_device_manager,
197195
acpi_device_manager,
196+
#[cfg(target_arch = "aarch64")]
198197
pv_time: None,
199198
};
200199

@@ -298,13 +297,13 @@ pub fn build_microvm_for_boot(
298297

299298
// Attempt to setup PVTime, continue if not supported
300299
#[cfg(target_arch = "aarch64")]
301-
if let Err(e) = setup_pv_time(&mut vmm, vcpus.as_mut()) {
302-
match e {
303-
StartMicrovmError::PVTimeNotSupported(e) => {
304-
warn!("PVTime not supported: {}", e);
305-
}
306-
other => return Err(other),
307-
}
300+
{
301+
vmm.pv_time = if PVTime::is_supported(&vcpus[0].kvm_vcpu.fd) {
302+
Some(setup_pv_time(&mut vmm, vcpus.as_mut())?)
303+
} else {
304+
warn!("PVTime is not supported by KVM. Steal time will not be reported to the guest.");
305+
None
306+
};
308307
}
309308

310309
configure_system_for_boot(
@@ -474,18 +473,25 @@ pub fn build_microvm_from_snapshot(
474473
}
475474

476475
// Restore the PVTime device
477-
let pvtime_state = microvm_state.pvtime_state;
478-
if let Some(pvtime_state) = pvtime_state {
479-
let pvtime_ctor_args = PVTimeConstructorArgs {
480-
resource_allocator: &mut vmm.resource_allocator,
481-
vcpu_count: vcpus.len() as u8,
482-
};
483-
vmm.pv_time = Some(
484-
PVTime::restore(pvtime_ctor_args, &pvtime_state)
485-
.map_err(BuildMicrovmFromSnapshotError::RestorePVTime)?,
486-
);
487-
setup_pv_time_with(vmm.pv_time.as_ref().unwrap(), &mut vcpus) // We can force unwrap here b/c we just set it
488-
.map_err(BuildMicrovmFromSnapshotError::RestorePVTime)?;
476+
#[cfg(target_arch = "aarch64")]
477+
{
478+
let pvtime_state = microvm_state.pvtime_state;
479+
if let Some(pvtime_state) = pvtime_state {
480+
#[allow(clippy::cast_possible_truncation)] // We know vcpu_count is u8 according to VcpuConfig
481+
let pvtime_ctor_args = PVTimeConstructorArgs {
482+
resource_allocator: &mut vmm.resource_allocator,
483+
vcpu_count: vcpus.len() as u8,
484+
};
485+
vmm.pv_time = Some(
486+
PVTime::restore(pvtime_ctor_args, &pvtime_state)
487+
.map_err(BuildMicrovmFromSnapshotError::RestorePVTime)?,
488+
);
489+
vmm.pv_time
490+
.as_ref()
491+
.unwrap()
492+
.register_all_vcpus(&mut vcpus) // We can safely unwrap here
493+
.map_err(StartMicrovmError::CreatePVTime)?;
494+
}
489495
}
490496

491497
#[cfg(target_arch = "aarch64")]
@@ -585,45 +591,20 @@ pub fn setup_serial_device(
585591

586592
/// Sets up the pvtime device.
587593
#[cfg(target_arch = "aarch64")]
588-
fn setup_pv_time(vmm: &mut Vmm, vcpus: &mut Vec<Vcpu>) -> Result<(), StartMicrovmError> {
594+
fn setup_pv_time(vmm: &mut Vmm, vcpus: &mut [Vcpu]) -> Result<PVTime, StartMicrovmError> {
589595
use crate::arch::aarch64::pvtime::PVTime;
590596

591-
// Check if pvtime is enabled
592-
let pvtime_device_attr = kvm_bindings::kvm_device_attr {
593-
group: kvm_bindings::KVM_ARM_VCPU_PVTIME_CTRL,
594-
attr: kvm_bindings::KVM_ARM_VCPU_PVTIME_IPA as u64,
595-
addr: 0,
596-
flags: 0,
597-
};
598-
599-
// Use kvm_has_device_attr to check if PVTime is supported
600-
// TODO: Flesh out feature detection. Either throw error and handle, or
601-
// even return silently and just log "no support", or something else.
602-
let vcpu_fd = &vcpus[0].kvm_vcpu.fd;
603-
vcpu_fd
604-
.has_device_attr(&pvtime_device_attr)
605-
.map_err(StartMicrovmError::PVTimeNotSupported)?;
606-
607597
// Create the pvtime device
598+
#[allow(clippy::cast_possible_truncation)] // We know vcpu_count is u8 according to VcpuConfig
608599
let pv_time = PVTime::new(&mut vmm.resource_allocator, vcpus.len() as u8)
609600
.map_err(StartMicrovmError::CreatePVTime)?;
610601

611602
// Register all vcpus with pvtime device
612-
setup_pv_time_with(&pv_time, vcpus).map_err(StartMicrovmError::CreatePVTime)?;
613-
614-
// TODO: Might be a better design to return the device instead of assigning it to vmm here.
615-
vmm.pv_time = Some(pv_time);
616-
Ok(())
617-
}
603+
pv_time
604+
.register_all_vcpus(vcpus)
605+
.map_err(StartMicrovmError::CreatePVTime)?;
618606

619-
/// Helper method to register all vcpus with pvtime device
620-
#[cfg(target_arch = "aarch64")]
621-
fn setup_pv_time_with(pv_time: &PVTime, vcpus: &mut Vec<Vcpu>) -> Result<(), PVTimeError> {
622-
// Register the vcpu with the pvtime device to map its steal time region
623-
for i in 0..vcpus.len() {
624-
pv_time.register_vcpu(i as u8, &vcpus[i].kvm_vcpu.fd)?; // TODO: Change this to its own error
625-
}
626-
Ok(())
607+
Ok(pv_time)
627608
}
628609

629610
#[cfg(target_arch = "aarch64")]
@@ -929,6 +910,8 @@ pub(crate) mod tests {
929910
#[cfg(target_arch = "x86_64")]
930911
pio_device_manager,
931912
acpi_device_manager,
913+
#[cfg(target_arch = "aarch64")]
914+
pv_time: None,
932915
}
933916
}
934917

Diff for: src/vmm/src/lib.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,8 @@ use vstate::kvm::Kvm;
134134
use vstate::vcpu::{self, StartThreadedError, VcpuSendEventError};
135135

136136
use crate::arch::DeviceType;
137+
#[cfg(target_arch = "aarch64")]
138+
use crate::arch::aarch64::pvtime::PVTime;
137139
use crate::cpu_config::templates::CpuConfiguration;
138140
#[cfg(target_arch = "x86_64")]
139141
use crate::device_manager::legacy::PortIODeviceManager;
@@ -157,7 +159,6 @@ use crate::vstate::memory::{
157159
use crate::vstate::vcpu::VcpuState;
158160
pub use crate::vstate::vcpu::{Vcpu, VcpuConfig, VcpuEvent, VcpuHandle, VcpuResponse};
159161
pub use crate::vstate::vm::Vm;
160-
use crate::arch::aarch64::pvtime::PVTime;
161162
/// Shorthand type for the EventManager flavour used by Firecracker.
162163
pub type EventManager = BaseEventManager<Arc<Mutex<dyn MutEventSubscriber>>>;
163164

@@ -326,7 +327,6 @@ pub struct Vmm {
326327
acpi_device_manager: ACPIDeviceManager,
327328
#[cfg(target_arch = "aarch64")]
328329
pv_time: Option<PVTime>,
329-
330330
}
331331

332332
impl Vmm {
@@ -536,6 +536,7 @@ impl Vmm {
536536
vcpu_states,
537537
device_states,
538538
acpi_dev_state,
539+
#[cfg(target_arch = "aarch64")]
539540
pvtime_state,
540541
})
541542
}

Diff for: src/vmm/src/persist.rs

+3
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ use serde::{Deserialize, Serialize};
1616
use userfaultfd::{FeatureFlags, Uffd, UffdBuilder};
1717
use vmm_sys_util::sock_ctrl_msg::ScmSocket;
1818

19+
#[cfg(target_arch = "aarch64")]
1920
use crate::arch::aarch64::pvtime::PVTimeState;
2021
#[cfg(target_arch = "aarch64")]
2122
use crate::arch::aarch64::vcpu::get_manufacturer_id_from_host;
@@ -769,6 +770,8 @@ mod tests {
769770
#[cfg(target_arch = "x86_64")]
770771
vm_state: vmm.vm.save_state().unwrap(),
771772
acpi_dev_state: vmm.acpi_device_manager.save(),
773+
#[cfg(target_arch = "aarch64")]
774+
pvtime_state: None,
772775
};
773776

774777
let mut buf = vec![0; 10000];

0 commit comments

Comments
 (0)