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(cpu, 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, 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(RISCVCPU *cpu, QEMUTimer *timer, 44 uint64_t timecmp, uint64_t delta, 45 uint32_t timer_irq) 46 { 47 uint64_t diff, ns_diff, next; 48 CPURISCVState *env = &cpu->env; 49 RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg; 50 uint32_t timebase_freq = mtimer->timebase_freq; 51 uint64_t rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta; 52 53 if (timecmp <= rtc_r) { 54 /* 55 * If we're setting an stimecmp value in the "past", 56 * immediately raise the timer interrupt 57 */ 58 if (timer_irq == MIP_VSTIP) { 59 env->vstime_irq = 1; 60 riscv_cpu_update_mip(cpu, 0, BOOL_TO_MASK(1)); 61 } else { 62 riscv_cpu_update_mip(cpu, MIP_STIP, BOOL_TO_MASK(1)); 63 } 64 return; 65 } 66 67 /* Clear the [VS|S]TIP bit in mip */ 68 if (timer_irq == MIP_VSTIP) { 69 env->vstime_irq = 0; 70 riscv_cpu_update_mip(cpu, 0, BOOL_TO_MASK(0)); 71 } else { 72 riscv_cpu_update_mip(cpu, timer_irq, BOOL_TO_MASK(0)); 73 } 74 75 /* otherwise, set up the future timer interrupt */ 76 diff = timecmp - rtc_r; 77 /* back to ns (note args switched in muldiv64) */ 78 ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq); 79 80 /* 81 * check if ns_diff overflowed and check if the addition would potentially 82 * overflow 83 */ 84 if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) || 85 ns_diff > INT64_MAX) { 86 next = INT64_MAX; 87 } else { 88 /* 89 * as it is very unlikely qemu_clock_get_ns will return a value 90 * greater than INT64_MAX, no additional check is needed for an 91 * unsigned integer overflow. 92 */ 93 next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff; 94 /* 95 * if ns_diff is INT64_MAX next may still be outside the range 96 * of a signed integer. 97 */ 98 next = MIN(next, INT64_MAX); 99 } 100 101 timer_mod(timer, next); 102 } 103 104 void riscv_timer_init(RISCVCPU *cpu) 105 { 106 CPURISCVState *env; 107 108 if (!cpu) { 109 return; 110 } 111 112 env = &cpu->env; 113 env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu); 114 env->stimecmp = 0; 115 116 env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu); 117 env->vstimecmp = 0; 118 } 119