Skip to content

Commit 6f9a355

Browse files
committed
WIP: Add PVTime device creation, boot-time setup, and logging
- Added PVTime struct that allocates and stores per-vCPU stolen_time regions - Implemented register_vcpu() to register the IPA for each vCPU with KVM - Invoked setup_pv_time in build_microvm_for_boot - Added PVTime detection in setup_pv_time - Started using debug/info logs while booting microvm for testing Limitations/WIP: - While manually booting a microvm for testing, error during set_device_attr in register_vcpu - Snapshotting not yet supported Signed-off-by: Dakshin Devanand <[email protected]>
1 parent f6fa622 commit 6f9a355

File tree

3 files changed

+180
-1
lines changed

3 files changed

+180
-1
lines changed

src/vmm/src/arch/aarch64/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ pub mod gic;
99
pub mod kvm;
1010
/// Layout for this aarch64 system.
1111
pub mod layout;
12+
/// Module for the paravirtualized time device
13+
pub mod pvtime;
1214
/// Logic for configuring aarch64 registers.
1315
pub mod regs;
1416
/// Architecture specific vCPU code
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
use std::collections::HashMap;
4+
5+
use displaydoc::Display;
6+
use kvm_bindings::{KVM_ARM_VCPU_PVTIME_CTRL, KVM_ARM_VCPU_PVTIME_IPA};
7+
use log::{debug, info, warn};
8+
use thiserror::Error;
9+
10+
use crate::device_manager::resources::ResourceAllocator;
11+
12+
/// Size of the stolen_time struct in bytes, see 3.2.2 in DEN0057A
13+
pub const STEALTIME_STRUCT_MEM_SIZE: u64 = 16;
14+
15+
/// Represent PVTime device for ARM
16+
#[derive(Debug)]
17+
pub struct PVTime {
18+
/// Maps vCPU index to IPA location of stolen_time struct as defined in DEN0057A
19+
steal_time_regions: HashMap<u8, u64>,
20+
}
21+
22+
/// Errors associated with PVTime operations
23+
#[derive(Debug, Error, Display, PartialEq, Eq)]
24+
pub enum PVTimeError {
25+
/// Failed to allocate memory region: {0}
26+
AllocationFailed(String),
27+
/// Invalid VCPU ID: {0}
28+
InvalidVcpuIndex(u8),
29+
/// Error while setting or getting device attributes for vCPU: {0}, {1}, {2}
30+
DeviceAttribute(kvm_ioctls::Error, bool, u32),
31+
}
32+
33+
impl PVTime {
34+
/// Creates a new PVTime device by allocating system memory for all vCPUs
35+
pub fn new(
36+
resource_allocator: &mut ResourceAllocator,
37+
vcpu_count: u8,
38+
) -> Result<Self, PVTimeError> {
39+
info!("Creating PVTime for {vcpu_count} vCPUs...");
40+
// This returns the IPA(?) of the start of our shared memory region for all vCPUs.
41+
// Q: Confirm that allocate_system_memory returns an IPA?
42+
let base_addr = resource_allocator
43+
.allocate_system_memory(
44+
STEALTIME_STRUCT_MEM_SIZE * vcpu_count as u64,
45+
16, // Q: We believe this to be 16, need confirmation.
46+
vm_allocator::AllocPolicy::LastMatch,
47+
)
48+
.map_err(|e| PVTimeError::AllocationFailed(e.to_string()))?;
49+
50+
debug!(
51+
"Allocated base address for PVTime stolen_time region: 0x{:x}",
52+
base_addr
53+
);
54+
55+
// Now we need to store the base IPA for each vCPU's steal_time struct.
56+
let mut steal_time_regions = HashMap::new();
57+
for i in 0..vcpu_count {
58+
let ipa = base_addr + (i as u64 * STEALTIME_STRUCT_MEM_SIZE);
59+
debug!("Assigned vCPU {} to stolen_time IPA 0x{:x}", i, ipa);
60+
steal_time_regions.insert(i, ipa);
61+
}
62+
63+
// Return the PVTime device with the steal_time region IPAs mapped to vCPU indices.
64+
Ok(PVTime { steal_time_regions })
65+
}
66+
67+
/// Register a vCPU with its pre-allocated steal time region
68+
pub fn register_vcpu(
69+
&self,
70+
vcpu_index: u8,
71+
vcpu_fd: &kvm_ioctls::VcpuFd,
72+
) -> Result<(), PVTimeError> {
73+
// Get IPA of the steal_time region for this vCPU
74+
let ipa = self
75+
.steal_time_regions
76+
.get(&vcpu_index)
77+
.ok_or(PVTimeError::InvalidVcpuIndex(vcpu_index))?;
78+
79+
debug!(
80+
"Registering vCPU {} with stolen_time IPA = 0x{:x}",
81+
vcpu_index, ipa
82+
);
83+
84+
// IMPORTANT QUESTION: We need to confirm this is safe. We need to somehow
85+
// ensure the ipa value is not dropped before we use it etc. since we
86+
// are creating a raw pointer? Do we need a Box?
87+
let ipa_val = *ipa;
88+
let ipa_ptr = &ipa_val as *const u64;
89+
debug!(
90+
"Registering vCPU {} with stolen_time IPA = 0x{:x} (at userspace addr 0x{:x})",
91+
vcpu_index, ipa_val, ipa_ptr as u64
92+
);
93+
94+
// Use KVM syscall (kvm_set_device_attr) to register the vCPU with the steal_time region
95+
let vcpu_device_attr = kvm_bindings::kvm_device_attr {
96+
group: KVM_ARM_VCPU_PVTIME_CTRL,
97+
attr: KVM_ARM_VCPU_PVTIME_IPA as u64,
98+
addr: ipa_ptr as u64, // userspace address of attr data
99+
flags: 0,
100+
};
101+
102+
vcpu_fd.set_device_attr(&vcpu_device_attr).map_err(|err| {
103+
warn!(
104+
"Failed to set device attribute for vCPU {}: {:?}",
105+
vcpu_index, err
106+
);
107+
PVTimeError::DeviceAttribute(err, true, KVM_ARM_VCPU_PVTIME_CTRL)
108+
})?;
109+
110+
Ok(())
111+
}
112+
}
113+
114+
// TODO/Q: Would we be correct in implementing Persist for PVTime? Some sort of persistence is
115+
// needed for snapshot capability. We would only need to store base_addr IPA of shared memory region
116+
// assuming the # of vCPUs is constant across snapshots. Also assuming we are correct that
117+
// kvm_set_device_attr takes an IPA and we get an IPA back from resource_allocator.

