projects/linux·blob master · refs

212 lines · 5.6 KiB

1// SPDX-License-Identifier: GPL-2.0
2/*
3 * Copyright (C) 2020-2023 Loongson Technology Corporation Limited
4 */
5
6#include <linux/kvm_host.h>
7#include <asm/delay.h>
8#include <asm/kvm_csr.h>
9#include <asm/kvm_vcpu.h>
10
11/*
12 * ktime_to_tick() - Scale ktime_t to timer tick value.
13 */
14static inline u64 ktime_to_tick(struct kvm_vcpu *vcpu, ktime_t now)
15{
16 u64 delta;
17
18 delta = ktime_to_ns(now);
19 return div_u64(delta * vcpu->arch.timer_mhz, MNSEC_PER_SEC);
20}
21
22static inline u64 tick_to_ns(struct kvm_vcpu *vcpu, u64 tick)
23{
24 return div_u64(tick * MNSEC_PER_SEC, vcpu->arch.timer_mhz);
25}
26
27/* Low level hrtimer wake routine */
28enum hrtimer_restart kvm_swtimer_wakeup(struct hrtimer *timer)
29{
30 struct kvm_vcpu *vcpu;
31
32 vcpu = container_of(timer, struct kvm_vcpu, arch.swtimer);
33 kvm_vcpu_wake_up(vcpu);
34
35 return HRTIMER_NORESTART;
36}
37
38/*
39 * Initialise the timer to the specified frequency, zero it
40 */
41void kvm_init_timer(struct kvm_vcpu *vcpu, unsigned long timer_hz)
42{
43 vcpu->arch.timer_mhz = timer_hz >> 20;
44
45 /* Starting at 0 */
46 kvm_write_sw_gcsr(vcpu->arch.csr, LOONGARCH_CSR_TVAL, 0);
47}
48
49/*
50 * Restore soft timer state from saved context.
51 */
52void kvm_restore_timer(struct kvm_vcpu *vcpu)
53{
54 unsigned long cfg, estat;
55 unsigned long ticks, delta, period;
56 ktime_t expire, now;
57 struct loongarch_csrs *csr = vcpu->arch.csr;
58
59 /*
60 * Set guest stable timer cfg csr
61 * Disable timer before restore estat CSR register, avoid to
62 * get invalid timer interrupt for old timer cfg
63 */
64 cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG);
65
66 write_gcsr_timercfg(0);
67 kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_ESTAT);
68 kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TCFG);
69 if (!(cfg & CSR_TCFG_EN)) {
70 /* Guest timer is disabled, just restore timer registers */
71 kvm_restore_hw_gcsr(csr, LOONGARCH_CSR_TVAL);
72 return;
73 }
74
75 /*
76 * Freeze the soft-timer and sync the guest stable timer with it.
77 */
78 if (kvm_vcpu_is_blocking(vcpu))
79 hrtimer_cancel(&vcpu->arch.swtimer);
80
81 /*
82 * From LoongArch Reference Manual Volume 1 Chapter 7.6.2
83 * If oneshot timer is fired, CSR TVAL will be -1, there are two
84 * conditions:
85 * 1) timer is fired during exiting to host
86 * 2) timer is fired and vm is doing timer irq, and then exiting to
87 * host. Host should not inject timer irq to avoid spurious
88 * timer interrupt again
89 */
90 ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL);
91 estat = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_ESTAT);
92 if (!(cfg & CSR_TCFG_PERIOD) && (ticks > cfg)) {
93 /*
94 * Writing 0 to LOONGARCH_CSR_TVAL will inject timer irq
95 * and set CSR TVAL with -1
96 */
97 write_gcsr_timertick(0);
98
99 /*
100 * Writing CSR_TINTCLR_TI to LOONGARCH_CSR_TINTCLR will clear
101 * timer interrupt, and CSR TVAL keeps unchanged with -1, it
102 * avoids spurious timer interrupt
103 */
104 if (!(estat & CPU_TIMER)) {
105 __delay(2); /* Wait cycles until timer interrupt injected */
106
107 /* Write TVAL with max value if no TI shot */
108 estat = kvm_read_hw_gcsr(LOONGARCH_CSR_ESTAT);
109 if (!(estat & CPU_TIMER))
110 write_gcsr_timertick(CSR_TCFG_VAL);
111 gcsr_write(CSR_TINTCLR_TI, LOONGARCH_CSR_TINTCLR);
112 }
113 return;
114 }
115
116 /*
117 * Set remainder tick value if not expired
118 */
119 delta = 0;
120 now = ktime_get();
121 expire = vcpu->arch.expire;
122 if (!expire) {
123 /*
124 * vcpu->arch.expire is host-internal and is not migrated,
125 * so it is 0 after migration. Reload the remaining countdown
126 * from the migrated TVAL. This covers both one-shot and
127 * periodic timers.
128 */
129 if (ticks < cfg)
130 delta = tick_to_ns(vcpu, ticks);
131 expire = ktime_add_ns(now, delta);
132 }
133
134 if (ktime_before(now, expire))
135 delta = ktime_to_tick(vcpu, ktime_sub(expire, now));
136 else if (cfg & CSR_TCFG_PERIOD) {
137 period = cfg & CSR_TCFG_VAL;
138 if (!period)
139 period = 1;
140 delta = ktime_to_tick(vcpu, ktime_sub(now, expire));
141 delta = period - (delta % period);
142
143 /*
144 * Inject timer here though sw timer should inject timer
145 * interrupt async already, since sw timer may be cancelled
146 * during injecting intr async
147 */
148 kvm_queue_irq(vcpu, INT_TI);
149 }
150
151 write_gcsr_timertick(delta);
152}
153
154/*
155 * Save guest timer state and switch to software emulation of guest
156 * timer. The hard timer must already be in use, so preemption should be
157 * disabled.
158 */
159static void _kvm_save_timer(struct kvm_vcpu *vcpu)
160{
161 unsigned long ticks, delta, cfg;
162 ktime_t expire;
163 struct loongarch_csrs *csr = vcpu->arch.csr;
164
165 cfg = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG);
166 ticks = kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TVAL);
167
168 /*
169 * From LoongArch Reference Manual Volume 1 Chapter 7.6.2
170 * If period timer is fired, CSR TVAL will be reloaded from CSR TCFG
171 * If oneshot timer is fired, CSR TVAL will be -1
172 * Here judge one-shot timer fired by checking whether TVAL is larger
173 * than TCFG
174 */
175 if (ticks < cfg)
176 delta = tick_to_ns(vcpu, ticks);
177 else
178 delta = 0;
179
180 expire = ktime_add_ns(ktime_get(), delta);
181 vcpu->arch.expire = expire;
182 if (kvm_vcpu_is_blocking(vcpu)) {
183
184 /*
185 * HRTIMER_MODE_PINNED_HARD is suggested since vcpu may run in
186 * the same physical cpu in next time, and the timer should run
187 * in hardirq context even in the PREEMPT_RT case.
188 */
189 hrtimer_start(&vcpu->arch.swtimer, expire, HRTIMER_MODE_ABS_PINNED_HARD);
190 }
191}
192
193/*
194 * Save guest timer state and switch to soft guest timer if hard timer was in
195 * use.
196 */
197void kvm_save_timer(struct kvm_vcpu *vcpu)
198{
199 struct loongarch_csrs *csr = vcpu->arch.csr;
200
201 preempt_disable();
202
203 /* Save hard timer state */
204 kvm_save_hw_gcsr(csr, LOONGARCH_CSR_TCFG);
205 kvm_save_hw_gcsr(csr, LOONGARCH_CSR_TVAL);
206 if (kvm_read_sw_gcsr(csr, LOONGARCH_CSR_TCFG) & CSR_TCFG_EN)
207 _kvm_save_timer(vcpu);
208
209 /* Save timer-related state to vCPU context */
210 kvm_save_hw_gcsr(csr, LOONGARCH_CSR_ESTAT);
211 preempt_enable();
212}