master
c 220 lines 7.01 KB
Raw
1 /*
2 * RISC-V timer helper implementation.
3 *
4 * Copyright (c) 2022 Rivos Inc.
5 *
6 * This program is free software; you can redistribute it and/or modify it
7 * under the terms and conditions of the GNU General Public License,
8 * version 2 or later, as published by the Free Software Foundation.
9 *
10 * This program is distributed in the hope it will be useful, but WITHOUT
11 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
12 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
13 * more details.
14 *
15 * You should have received a copy of the GNU General Public License along with
16 * this program. If not, see <http://www.gnu.org/licenses/>.
17 */
18
19 #include "qemu/osdep.h"
20 #include "qemu/log.h"
21 #include "cpu_bits.h"
22 #include "time_helper.h"
23 #include "hw/intc/riscv_aclint.h"
24 #include "kvm/kvm_riscv.h"
25 #include "system/kvm.h"
26 #include "system/tcg.h"
27
28 static void riscv_accel_set_irq(RISCVCPU *cpu, int irq, int level)
29 {
30 if (kvm_enabled()) {
31 kvm_riscv_set_irq(cpu, irq, level);
32 }
33
34 if (tcg_enabled()) {
35 riscv_cpu_update_mip(&cpu->env, irq, level);
36 }
37 }
38
39
40 static void riscv_vstimer_cb(void *opaque)
41 {
42 RISCVCPU *cpu = opaque;
43 CPURISCVState *env = &cpu->env;
44 env->vstime_irq = 1;
45 riscv_accel_set_irq(cpu, 0, BOOL_TO_MASK(1));
46 }
47
48 static void riscv_stimer_cb(void *opaque)
49 {
50 RISCVCPU *cpu = opaque;
51 riscv_accel_set_irq(cpu, MIP_STIP, BOOL_TO_MASK(1));
52 }
53
54 /*
55 * Called when timecmp is written to update the QEMU timer or immediately
56 * trigger timer interrupt if mtimecmp <= current timer value.
57 */
58 void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer,
59 uint64_t timecmp, uint64_t delta,
60 uint32_t timer_irq)
61 {
62 uint64_t diff, ns_diff, next;
63 RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
64 uint32_t timebase_freq;
65 uint64_t rtc_r;
66 RISCVCPU *cpu;
67
68 if (!riscv_cpu_cfg(env)->ext_sstc || !env->rdtime_fn ||
69 !env->rdtime_fn_arg || !get_field(env->menvcfg, MENVCFG_STCE)) {
70 /* S/VS Timer IRQ depends on sstc extension, rdtime_fn(), and STCE. */
71 return;
72 }
73
74 if (timer_irq == MIP_VSTIP &&
75 (!riscv_has_ext(env, RVH) || !get_field(env->henvcfg, HENVCFG_STCE))) {
76 /* VS Timer IRQ also depends on RVH and henvcfg.STCE. */
77 return;
78 }
79
80 timebase_freq = mtimer->timebase_freq;
81 rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
82 cpu = env_archcpu(env);
83
84 if (timecmp <= rtc_r) {
85 /*
86 * If we're setting an stimecmp value in the "past",
87 * immediately raise the timer interrupt
88 */
89 if (timer_irq == MIP_VSTIP) {
90 env->vstime_irq = 1;
91 riscv_accel_set_irq(cpu, 0, BOOL_TO_MASK(1));
92 } else {
93 riscv_accel_set_irq(cpu, MIP_STIP, BOOL_TO_MASK(1));
94 }
95 return;
96 }
97
98 /* Clear the [VS|S]TIP bit in mip */
99 if (timer_irq == MIP_VSTIP) {
100 env->vstime_irq = 0;
101 riscv_accel_set_irq(cpu, 0, BOOL_TO_MASK(0));
102 } else {
103 riscv_accel_set_irq(cpu, timer_irq, BOOL_TO_MASK(0));
104 }
105
106 /*
107 * Sstc specification says the following about timer interrupt:
108 * "A supervisor timer interrupt becomes pending - as reflected in
109 * the STIP bit in the mip and sip registers - whenever time contains
110 * a value greater than or equal to stimecmp, treating the values
111 * as unsigned integers. Writes to stimecmp are guaranteed to be
112 * reflected in STIP eventually, but not necessarily immediately.
113 * The interrupt remains posted until stimecmp becomes greater
114 * than time - typically as a result of writing stimecmp."
115 *
116 * When timecmp = UINT64_MAX, the time CSR will eventually reach
117 * timecmp value but on next timer tick the time CSR will wrap-around
118 * and become zero which is less than UINT64_MAX. Now, the timer
119 * interrupt behaves like a level triggered interrupt so it will
120 * become 1 when time = timecmp = UINT64_MAX and next timer tick
121 * it will become 0 again because time = 0 < timecmp = UINT64_MAX.
122 *
123 * Based on above, we don't re-start the QEMU timer when timecmp
124 * equals UINT64_MAX.
125 */
126 if (timecmp == UINT64_MAX) {
127 timer_del(timer);
128 return;
129 }
130
131 /* otherwise, set up the future timer interrupt */
132 diff = timecmp - rtc_r;
133 /* back to ns (note args switched in muldiv64) */
134 ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
135
136 /*
137 * check if ns_diff overflowed and check if the addition would potentially
138 * overflow
139 */
140 if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
141 ns_diff > INT64_MAX) {
142 next = INT64_MAX;
143 } else {
144 /*
145 * as it is very unlikely qemu_clock_get_ns will return a value
146 * greater than INT64_MAX, no additional check is needed for an
147 * unsigned integer overflow.
148 */
149 next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
150 /*
151 * if ns_diff is INT64_MAX next may still be outside the range
152 * of a signed integer.
153 */
154 next = MIN(next, INT64_MAX);
155 }
156
157 timer_mod(timer, next);
158 }
159
160 /*
161 * When disabling xenvcfg.STCE, the S/VS Timer may be disabled at the same time.
162 * It is safe to call this function regardless of whether the timer has been
163 * deleted or not. timer_del() will do nothing if the timer has already
164 * been deleted.
165 */
166 static void riscv_timer_disable_timecmp(CPURISCVState *env, QEMUTimer *timer,
167 uint32_t timer_irq)
168 {
169 /* Disable S-mode Timer IRQ and HW-based STIP */
170 if ((timer_irq == MIP_STIP) && !get_field(env->menvcfg, MENVCFG_STCE)) {
171 riscv_accel_set_irq(env_archcpu(env), timer_irq, BOOL_TO_MASK(0));
172 timer_del(timer);
173 return;
174 }
175
176 /* Disable VS-mode Timer IRQ and HW-based VSTIP */
177 if ((timer_irq == MIP_VSTIP) &&
178 (!get_field(env->menvcfg, MENVCFG_STCE) ||
179 !get_field(env->henvcfg, HENVCFG_STCE))) {
180 env->vstime_irq = 0;
181 riscv_accel_set_irq(env_archcpu(env), 0, BOOL_TO_MASK(0));
182 timer_del(timer);
183 return;
184 }
185 }
186
187 /* Enable or disable S/VS-mode Timer when xenvcfg.STCE is changed */
188 void riscv_timer_stce_changed(CPURISCVState *env, bool is_m_mode, bool enable)
189 {
190 if (enable) {
191 riscv_timer_write_timecmp(env, env->vstimer, env->vstimecmp,
192 env->htimedelta, MIP_VSTIP);
193 } else {
194 riscv_timer_disable_timecmp(env, env->vstimer, MIP_VSTIP);
195 }
196
197 if (is_m_mode) {
198 if (enable) {
199 riscv_timer_write_timecmp(env, env->stimer, env->stimecmp, 0, MIP_STIP);
200 } else {
201 riscv_timer_disable_timecmp(env, env->stimer, MIP_STIP);
202 }
203 }
204 }
205
206 void riscv_timer_init(RISCVCPU *cpu)
207 {
208 CPURISCVState *env;
209
210 if (!cpu) {
211 return;
212 }
213
214 env = &cpu->env;
215 env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
216 env->stimecmp = 0;
217
218 env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu);
219 env->vstimecmp = 0;
220 }