src/vmm/src/builder.rs

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
// SPDX-License-Identifier: Apache-2.0
33

44
//! Enables pre-boot setup, instantiation and booting of a Firecracker VMM.
5-
65
use std::fmt::Debug;
76
use std::io;
87
#[cfg(feature = "gdb")]
@@ -12,13 +11,15 @@ use std::sync::{Arc, Mutex};
1211
use event_manager::{MutEventSubscriber, SubscriberOps};
1312
use libc::EFD_NONBLOCK;
1413
use linux_loader::cmdline::Cmdline as LoaderKernelCmdline;
14+
use log::{info, warn};
1515
use userfaultfd::Uffd;
1616
use utils::time::TimestampUs;
1717
#[cfg(target_arch = "aarch64")]
1818
use vm_superio::Rtc;
1919
use vm_superio::Serial;
2020
use vmm_sys_util::eventfd::EventFd;
2121

22+
use crate::arch::aarch64::pvtime::PVTimeError;
2223
use crate::arch::{ConfigurationError, configure_system_for_boot, load_kernel};
2324
#[cfg(target_arch = "aarch64")]
2425
use crate::construct_kvm_mpidrs;
@@ -69,6 +70,9 @@ pub enum StartMicrovmError {
6970
AttachBlockDevice(io::Error),
7071
/// Unable to attach the VMGenID device: {0}
7172
AttachVmgenidDevice(kvm_ioctls::Error),
73+
/// PVTime not supported: {0}
74+
#[cfg(target_arch = "aarch64")]
75+
PVTimeNotSupported(kvm_ioctls::Error),
7276
/// System configuration error: {0}
7377
ConfigureSystem(#[from] ConfigurationError),
7478
/// Failed to create guest config: {0}
@@ -82,6 +86,8 @@ pub enum StartMicrovmError {
8286
CreateLegacyDevice(device_manager::legacy::LegacyDeviceError),
8387
/// Error creating VMGenID device: {0}
8488
CreateVMGenID(VmGenIdError),
89+
/// Error creating PVTime device: {0}
90+
CreatePVTime(PVTimeError),
8591
/// Invalid Memory Configuration: {0}
8692
GuestMemory(crate::vstate::memory::MemoryError),
8793
/// Error with initrd initialization: {0}.
@@ -289,6 +295,17 @@ pub fn build_microvm_for_boot(
289295

290296
attach_vmgenid_device(&mut vmm)?;
291297

298+
// Attempt to setup PVTime, continue if not supported
299+
#[cfg(target_arch = "aarch64")]
300+
if let Err(e) = setup_pv_time(&mut vmm, vcpus.as_mut()) {
301+
match e {
302+
StartMicrovmError::PVTimeNotSupported(e) => {
303+
warn!("PVTime not supported: {}", e);
304+
}
305+
other => return Err(other),
306+
}
307+
}
308+
292309
configure_system_for_boot(
293310
&mut vmm,
294311
vcpus.as_mut(),
@@ -548,6 +565,49 @@ pub fn setup_serial_device(
548565
Ok(serial)
549566
}
550567

568+
/// Sets up the pvtime device.
569+
#[cfg(target_arch = "aarch64")]
570+
fn setup_pv_time(vmm: &mut Vmm, vcpus: &mut Vec<Vcpu>) -> Result<(), StartMicrovmError> {
571+
// Q: Is this bad practice, using crates in this scope?
572+
use crate::arch::aarch64::pvtime::PVTime;
573+
574+
info!("PVTime setup started");
575+
576+
// Check if pvtime is enabled
577+
let pvtime_device_attr = kvm_bindings::kvm_device_attr {
578+
group: kvm_bindings::KVM_ARM_VCPU_PVTIME_CTRL,
579+
attr: kvm_bindings::KVM_ARM_VCPU_PVTIME_IPA as u64,
580+
addr: 0,
581+
flags: 0,
582+
};
583+
584+
// Use kvm_has_device_attr to check if PVTime is supported
585+
// TODO: Flesh out feature detection. Either throw error and handle, or
586+
// even return silently and just log "no support", or something else.
587+
let vcpu_fd = &vcpus[0].kvm_vcpu.fd;
588+
vcpu_fd
589+
.has_device_attr(&pvtime_device_attr)
590+
.map_err(StartMicrovmError::PVTimeNotSupported)?;
591+
592+
info!("PVTime is supported");
593+
594+
// Create the pvtime device
595+
let pv_time = PVTime::new(&mut vmm.resource_allocator, vcpus.len() as u8)
596+
.map_err(StartMicrovmError::CreatePVTime)?;
597+
598+
// Register the vcpu with the pvtime device to map its steal time region
599+
for i in 0..vcpus.len() {
600+
pv_time
601+
.register_vcpu(i as u8, &vcpus[i].kvm_vcpu.fd)
602+
.map_err(StartMicrovmError::CreatePVTime)?; // Change this to its own error
603+
}
604+
605+
// TODO: Store pv_time somewhere (in Vmm ?) for snapshotting later instead of dropping it
606+
info!("PVTime setup completed");
607+
608+
Ok(())
609+
}
610+
551611
#[cfg(target_arch = "aarch64")]
552612
fn attach_legacy_devices_aarch64(
553613
event_manager: &mut EventManager,

0 commit comments

Comments
 (0)