mirror of
https://github.com/torvalds/linux.git
synced 2026-11-07 06:17:02 +01:00
KVM: arm64: timers: Compute an offset-applied CVAL from the current count
When the hardware won't apply a timer's offset, KVM programs CVAL +
offset and lets it compare that against the raw counter. The compare is
unsigned (IsTimerConditionMet, DDI0487M_c J1.4.3.29):
condition_met = (UInt(PhysicalCountInt() - offset) -
UInt(compare_value)) >= 0;
so adding the offset to both sides holds only while both sums wrap or
neither does. A guest whose counter is ahead of the host's by more than
CVAL has an expired timer that never fires, and one behind it can have
a far-future timer fire at once.
On a CNTPOFF_EL2 host the first case livelocks: each entry reloads the
guest's CVAL, the timer fires, and the exit rewrites it as CVAL + offset
before the host can take the interrupt. Without CNTPOFF_EL2 the timer
hangs until the vCPU is next loaded, which is what Zenghui reported on
a Kunpeng920.
Program the value from the current count instead: CVAL + offset while
the deadline is ahead, 0 once the timer has expired for the guest, ~0
past the counter's wrap. KVM recomputes it at load, at exit and on a
trapped CVAL write, and an expired timer stays expired in between. The
value can't be inverted, so with an offset timer_save_state() keeps the
memory copy, and kvm_hyp_handle_timer() returns that copy for a guest
hypervisor's read of its physical CVAL rather than subtracting the
offset back out.
Fixes: c605ee2450 ("KVM: arm64: timers: Allow physical offset without CNTPOFF_EL2")
Fixes: 9404673293 ("KVM: arm64: timers: Correctly handle TGE flip with CNTPOFF_EL2")
Fixes: 0bc9a9e85f ("KVM: arm64: Work around x1e's CNTVOFF_EL2 bogosity")
Reported-by: Zenghui Yu <yuzenghui@huawei.com>
Closes: https://lore.kernel.org/r/460258be-0102-e922-c342-4e87cd94b9e5@huawei.com
Cc: stable@vger.kernel.org
Signed-off-by: Fuad Tabba <fuad.tabba@linux.dev>
Link: https://patch.msgid.link/20261007074343.324269-2-fuad.tabba@linux.dev
Signed-off-by: Marc Zyngier <maz@kernel.org>
This commit is contained in:
parent
517cdec3d4
commit
49930b1b55
|
|
@ -528,17 +528,11 @@ static void timer_save_state(struct arch_timer_context *ctx)
|
|||
goto out;
|
||||
|
||||
switch (index) {
|
||||
u64 cval;
|
||||
|
||||
case TIMER_VTIMER:
|
||||
case TIMER_HVTIMER:
|
||||
timer_set_ctl(ctx, read_sysreg_el0(SYS_CNTV_CTL));
|
||||
cval = read_sysreg_el0(SYS_CNTV_CVAL);
|
||||
|
||||
if (has_broken_cntvoff())
|
||||
cval -= timer_get_offset(ctx);
|
||||
|
||||
timer_set_cval(ctx, cval);
|
||||
if (!has_broken_cntvoff() || !timer_get_offset(ctx))
|
||||
timer_set_cval(ctx, read_sysreg_el0(SYS_CNTV_CVAL));
|
||||
|
||||
/* Disable the timer */
|
||||
write_sysreg_el0(0, SYS_CNTV_CTL);
|
||||
|
|
@ -564,11 +558,12 @@ static void timer_save_state(struct arch_timer_context *ctx)
|
|||
case TIMER_PTIMER:
|
||||
case TIMER_HPTIMER:
|
||||
timer_set_ctl(ctx, read_sysreg_el0(SYS_CNTP_CTL));
|
||||
cval = read_sysreg_el0(SYS_CNTP_CVAL);
|
||||
|
||||
cval -= timer_get_offset(ctx);
|
||||
|
||||
timer_set_cval(ctx, cval);
|
||||
/*
|
||||
* With an offset, memory already holds the guest's CVAL (the
|
||||
* trap handler or __deactivate_traps() wrote it).
|
||||
*/
|
||||
if (!timer_get_offset(ctx))
|
||||
timer_set_cval(ctx, read_sysreg_el0(SYS_CNTP_CVAL));
|
||||
|
||||
/* Disable the timer */
|
||||
write_sysreg_el0(0, SYS_CNTP_CTL);
|
||||
|
|
@ -647,7 +642,8 @@ static void timer_restore_state(struct arch_timer_context *ctx)
|
|||
offset = timer_get_offset(ctx);
|
||||
if (has_broken_cntvoff()) {
|
||||
set_cntvoff(0);
|
||||
cval += offset;
|
||||
if (offset)
|
||||
cval = timer_apply_offset(cval, offset, kvm_phys_timer_read());
|
||||
} else {
|
||||
set_cntvoff(offset);
|
||||
}
|
||||
|
|
@ -660,7 +656,8 @@ static void timer_restore_state(struct arch_timer_context *ctx)
|
|||
cval = timer_get_cval(ctx);
|
||||
offset = timer_get_offset(ctx);
|
||||
set_cntpoff(offset);
|
||||
cval += offset;
|
||||
if (offset)
|
||||
cval = timer_apply_offset(cval, offset, kvm_phys_timer_read());
|
||||
write_sysreg_el0(cval, SYS_CNTP_CVAL);
|
||||
isb();
|
||||
write_sysreg_el0(timer_get_ctl(ctx), SYS_CNTP_CTL);
|
||||
|
|
|
|||
|
|
@ -175,7 +175,8 @@ static void __deactivate_traps(struct kvm_vcpu *vcpu)
|
|||
offset = read_sysreg_s(SYS_CNTPOFF_EL2);
|
||||
|
||||
if (map.direct_ptimer && offset) {
|
||||
write_sysreg_el0(val + offset, SYS_CNTP_CVAL);
|
||||
val = timer_apply_offset(val, offset, arch_timer_read_cntpct_el0());
|
||||
write_sysreg_el0(val, SYS_CNTP_CVAL);
|
||||
isb();
|
||||
}
|
||||
}
|
||||
|
|
@ -296,10 +297,10 @@ static bool kvm_hyp_handle_timer(struct kvm_vcpu *vcpu, u64 *exit_code)
|
|||
break;
|
||||
case SYS_CNTP_CVAL_EL0:
|
||||
if (vcpu_el2_e2h_is_set(vcpu)) {
|
||||
val = read_sysreg_el0(SYS_CNTP_CVAL);
|
||||
|
||||
if (!has_cntpoff())
|
||||
val -= timer_get_offset(vcpu_hptimer(vcpu));
|
||||
if (!has_cntpoff() && timer_get_offset(vcpu_hptimer(vcpu)))
|
||||
val = __vcpu_sys_reg(vcpu, CNTHP_CVAL_EL2);
|
||||
else
|
||||
val = read_sysreg_el0(SYS_CNTP_CVAL);
|
||||
} else {
|
||||
val = __vcpu_sys_reg(vcpu, CNTP_CVAL_EL0);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,4 +195,19 @@ static inline void timer_set_offset(struct arch_timer_context *ctxt, u64 offset)
|
|||
WRITE_ONCE(*ctxt->offset.vm_offset, offset);
|
||||
}
|
||||
|
||||
/*
|
||||
* CVAL to program when the hardware won't apply the timer's offset, so that
|
||||
* its compare against the raw counter matches the guest's at 'now'. An
|
||||
* expired timer stays expired; one past the wrap never fires.
|
||||
*/
|
||||
static inline u64 timer_apply_offset(u64 cval, u64 offset, u64 now)
|
||||
{
|
||||
u64 hw = cval + offset;
|
||||
|
||||
if (now - offset >= cval)
|
||||
return 0;
|
||||
|
||||
return hw > now ? hw : U64_MAX;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user