From 772dc499be35b8db043b96c937196ae7c2772065 Mon Sep 17 00:00:00 2001 From: Egor Lazarchuk Date: Fri, 10 Jan 2025 11:27:02 +0000 Subject: [PATCH] feat: reset KVM_REG_ARM_PTIMER_CNT on VM boot Reset KVM_REG_ARM_PTIMER_CNT performance counter register on VM boot to avoid passing through host performance 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. Signed-off-by: Egor Lazarchuk --- src/vmm/src/arch/aarch64/regs.rs | 6 ++++++ src/vmm/src/arch/aarch64/vcpu.rs | 26 ++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) diff --git a/src/vmm/src/arch/aarch64/regs.rs b/src/vmm/src/arch/aarch64/regs.rs index 5238f58ba70..d844fbfb56b 100644 --- a/src/vmm/src/arch/aarch64/regs.rs +++ b/src/vmm/src/arch/aarch64/regs.rs @@ -99,6 +99,12 @@ arm64_sys_reg!(SYS_CNTV_CVAL_EL0, 3, 3, 14, 3, 2); // https://elixir.bootlin.com/linux/v6.8/source/arch/arm64/include/asm/sysreg.h#L459 arm64_sys_reg!(SYS_CNTPCT_EL0, 3, 3, 14, 0, 1); +// Physical Timer EL0 count Register +// The id of this register is same as SYS_CNTPCT_EL0, but KVM defines it +// separately, so we do as well. +// https://elixir.bootlin.com/linux/v6.12.6/source/arch/arm64/include/uapi/asm/kvm.h#L259 +arm64_sys_reg!(KVM_REG_ARM_PTIMER_CNT, 3, 3, 14, 0, 1); + // Translation Table Base Register // https://developer.arm.com/documentation/ddi0595/2021-03/AArch64-Registers/TTBR1-EL1--Translation-Table-Base-Register-1--EL1- arm64_sys_reg!(TTBR1_EL1, 3, 0, 2, 0, 1); diff --git a/src/vmm/src/arch/aarch64/vcpu.rs b/src/vmm/src/arch/aarch64/vcpu.rs index 80fc5a339df..c41096d5957 100644 --- a/src/vmm/src/arch/aarch64/vcpu.rs +++ b/src/vmm/src/arch/aarch64/vcpu.rs @@ -10,6 +10,7 @@ use std::path::PathBuf; use kvm_bindings::*; use kvm_ioctls::VcpuFd; +use log::warn; use super::get_fdt_addr; use super::regs::*; @@ -106,6 +107,22 @@ pub fn setup_boot_regs( vcpufd .set_one_reg(id, &get_fdt_addr(mem).to_le_bytes()) .map_err(|err| VcpuError::SetOneReg(id, err))?; + + // Reset the physical counter for the guest. This way we avoid guest reading + // host physical counter. + // Resetting KVM_REG_ARM_PTIMER_CNT for singe vcpu is enough because there is only + // one timer struct with offsets per VM. + // Because the access to KVM_REG_ARM_PTIMER_CNT is only present starting 6.4 kernel, + // we don't fail if ioctl returns an error. + // Note: the value observed by the guest will still be above 0, because there is a delta + // time between this resetting and first call to KVM_RUN + let zero: u64 = 0; + if vcpufd + .set_one_reg(KVM_REG_ARM_PTIMER_CNT, &zero.to_le_bytes()) + .is_err() + { + warn!("Unable to reset performance counter. VM will use host value instead."); + } } Ok(()) } @@ -238,6 +255,15 @@ mod tests { vcpu.vcpu_init(&kvi).unwrap(); setup_boot_regs(&vcpu, 0, 0x0, &mem).unwrap(); + + // Check that the register is reset on compatible kernels. + // Because there is a delta in time between we reset the register and time we + // read it, we cannot compare with 0. Choose some meaningfully small value instead. + let mut reg_bytes = [0_u8; 8]; + if vcpu.get_one_reg(SYS_CNTPCT_EL0, &mut reg_bytes).is_ok() { + let counter_value = u64::from_le_bytes(reg_bytes); + assert!(counter_value < 10_000); + } } #[test]