Commit d709d6db authored by Bibo Mao's avatar Bibo Mao Committed by openeuler-sync-bot
Browse files

loongarch/kvm: Fix oneshot timer emulation

LoongArch inclusion
category: bugfix
bugzilla: https://gitee.com/openeuler/kernel/issues/I8I3QU



------------------------------------------

When oneshot timer is fired, CSR TVAL will be -1 rather than 0.
It is not remaining timer ticks for expired time. There needs
special handing for this situation.

With this patch, runltp with version ltp20230516 passes to run
in vm.

Signed-off-by: default avatarBibo Mao <maobibo@loongson.cn>
(cherry picked from commit 1bddc4cd)
parent 730c627d
Loading
Loading
Loading
Loading
+46 −12
Original line number Diff line number Diff line
@@ -84,6 +84,7 @@ void kvm_restore_timer(struct kvm_vcpu *vcpu)
	struct loongarch_csrs *csr = vcpu->arch.csr;
	ktime_t expire, now;
	unsigned long cfg, delta, period;
	unsigned long ticks, estat;

	/*
	 * Set guest stable timer cfg csr
@@ -103,20 +104,44 @@ 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:
	 *  a) timer is fired during exiting to host
	 *  b) timer is fired and vm is handling timer irq, host should not
	 *     inject timer irq to avoid spurious timer interrupt
	 */
	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 CSR_TINTCLR_TI to LOONGARCH_CSR_TINTCLR will clear
		 * timer interrupt, and set CSR TVAL keeps unchanged with -1,
		 * it avoids spurious timer interrupt
		 */
		kvm_write_gcsr_timertick(0);
		if ((estat & CPU_TIMER) == 0)
			kvm_gcsr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR);
		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) {
	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;

		/*
		 * inject timer here though sw timer should inject timer
		 * interrupt async already
@@ -134,20 +159,29 @@ 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) {
		/*
		 * Update hrtimer to use new timeout
		 * HRTIMER_MODE_PINNED is suggested since vcpu may run in
		 * the same physical cpu in next time
		 */
		delta = tick_to_ns(vcpu, ticks);
		expire = ktime_add_ns(ktime_get(), delta);
		vcpu->arch.expire = expire;
		hrtimer_start(&vcpu->arch.swtimer, expire, HRTIMER_MODE_ABS_PINNED);
	} else if (vcpu->arch.blocking) {
		/*