| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2020-2023 Loongson Technology Corporation Limited | |
| */ | |
| #include <linux/kvm_host.h> | |
| #include <asm/delay.h> | |
| #include <asm/kvm_csr.h> | |
| #include <asm/kvm_vcpu.h> | |
| /* | |
| * ktime_to_tick() - Scale ktime_t to timer tick value. | |
| */ | |
| static inline u64 ktime_to_tick(struct kvm_vcpu *vcpu, ktime_t now) | |
| { | |
| u64 delta; | |
| delta = ktime_to_ns(now); | |
| return div_u64(delta * vcpu->arch.timer_mhz, MNSEC_PER_SEC); | |
| } | |
| static inline u64 tick_to_ns(struct kvm_vcpu *vcpu, u64 tick) | |
| { | |
| return div_u64(tick * MNSEC_PER_SEC, vcpu->arch.timer_mhz); | |
| } | |
| /* Low level hrtimer wake routine */ | |
| enum hrtimer_restart kvm_swtimer_wakeup(struct hrtimer *timer) | |
| { | |
| struct kvm_vcpu *vcpu; | |
| vcpu = container_of(timer, struct kvm_vcpu, arch.swtimer); | |
| kvm_vcpu_wake_up(vcpu); | |
| return HRTIMER_NORESTART; | |
| } | |
| /* | |
| * Initialise the timer to the specified frequency, zero it | |
| */ | |
| void kvm_init_timer(struct kvm_vcpu *vcpu, unsigned long timer_hz) | |
| { | |
| vcpu->arch.timer_mhz = timer_hz >> 20; | |
| /* Starting at 0 */ | |
| kvm_write_sw_gcsr(vcpu->arch.csr, LOONGARCH_CSR_TVAL, 0); | |
| } | |
| /* | |
| * Restore soft timer state from saved context. | |
| */ | |
| void kvm_restore_timer(struct kvm_vcpu *vcpu) | |
| { | |
| unsigned long cfg, estat; | |
| unsigned long ticks, delta, period; | |
| ktime_t expire, now; | |
| struct loongarch_csrs *csr = vcpu->arch.csr; | |
| /* | |
| * Set guest stable timer cfg csr | |
| * Disable timer before restore estat CSR register, avoid to | |
| * get invalid timer interrupt for old timer cfg | |
| */ | |
| cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG); | |
| write_gcsr_timercfg(0); | |
| kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_ESTAT); | |
| kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TCFG); | |
| if (!(cfg & CSR_TCFG_EN)) { | |
| /* Guest timer is disabled, just restore timer registers */ | |
| kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TVAL); | |
| return; | |
| } | |
| /* | |
| * Freeze the soft-timer and sync the guest stable timer with it. | |
| */ | |
| if (kvm_vcpu_is_blocking(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 | |
| */ | |
| write_gcsr_timertick(0); | |
| /* | |
| * Writing CSR_TINTCLR_TI to LOONGARCH_CSR_TINTCLR will clear | |
| * timer interrupt, and CSR TVAL keeps unchanged with -1, it | |
| * avoids spurious timer interrupt | |
| */ | |
| if (!(estat & CPU_TIMER)) { | |
| __delay(2); /* Wait cycles until timer interrupt injected */ | |
| /* Write TVAL with max value if no TI shot */ | |
| estat = kvm_read_hw_gcsr(LOONGARCH_CSR_ESTAT); | |
| if (!(estat & CPU_TIMER)) | |
| write_gcsr_timertick(CSR_TCFG_VAL); | |
| gcsr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR); | |
| } | |
| return; | |
| } | |
| /* | |
| * Set remainder tick value if not expired | |
| */ | |
| delta = 0; | |
| now = ktime_get(); | |
| expire = vcpu->arch.expire; | |
| if (!expire) { | |
| /* | |
| * vcpu->arch.expire is host-internal and is not migrated, | |
| * so it is 0 after migration. Reload the remaining countdown | |
| * from the migrated TVAL. This covers both one-shot and | |
| * periodic timers. | |
| */ | |
| if (ticks < cfg) | |
| delta = tick_to_ns(vcpu, ticks); | |
| expire = ktime_add_ns(now, delta); | |
| } | |
| 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; | |
| if (!period) | |
| period = 1; | |
| 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 | |
| * during injecting intr async | |
| */ | |
| kvm_queue_irq(vcpu, INT_TI); | |
| } | |
| write_gcsr_timertick(delta); | |
| } | |
| /* | |
| * Save guest timer state and switch to software emulation of guest | |
| * timer. The hard timer must already be in use, so preemption should be | |
| * disabled. | |
| */ | |
| static void _kvm_save_timer(struct kvm_vcpu *vcpu) | |
| { | |
| unsigned long ticks, delta, cfg; | |
| ktime_t expire; | |
| struct loongarch_csrs *csr = vcpu->arch.csr; | |
| cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG); | |
| ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL); | |
| /* | |
| * 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); | |
| else | |
| delta = 0; | |
| expire = ktime_add_ns(ktime_get(), delta); | |
| vcpu->arch.expire = expire; | |
| if (kvm_vcpu_is_blocking(vcpu)) { | |
| /* | |
| * HRTIMER_MODE_PINNED_HARD is suggested since vcpu may run in | |
| * the same physical cpu in next time, and the timer should run | |
| * in hardirq context even in the PREEMPT_RT case. | |
| */ | |
| hrtimer_start(&vcpu->arch.swtimer, expire, HRTIMER_MODE_ABS_PINNED_HARD); | |
| } | |
| } | |
| /* | |
| * Save guest timer state and switch to soft guest timer if hard timer was in | |
| * use. | |
| */ | |
| void kvm_save_timer(struct kvm_vcpu *vcpu) | |
| { | |
| struct loongarch_csrs *csr = vcpu->arch.csr; | |
| preempt_disable(); | |
| /* Save hard timer state */ | |
| kvm_save_hw_gcsr(csr, LOONGARCH_CSR_TCFG); | |
| kvm_save_hw_gcsr(csr, LOONGARCH_CSR_TVAL); | |
| if (kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG) & CSR_TCFG_EN) | |
| _kvm_save_timer(vcpu); | |
| /* Save timer-related state to vCPU context */ | |
| kvm_save_hw_gcsr(csr, LOONGARCH_CSR_ESTAT); | |
| preempt_enable(); | |
| } |