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;

Adding the offset to both sides preserves it only when 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 CVAL + offset puts in the far
future, and one behind it has a far-future timer that CVAL + offset
wraps into the past.

On a CNTPOFF_EL2 host the first case livelocks: __activate_traps()
reloads the guest's CVAL and the timer fires, __deactivate_traps()
rewrites it as CVAL + offset on the way out and the line drops before
the host can take the interrupt, and the vCPU only makes progress once
an unrelated vcpu_put() and vcpu_load() inject the interrupt from the
memory copy. arch_timer_edge_cases stops in its physical past-timer
cases. Without CNTPOFF_EL2 the same cases hang until the vCPU is next
loaded, which is what Zenghui reported on a Kunpeng920.

Derive the programmed value from the current count instead: CVAL +
offset while the deadline is ahead, 0 for a timer that has already
expired for the guest, ~0 for one past the counter's wrap. That value
can't be inverted, so timer_save_state() keeps the memory copy whenever
an offset is in play; it's current there, written by the trap handler
when the accesses are trapped and by __deactivate_traps() on the
CNTPOFF_EL2 path. kvm_hyp_handle_timer() subtracted the offset back out
for a guest hypervisor's read of its own physical CVAL, so return the
memory copy there too.

Fixes: c605ee245097 ("KVM: arm64: timers: Allow physical offset without 
CNTPOFF_EL2")
Fixes: 9404673293b0 ("KVM: arm64: timers: Correctly handle TGE flip with 
CNTPOFF_EL2")
Fixes: 0bc9a9e85fcf ("KVM: arm64: Work around x1e's CNTVOFF_EL2 bogosity")
Reported-by: Zenghui Yu <[email protected]>
Closes: 
https://lore.kernel.org/r/[email protected]
Cc: [email protected]
Signed-off-by: Fuad Tabba <[email protected]>
---
 arch/arm64/kvm/arch_timer.c     | 27 ++++++++++++---------------
 arch/arm64/kvm/hyp/vhe/switch.c | 11 ++++++-----
 include/kvm/arm_arch_timer.h    | 15 +++++++++++++++
 3 files changed, 33 insertions(+), 20 deletions(-)

diff --git a/arch/arm64/kvm/arch_timer.c b/arch/arm64/kvm/arch_timer.c
index 6ac3321f4c575..9278b5383c044 100644
--- a/arch/arm64/kvm/arch_timer.c
+++ b/arch/arm64/kvm/arch_timer.c
@@ -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);
diff --git a/arch/arm64/kvm/hyp/vhe/switch.c b/arch/arm64/kvm/hyp/vhe/switch.c
index 7875911c05063..14aada311bac4 100644
--- a/arch/arm64/kvm/hyp/vhe/switch.c
+++ b/arch/arm64/kvm/hyp/vhe/switch.c
@@ -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);
                }
diff --git a/include/kvm/arm_arch_timer.h b/include/kvm/arm_arch_timer.h
index bc6f2fdd7ad33..80f96ea59f1ae 100644
--- a/include/kvm/arm_arch_timer.h
+++ b/include/kvm/arm_arch_timer.h
@@ -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
-- 
2.39.5


Reply via email to