[PATCH] LoongArch: KVM: Fix oneshot timer emulation

From: Bibo Mao
Date: Mon Nov 20 2023 - 21:37:05 EST


When oneshot timer is fired, CSR TVAL will be -1 rather than 0.
There needs special handing for this situation. There are two
scenarios when oneshot timer is fired. One scenario is that
time is fired after exiting to host, CSR TVAL is set with 0
in order to inject hw interrupt, and -1 will assigned to CSR TVAL
soon.

The other situation is that timer is fired in VM and guest kernel
is hanlding timer IRQ and is ready to set next expired timer val,
timer interrupt should not be injected at this point, else there
will be spurious timer interrupt. After VM finished doing timer IRQ
and timer IRQ will be triggered again, however the next expired
timer val does not reach to 0, it is just spurious timer interrupt.

Here hw timer irq status in CSR ESTAT is used to judge these
two scenarios. If CSR TVAL is -1, the oneshot timer is fired;
and if timer hw irq is on in CSR ESTAT register, it happens after
exiting to host; else if timer hw irq is off, we think that it
happens in vm and timer IRQ handler has already acked IRQ.

With this patch, runltp with version ltp20230516 passes to run
in vm. And this patch is based on the patch series.
https://lore.kernel.org/lkml/20231116023036.2324371-1-maobibo@xxxxxxxxxxx/

Signed-off-by: Bibo Mao <maobibo@xxxxxxxxxxx>
---
arch/loongarch/kvm/timer.c | 61 ++++++++++++++++++++++++++++++--------
1 file changed, 49 insertions(+), 12 deletions(-)

diff --git a/arch/loongarch/kvm/timer.c b/arch/loongarch/kvm/timer.c
index 711982f9eeb5..00487d67f86d 100644
--- a/arch/loongarch/kvm/timer.c
+++ b/arch/loongarch/kvm/timer.c
@@ -70,6 +70,7 @@ void kvm_init_timer(struct kvm_vcpu *vcpu, unsigned long timer_hz)
void kvm_restore_timer(struct kvm_vcpu *vcpu)
{
unsigned long cfg, delta, period;
+ unsigned long ticks, estat;
ktime_t expire, now;
struct loongarch_csrs *csr = vcpu->arch.csr;

@@ -90,20 +91,46 @@ void kvm_restore_timer(struct kvm_vcpu *vcpu)
*/
hrtimer_cancel(&vcpu->arch.swtimer);

+ /*
+ * From LoongArch Reference Manual Volume 1 Chapter 7.6.2
+ * If oneshot timer is fired, CSR TVAL will be -1, there are two
+ * conditions:
+ * 1) timer is fired during exiting to host
+ * 2) timer is fired and vm is doing timer irq, and then exiting to
+ * host. Host should not inject timer irq to avoid spurious
+ * timer interrupt again
+ */
+ ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL);
+ estat = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_ESTAT);
+ if (!(cfg & CSR_TCFG_PERIOD) && (ticks > cfg)) {
+ /*
+ * Writing 0 to LOONGARCH_CSR_TVAL will inject timer irq
+ * and set CSR TVAL with -1
+ *
+ * Writing ticks to LOONGARCH_CSR_TVAL will not inject timer
+ * irq, HW CSR TVAL will go down from value ticks, it avoids
+ * spurious timer interrupt
+ */
+ if (estat & INT_TI)
+ write_gcsr_timertick(0);
+ else
+ kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TVAL);
+ return;
+ }
+
/*
* Set remainder tick value if not expired
*/
now = ktime_get();
expire = vcpu->arch.expire;
+ delta = 0;
if (ktime_before(now, expire))
delta = ktime_to_tick(vcpu, ktime_sub(expire, now));
- else {
- if (cfg & CSR_TCFG_PERIOD) {
- period = cfg & CSR_TCFG_VAL;
- delta = ktime_to_tick(vcpu, ktime_sub(now, expire));
- delta = period - (delta % period);
- } else
- delta = 0;
+ else if (cfg & CSR_TCFG_PERIOD) {
+ period = cfg & CSR_TCFG_VAL;
+ delta = ktime_to_tick(vcpu, ktime_sub(now, expire));
+ delta = period - (delta % period);
+
/*
* Inject timer here though sw timer should inject timer
* interrupt async already, since sw timer may be cancelled
@@ -122,15 +149,25 @@ void kvm_restore_timer(struct kvm_vcpu *vcpu)
*/
static void _kvm_save_timer(struct kvm_vcpu *vcpu)
{
- unsigned long ticks, delta;
+ unsigned long ticks, delta, cfg;
ktime_t expire;
struct loongarch_csrs *csr = vcpu->arch.csr;

ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL);
- delta = tick_to_ns(vcpu, ticks);
- expire = ktime_add_ns(ktime_get(), delta);
- vcpu->arch.expire = expire;
- if (ticks) {
+ cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG);
+
+ /*
+ * From LoongArch Reference Manual Volume 1 Chapter 7.6.2
+ * If period timer is fired, CSR TVAL will be reloaded from CSR TCFG
+ * If oneshot timer is fired, CSR TVAL will be -1
+ * Here judge one shot timer fired by checking whether TVAL is larger
+ * than TCFG
+ */
+ if (ticks < cfg) {
+ delta = tick_to_ns(vcpu, ticks);
+ expire = ktime_add_ns(ktime_get(), delta);
+ vcpu->arch.expire = expire;
+
/*
* HRTIMER_MODE_PINNED is suggested since vcpu may run in
* the same physical cpu in next time

base-commit: 98b1cc82c4affc16f5598d4fa14b1858671b2263
--
2.39.3