Skip to content

Commit d71ff8a

Browse files
committed
feat: reset KVM_REG_ARM_PTIMER_CNT on VM boot
Reset KVM_REG_ARM_PTIMER_CNT physical counter register on VM boot to avoid passing through host physical counter. Note that resetting the register on VM boot does not guarantee that VM will see the counter value 0 at startup because there is a delta in time between register reset and VM boot during which counter continues to advance. In order to check if the kernel supports the counter reset we query KVM_CAP_COUNTER_OFFSET capability and only reset the KVM_REG_ARM_PTIMER_CNT if it is present. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent 0efae50 commit d71ff8a

File tree

6 files changed

+92
-13
lines changed

6 files changed

+92
-13
lines changed

src/vmm/src/arch/aarch64/regs.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,12 @@ arm64_sys_reg!(SYS_CNTV_CVAL_EL0, 3, 3, 14, 3, 2);
9999
// https://elixir.bootlin.com/linux/v6.8/source/arch/arm64/include/asm/sysreg.h#L459
100100
arm64_sys_reg!(SYS_CNTPCT_EL0, 3, 3, 14, 0, 1);
101101

102+
// Physical Timer EL0 count Register
103+
// The id of this register is same as SYS_CNTPCT_EL0, but KVM defines it
104+
// separately, so we do as well.
105+
// https://elixir.bootlin.com/linux/v6.12.6/source/arch/arm64/include/uapi/asm/kvm.h#L259
106+
arm64_sys_reg!(KVM_REG_ARM_PTIMER_CNT, 3, 3, 14, 0, 1);
107+
102108
// Translation Table Base Register
103109
// https://developer.arm.com/documentation/ddi0595/2021-03/AArch64-Registers/TTBR1-EL1--Translation-Table-Base-Register-1--EL1-
104110
arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1);

src/vmm/src/arch/aarch64/vcpu.rs

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use kvm_ioctls::VcpuFd;
1313

1414
use super::get_fdt_addr;
1515
use super::regs::*;
16+
use crate::vstate::kvm::OptionalCapabilities;
1617
use crate::vstate::memory::GuestMemoryMmap;
1718

