xref: /openbmc/qemu/target/riscv/time_helper.c (revision 2e1cacfb)
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 
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 
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  */
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 = mtimer->timebase_freq;
50     uint64_t rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
51 
52     if (timecmp <= rtc_r) {
53         /*
54          * If we're setting an stimecmp value in the "past",
55          * immediately raise the timer interrupt
56          */
57         if (timer_irq == MIP_VSTIP) {
58             env->vstime_irq = 1;
59             riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
60         } else {
61             riscv_cpu_update_mip(env, MIP_STIP, BOOL_TO_MASK(1));
62         }
63         return;
64     }
65 
66     /* Clear the [VS|S]TIP bit in mip */
67     if (timer_irq == MIP_VSTIP) {
68         env->vstime_irq = 0;
69         riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(0));
70     } else {
71         riscv_cpu_update_mip(env, timer_irq, BOOL_TO_MASK(0));
72     }
73 
74     /*
75      * Sstc specification says the following about timer interrupt:
76      * "A supervisor timer interrupt becomes pending - as reflected in
77      * the STIP bit in the mip and sip registers - whenever time contains
78      * a value greater than or equal to stimecmp, treating the values
79      * as unsigned integers. Writes to stimecmp are guaranteed to be
80      * reflected in STIP eventually, but not necessarily immediately.
81      * The interrupt remains posted until stimecmp becomes greater
82      * than time - typically as a result of writing stimecmp."
83      *
84      * When timecmp = UINT64_MAX, the time CSR will eventually reach
85      * timecmp value but on next timer tick the time CSR will wrap-around
86      * and become zero which is less than UINT64_MAX. Now, the timer
87      * interrupt behaves like a level triggered interrupt so it will
88      * become 1 when time = timecmp = UINT64_MAX and next timer tick
89      * it will become 0 again because time = 0 < timecmp = UINT64_MAX.
90      *
91      * Based on above, we don't re-start the QEMU timer when timecmp
92      * equals UINT64_MAX.
93      */
94     if (timecmp == UINT64_MAX) {
95         timer_del(timer);
96         return;
97     }
98 
99     /* otherwise, set up the future timer interrupt */
100     diff = timecmp - rtc_r;
101     /* back to ns (note args switched in muldiv64) */
102     ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
103 
104     /*
105      * check if ns_diff overflowed and check if the addition would potentially
106      * overflow
107      */
108     if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
109         ns_diff > INT64_MAX) {
110         next = INT64_MAX;
111     } else {
112         /*
113          * as it is very unlikely qemu_clock_get_ns will return a value
114          * greater than INT64_MAX, no additional check is needed for an
115          * unsigned integer overflow.
116          */
117         next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
118         /*
119          * if ns_diff is INT64_MAX next may still be outside the range
120          * of a signed integer.
121          */
122         next = MIN(next, INT64_MAX);
123     }
124 
125     timer_mod(timer, next);
126 }
127 
128 void riscv_timer_init(RISCVCPU *cpu)
129 {
130     CPURISCVState *env;
131 
132     if (!cpu) {
133         return;
134     }
135 
136     env = &cpu->env;
137     env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
138     env->stimecmp = 0;
139 
140     env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu);
141     env->vstimecmp = 0;
142 }
143