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 /* 76 * Sstc specification says the following about timer interrupt: 77 * "A supervisor timer interrupt becomes pending - as reflected in 78 * the STIP bit in the mip and sip registers - whenever time contains 79 * a value greater than or equal to stimecmp, treating the values 80 * as unsigned integers. Writes to stimecmp are guaranteed to be 81 * reflected in STIP eventually, but not necessarily immediately. 82 * The interrupt remains posted until stimecmp becomes greater 83 * than time - typically as a result of writing stimecmp." 84 * 85 * When timecmp = UINT64_MAX, the time CSR will eventually reach 86 * timecmp value but on next timer tick the time CSR will wrap-around 87 * and become zero which is less than UINT64_MAX. Now, the timer 88 * interrupt behaves like a level triggered interrupt so it will 89 * become 1 when time = timecmp = UINT64_MAX and next timer tick 90 * it will become 0 again because time = 0 < timecmp = UINT64_MAX. 91 * 92 * Based on above, we don't re-start the QEMU timer when timecmp 93 * equals UINT64_MAX. 94 */ 95 if (timecmp == UINT64_MAX) { 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