Skip to content

Commit 986ac8b

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. Signed-off-by: Egor Lazarchuk <[email protected]>
1 parent a5ffb7a commit 986ac8b

File tree

2 files changed

+47
-0
lines changed

2 files changed

+47
-0
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: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use std::path::PathBuf;
1010

1111
use kvm_bindings::*;
1212
use kvm_ioctls::VcpuFd;
13+
use log::warn;
1314

1415
use super::get_fdt_addr;
1516
use super::regs::*;
@@ -106,6 +107,24 @@ pub fn setup_boot_regs(
106107
vcpufd
107108
.set_one_reg(id, &get_fdt_addr(mem).to_le_bytes())
108109
.map_err(|err| VcpuError::SetOneReg(id, err))?;
110+
111+
// Reset the physical counter for the guest. This way we avoid guest reading
112+
// host physical counter.
113+
// Resetting KVM_REG_ARM_PTIMER_CNT for single vcpu is enough because there is only
114+
// one timer struct with offsets per VM.
115+
// Because the access to KVM_REG_ARM_PTIMER_CNT is only present starting 6.4 kernel,
116+
// we don't fail if ioctl returns an error.
117+
// Path series which introduced the needed changes:
118+
// https://lore.kernel.org/all/[email protected]/
119+
// Note: the value observed by the guest will still be above 0, because there is a delta
120+
// time between this resetting and first call to KVM_RUN.
121+
let zero: u64 = 0;
122+
if vcpufd
123+
.set_one_reg(KVM_REG_ARM_PTIMER_CNT, &zero.to_le_bytes())
124+
.is_err()
125+
{
126+
warn!("Unable to reset VM physical counter. VM will use host value instead.");
127+
}
109128
}
110129
Ok(())
111130
}
@@ -238,6 +257,28 @@ mod tests {
238257
vcpu.vcpu_init(&kvi).unwrap();
239258

240259
setup_boot_regs(&vcpu, 0, 0x0, &mem).unwrap();
260+
261+
// Check that the register is reset on compatible kernels.
262+
// Because there is a delta in time between we reset the register and time we
263+
// read it, we cannot compare with 0. Instead we read CNTVNT_EL0 (Virtual counter)
264+
// from the host. The host value should be much bigger than the VM physical counter.
265+
let mut reg_bytes = [0_u8; 8];
266+
if vcpu.get_one_reg(SYS_CNTPCT_EL0, &mut reg_bytes).is_ok() {
267+
let virt_count = unsafe {
268+
// Rust is so smart, it cannot figure out that `virt_count`
269+
// is read later in the assert!, so it complains about unused assignment.
270+
#[allow(unused)]
271+
let mut virt_count: u64 = 0;
272+
use std::arch::asm;
273+
asm!(
274+
"mrs {tmp}, cntvct_el0",
275+
tmp = out(reg) virt_count,
276+
);
277+
virt_count
278+
};
279+
let counter_value = u64::from_le_bytes(reg_bytes);
280+
assert!(counter_value < virt_count);
281+
}
241282
}
242283

243284
#[test]

0 commit comments

Comments
 (0)