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