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-
53use displaydoc:: Display ;
64use kvm_bindings:: { KVM_ARM_VCPU_PVTIME_CTRL , KVM_ARM_VCPU_PVTIME_IPA } ;
5+ use kvm_ioctls:: VcpuFd ;
76use serde:: { Deserialize , Serialize } ;
87use thiserror:: Error ;
98use vm_memory:: GuestAddress ;
109
10+ use crate :: Vcpu ;
1111use crate :: device_manager:: resources:: ResourceAllocator ;
1212use 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
1515pub 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 ) ]
2019pub 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 ) ]
2928pub 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
3837impl 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 ) ]
113133pub 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 ,
0 commit comments