xref: /qemu/target/riscv/time_helper.c (revision 14cb78bfaf4f99283252d9683ea4c0d97274ddea)
143888c2fSAtish Patra /*
243888c2fSAtish Patra  * RISC-V timer helper implementation.
343888c2fSAtish Patra  *
443888c2fSAtish Patra  * Copyright (c) 2022 Rivos Inc.
543888c2fSAtish Patra  *
643888c2fSAtish Patra  * This program is free software; you can redistribute it and/or modify it
743888c2fSAtish Patra  * under the terms and conditions of the GNU General Public License,
843888c2fSAtish Patra  * version 2 or later, as published by the Free Software Foundation.
943888c2fSAtish Patra  *
1043888c2fSAtish Patra  * This program is distributed in the hope it will be useful, but WITHOUT
1143888c2fSAtish Patra  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
1243888c2fSAtish Patra  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
1343888c2fSAtish Patra  * more details.
1443888c2fSAtish Patra  *
1543888c2fSAtish Patra  * You should have received a copy of the GNU General Public License along with
1643888c2fSAtish Patra  * this program.  If not, see <http://www.gnu.org/licenses/>.
1743888c2fSAtish Patra  */
1843888c2fSAtish Patra 
1943888c2fSAtish Patra #include "qemu/osdep.h"
2043888c2fSAtish Patra #include "qemu/log.h"
2143888c2fSAtish Patra #include "cpu_bits.h"
2243888c2fSAtish Patra #include "time_helper.h"
2343888c2fSAtish Patra #include "hw/intc/riscv_aclint.h"
2443888c2fSAtish Patra 
253ec0fe18SAtish Patra static void riscv_vstimer_cb(void *opaque)
263ec0fe18SAtish Patra {
273ec0fe18SAtish Patra     RISCVCPU *cpu = opaque;
283ec0fe18SAtish Patra     CPURISCVState *env = &cpu->env;
293ec0fe18SAtish Patra     env->vstime_irq = 1;
30*14cb78bfSAnup Patel     riscv_cpu_update_mip(cpu, 0, BOOL_TO_MASK(1));
313ec0fe18SAtish Patra }
323ec0fe18SAtish Patra 
3343888c2fSAtish Patra static void riscv_stimer_cb(void *opaque)
3443888c2fSAtish Patra {
3543888c2fSAtish Patra     RISCVCPU *cpu = opaque;
3643888c2fSAtish Patra     riscv_cpu_update_mip(cpu, MIP_STIP, BOOL_TO_MASK(1));
3743888c2fSAtish Patra }
3843888c2fSAtish Patra 
3943888c2fSAtish Patra /*
4043888c2fSAtish Patra  * Called when timecmp is written to update the QEMU timer or immediately
4143888c2fSAtish Patra  * trigger timer interrupt if mtimecmp <= current timer value.
4243888c2fSAtish Patra  */
4343888c2fSAtish Patra void riscv_timer_write_timecmp(RISCVCPU *cpu, QEMUTimer *timer,
4443888c2fSAtish Patra                                uint64_t timecmp, uint64_t delta,
4543888c2fSAtish Patra                                uint32_t timer_irq)
4643888c2fSAtish Patra {
4743888c2fSAtish Patra     uint64_t diff, ns_diff, next;
4843888c2fSAtish Patra     CPURISCVState *env = &cpu->env;
4943888c2fSAtish Patra     RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
5043888c2fSAtish Patra     uint32_t timebase_freq = mtimer->timebase_freq;
5143888c2fSAtish Patra     uint64_t rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
5243888c2fSAtish Patra 
5343888c2fSAtish Patra     if (timecmp <= rtc_r) {
5443888c2fSAtish Patra         /*
5543888c2fSAtish Patra          * If we're setting an stimecmp value in the "past",
5643888c2fSAtish Patra          * immediately raise the timer interrupt
5743888c2fSAtish Patra          */
583ec0fe18SAtish Patra         if (timer_irq == MIP_VSTIP) {
593ec0fe18SAtish Patra             env->vstime_irq = 1;
60*14cb78bfSAnup Patel             riscv_cpu_update_mip(cpu, 0, BOOL_TO_MASK(1));
61*14cb78bfSAnup Patel         } else {
62*14cb78bfSAnup Patel             riscv_cpu_update_mip(cpu, MIP_STIP, BOOL_TO_MASK(1));
633ec0fe18SAtish Patra         }
6443888c2fSAtish Patra         return;
6543888c2fSAtish Patra     }
6643888c2fSAtish Patra 
67*14cb78bfSAnup Patel     /* Clear the [VS|S]TIP bit in mip */
683ec0fe18SAtish Patra     if (timer_irq == MIP_VSTIP) {
693ec0fe18SAtish Patra         env->vstime_irq = 0;
70*14cb78bfSAnup Patel         riscv_cpu_update_mip(cpu, 0, BOOL_TO_MASK(0));
71*14cb78bfSAnup Patel     } else {
7243888c2fSAtish Patra         riscv_cpu_update_mip(cpu, timer_irq, BOOL_TO_MASK(0));
73*14cb78bfSAnup Patel     }
7443888c2fSAtish Patra 
7543888c2fSAtish Patra     /* otherwise, set up the future timer interrupt */
7643888c2fSAtish Patra     diff = timecmp - rtc_r;
7743888c2fSAtish Patra     /* back to ns (note args switched in muldiv64) */
7843888c2fSAtish Patra     ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
7943888c2fSAtish Patra 
8043888c2fSAtish Patra     /*
8143888c2fSAtish Patra      * check if ns_diff overflowed and check if the addition would potentially
8243888c2fSAtish Patra      * overflow
8343888c2fSAtish Patra      */
8443888c2fSAtish Patra     if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
8543888c2fSAtish Patra         ns_diff > INT64_MAX) {
8643888c2fSAtish Patra         next = INT64_MAX;
8743888c2fSAtish Patra     } else {
8843888c2fSAtish Patra         /*
8943888c2fSAtish Patra          * as it is very unlikely qemu_clock_get_ns will return a value
9043888c2fSAtish Patra          * greater than INT64_MAX, no additional check is needed for an
9143888c2fSAtish Patra          * unsigned integer overflow.
9243888c2fSAtish Patra          */
9343888c2fSAtish Patra         next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
9443888c2fSAtish Patra         /*
9543888c2fSAtish Patra          * if ns_diff is INT64_MAX next may still be outside the range
9643888c2fSAtish Patra          * of a signed integer.
9743888c2fSAtish Patra          */
9843888c2fSAtish Patra         next = MIN(next, INT64_MAX);
9943888c2fSAtish Patra     }
10043888c2fSAtish Patra 
10143888c2fSAtish Patra     timer_mod(timer, next);
10243888c2fSAtish Patra }
10343888c2fSAtish Patra 
10443888c2fSAtish Patra void riscv_timer_init(RISCVCPU *cpu)
10543888c2fSAtish Patra {
10643888c2fSAtish Patra     CPURISCVState *env;
10743888c2fSAtish Patra 
10843888c2fSAtish Patra     if (!cpu) {
10943888c2fSAtish Patra         return;
11043888c2fSAtish Patra     }
11143888c2fSAtish Patra 
11243888c2fSAtish Patra     env = &cpu->env;
11343888c2fSAtish Patra     env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
11443888c2fSAtish Patra     env->stimecmp = 0;
11543888c2fSAtish Patra 
1163ec0fe18SAtish Patra     env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu);
1173ec0fe18SAtish Patra     env->vstimecmp = 0;
11843888c2fSAtish Patra }
119