xref: /qemu/target/riscv/time_helper.c (revision e240f6cc25917f3138d9e95e0343ae23b63a3f8c)
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 
riscv_vstimer_cb(void * opaque)25 static void riscv_vstimer_cb(void *opaque)
26 {
27     RISCVCPU *cpu = opaque;
28     CPURISCVState *env = &cpu->env;
29     env->vstime_irq = 1;
30     riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
31 }
32 
riscv_stimer_cb(void * opaque)33 static void riscv_stimer_cb(void *opaque)
34 {
35     RISCVCPU *cpu = opaque;
36     riscv_cpu_update_mip(&cpu->env, MIP_STIP, BOOL_TO_MASK(1));
37 }
38 
39 /*
40  * Called when timecmp is written to update the QEMU timer or immediately
41  * trigger timer interrupt if mtimecmp <= current timer value.
42  */
riscv_timer_write_timecmp(CPURISCVState * env,QEMUTimer * timer,uint64_t timecmp,uint64_t delta,uint32_t timer_irq)43 void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer,
44                                uint64_t timecmp, uint64_t delta,
45                                uint32_t timer_irq)
46 {
47     uint64_t diff, ns_diff, next;
48     RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
49     uint32_t timebase_freq;
50     uint64_t rtc_r;
51 
52     if (!riscv_cpu_cfg(env)->ext_sstc || !env->rdtime_fn ||
53         !env->rdtime_fn_arg || !get_field(env->menvcfg, MENVCFG_STCE)) {
54         /* S/VS Timer IRQ depends on sstc extension, rdtime_fn(), and STCE. */
55         return;
56     }
57 
58     if (timer_irq == MIP_VSTIP &&
59         (!riscv_has_ext(env, RVH) || !get_field(env->henvcfg, HENVCFG_STCE))) {
60         /* VS Timer IRQ also depends on RVH and henvcfg.STCE. */
61         return;
62     }
63 
64     timebase_freq = mtimer->timebase_freq;
65     rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
66 
67     if (timecmp <= rtc_r) {
68         /*
69          * If we're setting an stimecmp value in the "past",
70          * immediately raise the timer interrupt
71          */
72         if (timer_irq == MIP_VSTIP) {
73             env->vstime_irq = 1;
74             riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
75         } else {
76             riscv_cpu_update_mip(env, MIP_STIP, BOOL_TO_MASK(1));
77         }
78         return;
79     }
80 
81     /* Clear the [VS|S]TIP bit in mip */
82     if (timer_irq == MIP_VSTIP) {
83         env->vstime_irq = 0;
84         riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(0));
85     } else {
86         riscv_cpu_update_mip(env, timer_irq, BOOL_TO_MASK(0));
87     }
88 
89     /*
90      * Sstc specification says the following about timer interrupt:
91      * "A supervisor timer interrupt becomes pending - as reflected in
92      * the STIP bit in the mip and sip registers - whenever time contains
93      * a value greater than or equal to stimecmp, treating the values
94      * as unsigned integers. Writes to stimecmp are guaranteed to be
95      * reflected in STIP eventually, but not necessarily immediately.
96      * The interrupt remains posted until stimecmp becomes greater
97      * than time - typically as a result of writing stimecmp."
98      *
99      * When timecmp = UINT64_MAX, the time CSR will eventually reach
100      * timecmp value but on next timer tick the time CSR will wrap-around
101      * and become zero which is less than UINT64_MAX. Now, the timer
102      * interrupt behaves like a level triggered interrupt so it will
103      * become 1 when time = timecmp = UINT64_MAX and next timer tick
104      * it will become 0 again because time = 0 < timecmp = UINT64_MAX.
105      *
106      * Based on above, we don't re-start the QEMU timer when timecmp
107      * equals UINT64_MAX.
108      */
109     if (timecmp == UINT64_MAX) {
110         timer_del(timer);
111         return;
112     }
113 
114     /* otherwise, set up the future timer interrupt */
115     diff = timecmp - rtc_r;
116     /* back to ns (note args switched in muldiv64) */
117     ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
118 
119     /*
120      * check if ns_diff overflowed and check if the addition would potentially
121      * overflow
122      */
123     if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
124         ns_diff > INT64_MAX) {
125         next = INT64_MAX;
126     } else {
127         /*
128          * as it is very unlikely qemu_clock_get_ns will return a value
129          * greater than INT64_MAX, no additional check is needed for an
130          * unsigned integer overflow.
131          */
132         next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
133         /*
134          * if ns_diff is INT64_MAX next may still be outside the range
135          * of a signed integer.
136          */
137         next = MIN(next, INT64_MAX);
138     }
139 
140     timer_mod(timer, next);
141 }
142 
143 /*
144  * When disabling xenvcfg.STCE, the S/VS Timer may be disabled at the same time.
145  * It is safe to call this function regardless of whether the timer has been
146  * deleted or not. timer_del() will do nothing if the timer has already
147  * been deleted.
148  */
riscv_timer_disable_timecmp(CPURISCVState * env,QEMUTimer * timer,uint32_t timer_irq)149 static void riscv_timer_disable_timecmp(CPURISCVState *env, QEMUTimer *timer,
150                                  uint32_t timer_irq)
151 {
152     /* Disable S-mode Timer IRQ and HW-based STIP */
153     if ((timer_irq == MIP_STIP) && !get_field(env->menvcfg, MENVCFG_STCE)) {
154         riscv_cpu_update_mip(env, timer_irq, BOOL_TO_MASK(0));
155         timer_del(timer);
156         return;
157     }
158 
159     /* Disable VS-mode Timer IRQ and HW-based VSTIP */
160     if ((timer_irq == MIP_VSTIP) &&
161         (!get_field(env->menvcfg, MENVCFG_STCE) ||
162          !get_field(env->henvcfg, HENVCFG_STCE))) {
163         env->vstime_irq = 0;
164         riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(0));
165         timer_del(timer);
166         return;
167     }
168 }
169 
170 /* Enable or disable S/VS-mode Timer when xenvcfg.STCE is changed */
riscv_timer_stce_changed(CPURISCVState * env,bool is_m_mode,bool enable)171 void riscv_timer_stce_changed(CPURISCVState *env, bool is_m_mode, bool enable)
172 {
173     if (enable) {
174         riscv_timer_write_timecmp(env, env->vstimer, env->vstimecmp,
175                                   env->htimedelta, MIP_VSTIP);
176     } else {
177         riscv_timer_disable_timecmp(env, env->vstimer, MIP_VSTIP);
178     }
179 
180     if (is_m_mode) {
181         if (enable) {
182             riscv_timer_write_timecmp(env, env->stimer, env->stimecmp, 0, MIP_STIP);
183         } else {
184             riscv_timer_disable_timecmp(env, env->stimer, MIP_STIP);
185         }
186     }
187 }
188 
riscv_timer_init(RISCVCPU * cpu)189 void riscv_timer_init(RISCVCPU *cpu)
190 {
191     CPURISCVState *env;
192 
193     if (!cpu) {
194         return;
195     }
196 
197     env = &cpu->env;
198     env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
199     env->stimecmp = 0;
200 
201     env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu);
202     env->vstimecmp = 0;
203 }
204