1819
/// Errors thrown while setting aarch64 registers.
@@ -78,6 +79,7 @@ pub fn setup_boot_regs(
7879
cpu_id: u8,
7980
boot_ip: u64,
8081
mem: &GuestMemoryMmap,
82+
optional_capabilities: &OptionalCapabilities,
8183
) -> Result<(), VcpuError> {
8284
let kreg_off = offset_of!(kvm_regs, regs);
8385

@@ -106,6 +108,23 @@ pub fn setup_boot_regs(
106108
vcpufd
107109
.set_one_reg(id, &get_fdt_addr(mem).to_le_bytes())
108110
.map_err(|err| VcpuError::SetOneReg(id, err))?;
111+
112+
// Reset the physical counter for the guest. This way we avoid guest reading
113+
// host physical counter.
114+
// Resetting KVM_REG_ARM_PTIMER_CNT for single vcpu is enough because there is only
115+
// one timer struct with offsets per VM.
116+
// Because the access to KVM_REG_ARM_PTIMER_CNT is only present starting 6.4 kernel,
117+
// we only do the reset if KVM_CAP_COUNTER_OFFSET is present as it was added
118+
// in the same patch series as the ability to set the KVM_REG_ARM_PTIMER_CNT register.
119+
// Path series which introduced the needed changes:
120+
// https://lore.kernel.org/all/[email protected]/
121+
// Note: the value observed by the guest will still be above 0, because there is a delta
122+
// time between this resetting and first call to KVM_RUN.
123+
if optional_capabilities.counter_offset {
124+
vcpufd
125+
.set_one_reg(KVM_REG_ARM_PTIMER_CNT, &[0; 8])
126+
.map_err(|err| VcpuError::SetOneReg(id, err))?;
127+
}
109128
}
110129
Ok(())
111130
}
@@ -226,8 +245,9 @@ mod tests {
226245
let vm = kvm.fd.create_vm().unwrap();
227246
let vcpu = vm.create_vcpu(0).unwrap();
228247
let mem = arch_mem(layout::FDT_MAX_SIZE + 0x1000);
248+
let optional_capabilities = kvm.optional_capabilities();
229249

230-
let res = setup_boot_regs(&vcpu, 0, 0x0, &mem);
250+
let res = setup_boot_regs(&vcpu, 0, 0x0, &mem, &optional_capabilities);
231251
assert!(matches!(
232252
res.unwrap_err(),
233253
VcpuError::SetOneReg(0x6030000000100042, _)
@@ -237,7 +257,25 @@ mod tests {
237257
vm.get_preferred_target(&mut kvi).unwrap();
238258
vcpu.vcpu_init(&kvi).unwrap();
239259

240-
setup_boot_regs(&vcpu, 0, 0x0, &mem).unwrap();
260+
setup_boot_regs(&vcpu, 0, 0x0, &mem, &optional_capabilities).unwrap();
261+
262+
// Check that the register is reset on compatible kernels.
263+
// Because there is a delta in time between we reset the register and time we
264+
// read it, we cannot compare with 0. Instead we compare it with meaningfully
265+
// small value.
266+
if optional_capabilities.counter_offset {
267+
let mut reg_bytes = [0_u8; 8];
268+
vcpu.get_one_reg(SYS_CNTPCT_EL0, &mut reg_bytes).unwrap();
269+
let counter_value = u64::from_le_bytes(reg_bytes);
270+
271+
// We are reading the SYS_CNTPCT_EL0 right after resetting it.
272+
// If reset did happen successfully, the value should be quite small when we read it.
273+
// If the reset did not happen, the value will be same as on the host and it surely
274+
// will be more that MAX_VALUE.
275+
let max_value = 1000;
276+
277+
assert!(counter_value < max_value);
278+
}
241279
}
242280

243281
#[test]

src/vmm/src/builder.rs

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -814,16 +814,16 @@ pub fn configure_system_for_boot(
814814
cpu_config,
815815
};
816816

817-
// Configure vCPUs with normalizing and setting the generated CPU configuration.
818-
for vcpu in vcpus.iter_mut() {
819-
vcpu.kvm_vcpu
820-
.configure(vmm.guest_memory(), entry_addr, &vcpu_config)
821-
.map_err(VmmError::VcpuConfigure)
822-
.map_err(Internal)?;
823-
}
824-
825817
#[cfg(target_arch = "x86_64")]
826818
{
819+
// Configure vCPUs with normalizing and setting the generated CPU configuration.
820+
for vcpu in vcpus.iter_mut() {
821+
vcpu.kvm_vcpu
822+
.configure(vmm.guest_memory(), entry_addr, &vcpu_config)
823+
.map_err(VmmError::VcpuConfigure)
824+
.map_err(Internal)?;
825+
}
826+
827827
// Write the kernel command line to guest memory. This is x86_64 specific, since on
828828
// aarch64 the command line will be specified through the FDT.
829829
let cmdline_size = boot_cmdline
@@ -858,6 +858,19 @@ pub fn configure_system_for_boot(
858858
}
859859
#[cfg(target_arch = "aarch64")]
860860
{
861+
let optional_capabilities = vmm.kvm.optional_capabilities();
862+
// Configure vCPUs with normalizing and setting the generated CPU configuration.
863+
for vcpu in vcpus.iter_mut() {
864+
vcpu.kvm_vcpu
865+
.configure(
866+
vmm.guest_memory(),
867+
entry_addr,
868+
&vcpu_config,
869+
&optional_capabilities,
870+
)
871+
.map_err(VmmError::VcpuConfigure)
872+
.map_err(Internal)?;
873+
}
861874
let vcpu_mpidr = vcpus
862875
.iter_mut()
863876
.map(|cpu| cpu.kvm_vcpu.get_mpidr())

src/vmm/src/vstate/kvm.rs

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,13 @@ impl Kvm {
140140
}
141141
}
142142
}
143-
143+
#[cfg(target_arch = "aarch64")]
144+
/// Optional capabilities.
145+
#[derive(Debug, Default)]
146+
pub struct OptionalCapabilities {
147+
/// KVM_CAP_COUNTER_OFFSET
148+
pub counter_offset: bool,
149+
}
144150
#[cfg(target_arch = "aarch64")]
145151
impl Kvm {
146152
const DEFAULT_CAPABILITIES: [u32; 7] = [
@@ -152,6 +158,16 @@ impl Kvm {
152158
kvm_bindings::KVM_CAP_MP_STATE,
153159
kvm_bindings::KVM_CAP_ONE_REG,
154160
];
161+
162+
/// Returns struct with optional capabilities statuses.
163+
pub fn optional_capabilities(&self) -> OptionalCapabilities {
164+
OptionalCapabilities {
165+
counter_offset: self
166+
.fd
167+
.check_extension_raw(kvm_bindings::KVM_CAP_COUNTER_OFFSET.into())
168+
!= 0,
169+
}
170+
}
155171
}
156172

157173
#[cfg(target_arch = "x86_64")]

src/vmm/src/vstate/vcpu/aarch64.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ use crate::cpu_config::aarch64::custom_cpu_template::VcpuFeatures;
2222
use crate::cpu_config::templates::CpuConfiguration;
2323
use crate::logger::{error, IncMetric, METRICS};
2424
use crate::vcpu::{VcpuConfig, VcpuError};
25-
use crate::vstate::kvm::Kvm;
25+
use crate::vstate::kvm::{Kvm, OptionalCapabilities};
2626
use crate::vstate::memory::{Address, GuestAddress, GuestMemoryMmap};
2727
use crate::vstate::vcpu::VcpuEmulation;
2828
use crate::vstate::vm::Vm;
@@ -116,6 +116,7 @@ impl KvmVcpu {
116116
guest_mem: &GuestMemoryMmap,
117117
kernel_load_addr: GuestAddress,
118118
vcpu_config: &VcpuConfig,
119+
optional_capabilities: &OptionalCapabilities,
119120
) -> Result<(), KvmVcpuError> {
120121
for reg in vcpu_config.cpu_config.regs.iter() {
121122
self.fd
@@ -128,6 +129,7 @@ impl KvmVcpu {
128129
self.index,
129130
kernel_load_addr.raw_value(),
130131
guest_mem,
132+
optional_capabilities,
131133
)
132134
.map_err(KvmVcpuError::ConfigureRegisters)?;
133135

@@ -338,7 +340,8 @@ mod tests {
338340

339341
#[test]
340342
fn test_configure_vcpu() {
341-
let (_, _, mut vcpu, vm_mem) = setup_vcpu(0x10000);
343+
let (kvm, _, mut vcpu, vm_mem) = setup_vcpu(0x10000);
344+
let optional_capabilities = kvm.optional_capabilities();
342345

343346
let vcpu_config = VcpuConfig {
344347
vcpu_count: 1,
@@ -349,6 +352,7 @@ mod tests {
349352
&vm_mem,
350353
GuestAddress(crate::arch::get_kernel_start()),
351354
&vcpu_config,
355+
&optional_capabilities,
352356
)
353357
.unwrap();
354358

@@ -358,6 +362,7 @@ mod tests {
358362
&vm_mem,
359363
GuestAddress(crate::arch::get_kernel_start()),
360364
&vcpu_config,
365+
&optional_capabilities,
361366
);
362367
assert_eq!(
363368
err.unwrap_err(),

src/vmm/src/vstate/vcpu/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,6 +1008,7 @@ pub(crate) mod tests {
10081008
smt: false,
10091009
cpu_config: crate::cpu_config::aarch64::CpuConfiguration::default(),
10101010
},
1011+
&kvm.optional_capabilities(),
10111012
)
10121013
.expect("failed to configure vcpu");
10131014

0 commit comments

Comments
 (0)