1
1
// Copyright 2025 Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
2
// SPDX-License-Identifier: Apache-2.0
3
- use std:: collections:: HashMap ;
4
-
5
3
use displaydoc:: Display ;
6
4
use kvm_bindings:: { KVM_ARM_VCPU_PVTIME_CTRL , KVM_ARM_VCPU_PVTIME_IPA } ;
5
+ use kvm_ioctls:: VcpuFd ;
7
6
use serde:: { Deserialize , Serialize } ;
8
7
use thiserror:: Error ;
9
8
use vm_memory:: GuestAddress ;
10
9
10
+ use crate :: Vcpu ;
11
11
use crate :: device_manager:: resources:: ResourceAllocator ;
12
12
use crate :: snapshot:: Persist ;
13
13
14
14
/// 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
15
15
pub const STEALTIME_STRUCT_MEM_SIZE : u64 = 64 ;
16
16
17
17
/// Represent PVTime device for ARM
18
- /// TODO: Decide whether we want to keep the hashmap OR the base IPA
19
18
#[ derive( Debug ) ]
20
19
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 ,
23
22
/// The base IPA of the shared memory region
24
- base_ipa : u64 ,
23
+ base_ipa : GuestAddress ,
25
24
}
26
25
27
26
/// Errors associated with PVTime operations
28
27
#[ derive( Debug , Error , Display , PartialEq , Eq ) ]
29
28
pub enum PVTimeError {
30
29
/// Failed to allocate memory region: {0}
31
- AllocationFailed ( String ) ,
30
+ AllocationFailed ( vm_allocator :: Error ) ,
32
31
/// Invalid VCPU ID: {0}
33
32
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 ) ,
36
35
}
37
36
38
37
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) ) ;
49
42
}
43
+ Ok ( GuestAddress (
44
+ self . base_ipa . 0 + ( vcpu_index as u64 * STEALTIME_STRUCT_MEM_SIZE ) ,
45
+ ) )
46
+ }
50
47
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 {
52
51
PVTime {
53
- steal_time_regions ,
52
+ vcpu_count ,
54
53
base_ipa,
55
54
}
56
55
}
@@ -65,41 +64,61 @@ impl PVTime {
65
64
resource_allocator
66
65
. allocate_system_memory (
67
66
STEALTIME_STRUCT_MEM_SIZE * vcpu_count as u64 ,
68
- 64 ,
67
+ STEALTIME_STRUCT_MEM_SIZE ,
69
68
vm_allocator:: AllocPolicy :: LastMatch ,
70
69
)
71
- . map_err ( |e| PVTimeError :: AllocationFailed ( e . to_string ( ) ) ) ?,
70
+ . map_err ( PVTimeError :: AllocationFailed ) ?,
72
71
) ;
73
-
74
72
Ok ( Self :: from_base ( base_ipa, vcpu_count) )
75
73
}
76
74
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
+
77
89
/// Register a vCPU with its pre-allocated steal time region
78
- pub fn register_vcpu (
90
+ fn register_vcpu (
79
91
& self ,
80
92
vcpu_index : u8 ,
81
93
vcpu_fd : & kvm_ioctls:: VcpuFd ,
82
94
) -> Result < ( ) , PVTimeError > {
83
95
// 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) ?;
88
97
89
98
// Use KVM syscall (kvm_set_device_attr) to register the vCPU with the steal_time region
90
99
let vcpu_device_attr = kvm_bindings:: kvm_device_attr {
91
100
group : KVM_ARM_VCPU_PVTIME_CTRL ,
92
101
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
94
103
flags : 0 ,
95
104
} ;
96
105
97
106
vcpu_fd
98
107
. set_device_attr ( & vcpu_device_attr)
99
- . map_err ( |err| PVTimeError :: DeviceAttribute ( err , true , KVM_ARM_VCPU_PVTIME_CTRL ) ) ?;
108
+ . map_err ( PVTimeError :: DeviceAttribute ) ?;
100
109
101
110
Ok ( ( ) )
102
111
}
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
+ }
103
122
}
104
123
105
124
/// Logic to save/restore the state of a PVTime device
@@ -109,9 +128,12 @@ pub struct PVTimeState {
109
128
pub base_ipa : u64 ,
110
129
}
111
130
131
+ /// Arguments to restore a PVTime device from PVTimeState
112
132
#[ derive( Debug ) ]
113
133
pub struct PVTimeConstructorArgs < ' a > {
134
+ /// For steal_time shared memory region
114
135
pub resource_allocator : & ' a mut ResourceAllocator ,
136
+ /// Number of vCPUs (should be consistent with pre-snapshot state)
115
137
pub vcpu_count : u8 ,
116
138
}
117
139
@@ -123,7 +145,7 @@ impl<'a> Persist<'a> for PVTime {
123
145
/// Save base IPA of PVTime device for persistence
124
146
fn save ( & self ) -> Self :: State {
125
147
PVTimeState {
126
- base_ipa : self . base_ipa ,
148
+ base_ipa : self . base_ipa . 0 ,
127
149
}
128
150
}
129
151
@@ -136,10 +158,10 @@ impl<'a> Persist<'a> for PVTime {
136
158
. resource_allocator
137
159
. allocate_system_memory (
138
160
STEALTIME_STRUCT_MEM_SIZE * constructor_args. vcpu_count as u64 ,
139
- 64 ,
161
+ STEALTIME_STRUCT_MEM_SIZE ,
140
162
vm_allocator:: AllocPolicy :: ExactMatch ( state. base_ipa ) ,
141
163
)
142
- . map_err ( |e| PVTimeError :: AllocationFailed ( e . to_string ( ) ) ) ?;
164
+ . map_err ( PVTimeError :: AllocationFailed ) ?;
143
165
Ok ( Self :: from_base (
144
166
GuestAddress ( state. base_ipa ) ,
145
167
constructor_args. vcpu_count ,
0 commit comments