xref: /openbmc/qemu/target/riscv/time_helper.c (revision 43888c2f)
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_stimer_cb(void *opaque)
26 {
27     RISCVCPU *cpu = opaque;
28     riscv_cpu_update_mip(cpu, MIP_STIP, BOOL_TO_MASK(1));
29 }
30 
31 /*
32  * Called when timecmp is written to update the QEMU timer or immediately
33  * trigger timer interrupt if mtimecmp <= current timer value.
34  */
35 void riscv_timer_write_timecmp(RISCVCPU *cpu, QEMUTimer *timer,
36                                uint64_t timecmp, uint64_t delta,
37                                uint32_t timer_irq)
38 {
39     uint64_t diff, ns_diff, next;
40     CPURISCVState *env = &cpu->env;
41     RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
42     uint32_t timebase_freq = mtimer->timebase_freq;
43     uint64_t rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
44 
45     if (timecmp <= rtc_r) {
46         /*
47          * If we're setting an stimecmp value in the "past",
48          * immediately raise the timer interrupt
49          */
50         riscv_cpu_update_mip(cpu, timer_irq, BOOL_TO_MASK(1));
51         return;
52     }
53 
54     /* Clear the [V]STIP bit in mip */
55     riscv_cpu_update_mip(cpu, timer_irq, BOOL_TO_MASK(0));
56 
57     /* otherwise, set up the future timer interrupt */
58     diff = timecmp - rtc_r;
59     /* back to ns (note args switched in muldiv64) */
60     ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
61 
62     /*
63      * check if ns_diff overflowed and check if the addition would potentially
64      * overflow
65      */
66     if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
67         ns_diff > INT64_MAX) {
68         next = INT64_MAX;
69     } else {
70         /*
71          * as it is very unlikely qemu_clock_get_ns will return a value
72          * greater than INT64_MAX, no additional check is needed for an
73          * unsigned integer overflow.
74          */
75         next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
76         /*
77          * if ns_diff is INT64_MAX next may still be outside the range
78          * of a signed integer.
79          */
80         next = MIN(next, INT64_MAX);
81     }
82 
83     timer_mod(timer, next);
84 }
85 
86 void riscv_timer_init(RISCVCPU *cpu)
87 {
88     CPURISCVState *env;
89 
90     if (!cpu) {
91         return;
92     }
93 
94     env = &cpu->env;
95     env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
96     env->stimecmp = 0;
97 
98 }
99