qemu/target/riscv/time_helper.c
Jim Shu dff5f51540 target/riscv: Enable/Disable S/VS-mode Timer when STCE bit is changed
Updating STCE will enable/disable SSTC in S-mode or/and VS-mode, so we
also need to update S/VS-mode Timer and S/VSTIP bits in $mip CSR.

Signed-off-by: Jim Shu <jim.shu@sifive.com>
Acked-by: Alistair Francis <alistair.francis@wdc.com>
Message-ID: <20250519143518.11086-5-jim.shu@sifive.com>
Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
2025-07-04 21:09:48 +10:00

203 lines
6.7 KiB
C

/*
* RISC-V timer helper implementation.
*
* Copyright (c) 2022 Rivos Inc.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2 or later, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "qemu/osdep.h"
#include "qemu/log.h"
#include "cpu_bits.h"
#include "time_helper.h"
#include "hw/intc/riscv_aclint.h"
static void riscv_vstimer_cb(void *opaque)
{
RISCVCPU *cpu = opaque;
CPURISCVState *env = &cpu->env;
env->vstime_irq = 1;
riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
}
static void riscv_stimer_cb(void *opaque)
{
RISCVCPU *cpu = opaque;
riscv_cpu_update_mip(&cpu->env, MIP_STIP, BOOL_TO_MASK(1));
}
/*
* Called when timecmp is written to update the QEMU timer or immediately
* trigger timer interrupt if mtimecmp <= current timer value.
*/
void riscv_timer_write_timecmp(CPURISCVState *env, QEMUTimer *timer,
uint64_t timecmp, uint64_t delta,
uint32_t timer_irq)
{
uint64_t diff, ns_diff, next;
RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
uint32_t timebase_freq;
uint64_t rtc_r;
if (!riscv_cpu_cfg(env)->ext_sstc || !env->rdtime_fn ||
!env->rdtime_fn_arg || !get_field(env->menvcfg, MENVCFG_STCE)) {
/* S/VS Timer IRQ depends on sstc extension, rdtime_fn(), and STCE. */
return;
}
if (timer_irq == MIP_VSTIP &&
(!riscv_has_ext(env, RVH) || !get_field(env->henvcfg, HENVCFG_STCE))) {
/* VS Timer IRQ also depends on RVH and henvcfg.STCE. */
return;
}
timebase_freq = mtimer->timebase_freq;
rtc_r = env->rdtime_fn(env->rdtime_fn_arg) + delta;
if (timecmp <= rtc_r) {
/*
* If we're setting an stimecmp value in the "past",
* immediately raise the timer interrupt
*/
if (timer_irq == MIP_VSTIP) {
env->vstime_irq = 1;
riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(1));
} else {
riscv_cpu_update_mip(env, MIP_STIP, BOOL_TO_MASK(1));
}
return;
}
/* Clear the [VS|S]TIP bit in mip */
if (timer_irq == MIP_VSTIP) {
env->vstime_irq = 0;
riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(0));
} else {
riscv_cpu_update_mip(env, timer_irq, BOOL_TO_MASK(0));
}
/*
* Sstc specification says the following about timer interrupt:
* "A supervisor timer interrupt becomes pending - as reflected in
* the STIP bit in the mip and sip registers - whenever time contains
* a value greater than or equal to stimecmp, treating the values
* as unsigned integers. Writes to stimecmp are guaranteed to be
* reflected in STIP eventually, but not necessarily immediately.
* The interrupt remains posted until stimecmp becomes greater
* than time - typically as a result of writing stimecmp."
*
* When timecmp = UINT64_MAX, the time CSR will eventually reach
* timecmp value but on next timer tick the time CSR will wrap-around
* and become zero which is less than UINT64_MAX. Now, the timer
* interrupt behaves like a level triggered interrupt so it will
* become 1 when time = timecmp = UINT64_MAX and next timer tick
* it will become 0 again because time = 0 < timecmp = UINT64_MAX.
*
* Based on above, we don't re-start the QEMU timer when timecmp
* equals UINT64_MAX.
*/
if (timecmp == UINT64_MAX) {
timer_del(timer);
return;
}
/* otherwise, set up the future timer interrupt */
diff = timecmp - rtc_r;
/* back to ns (note args switched in muldiv64) */
ns_diff = muldiv64(diff, NANOSECONDS_PER_SECOND, timebase_freq);
/*
* check if ns_diff overflowed and check if the addition would potentially
* overflow
*/
if ((NANOSECONDS_PER_SECOND > timebase_freq && ns_diff < diff) ||
ns_diff > INT64_MAX) {
next = INT64_MAX;
} else {
/*
* as it is very unlikely qemu_clock_get_ns will return a value
* greater than INT64_MAX, no additional check is needed for an
* unsigned integer overflow.
*/
next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + ns_diff;
/*
* if ns_diff is INT64_MAX next may still be outside the range
* of a signed integer.
*/
next = MIN(next, INT64_MAX);
}
timer_mod(timer, next);
}
/*
* When disabling xenvcfg.STCE, the S/VS Timer may be disabled at the same time.
* It is safe to call this function regardless of whether the timer has been
* deleted or not. timer_del() will do nothing if the timer has already
* been deleted.
*/
static void riscv_timer_disable_timecmp(CPURISCVState *env, QEMUTimer *timer,
uint32_t timer_irq)
{
/* Disable S-mode Timer IRQ and HW-based STIP */
if ((timer_irq == MIP_STIP) && !get_field(env->menvcfg, MENVCFG_STCE)) {
riscv_cpu_update_mip(env, timer_irq, BOOL_TO_MASK(0));
timer_del(timer);
return;
}
/* Disable VS-mode Timer IRQ and HW-based VSTIP */
if ((timer_irq == MIP_VSTIP) &&
(!get_field(env->menvcfg, MENVCFG_STCE) ||
!get_field(env->henvcfg, HENVCFG_STCE))) {
env->vstime_irq = 0;
riscv_cpu_update_mip(env, 0, BOOL_TO_MASK(0));
timer_del(timer);
return;
}
}
/* Enable or disable S/VS-mode Timer when xenvcfg.STCE is changed */
void riscv_timer_stce_changed(CPURISCVState *env, bool is_m_mode, bool enable)
{
if (enable) {
riscv_timer_write_timecmp(env, env->vstimer, env->vstimecmp,
env->htimedelta, MIP_VSTIP);
} else {
riscv_timer_disable_timecmp(env, env->vstimer, MIP_VSTIP);
}
if (is_m_mode) {
if (enable) {
riscv_timer_write_timecmp(env, env->stimer, env->stimecmp, 0, MIP_STIP);
} else {
riscv_timer_disable_timecmp(env, env->stimer, MIP_STIP);
}
}
}
void riscv_timer_init(RISCVCPU *cpu)
{
CPURISCVState *env;
if (!cpu) {
return;
}
env = &cpu->env;
env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_stimer_cb, cpu);
env->stimecmp = 0;
env->vstimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, &riscv_vstimer_cb, cpu);
env->vstimecmp = 0;
}