target/riscv: Add stimecmp support
stimecmp allows the supervisor mode to update stimecmp CSR directly to program the next timer interrupt. This CSR is part of the Sstc extension which was ratified recently. Reviewed-by: Alistair Francis <alistair.francis@wdc.com> Signed-off-by: Atish Patra <atishp@rivosinc.com> Message-Id: <20220824221357.41070-3-atishp@rivosinc.com> Signed-off-by: Alistair Francis <alistair.francis@wdc.com>
This commit is contained in:
parent
7cbcc538f4
commit
43888c2f18
@ -23,6 +23,7 @@
|
||||
#include "qemu/log.h"
|
||||
#include "cpu.h"
|
||||
#include "internals.h"
|
||||
#include "time_helper.h"
|
||||
#include "exec/exec-all.h"
|
||||
#include "qapi/error.h"
|
||||
#include "qemu/error-report.h"
|
||||
@ -101,6 +102,7 @@ static const struct isa_ext_data isa_edata_arr[] = {
|
||||
ISA_EXT_DATA_ENTRY(zhinxmin, true, PRIV_VERSION_1_12_0, ext_zhinxmin),
|
||||
ISA_EXT_DATA_ENTRY(smaia, true, PRIV_VERSION_1_12_0, ext_smaia),
|
||||
ISA_EXT_DATA_ENTRY(ssaia, true, PRIV_VERSION_1_12_0, ext_ssaia),
|
||||
ISA_EXT_DATA_ENTRY(sstc, true, PRIV_VERSION_1_12_0, ext_sstc),
|
||||
ISA_EXT_DATA_ENTRY(svinval, true, PRIV_VERSION_1_12_0, ext_svinval),
|
||||
ISA_EXT_DATA_ENTRY(svnapot, true, PRIV_VERSION_1_12_0, ext_svnapot),
|
||||
ISA_EXT_DATA_ENTRY(svpbmt, true, PRIV_VERSION_1_12_0, ext_svpbmt),
|
||||
@ -674,6 +676,12 @@ static void riscv_cpu_realize(DeviceState *dev, Error **errp)
|
||||
|
||||
set_resetvec(env, cpu->cfg.resetvec);
|
||||
|
||||
#ifndef CONFIG_USER_ONLY
|
||||
if (cpu->cfg.ext_sstc) {
|
||||
riscv_timer_init(cpu);
|
||||
}
|
||||
#endif /* CONFIG_USER_ONLY */
|
||||
|
||||
/* Validate that MISA_MXL is set properly. */
|
||||
switch (env->misa_mxl_max) {
|
||||
#ifdef TARGET_RISCV64
|
||||
@ -994,6 +1002,7 @@ static Property riscv_cpu_extensions[] = {
|
||||
DEFINE_PROP_BOOL("Zve64f", RISCVCPU, cfg.ext_zve64f, false),
|
||||
DEFINE_PROP_BOOL("mmu", RISCVCPU, cfg.mmu, true),
|
||||
DEFINE_PROP_BOOL("pmp", RISCVCPU, cfg.pmp, true),
|
||||
DEFINE_PROP_BOOL("sstc", RISCVCPU, cfg.ext_sstc, true),
|
||||
|
||||
DEFINE_PROP_STRING("priv_spec", RISCVCPU, cfg.priv_spec),
|
||||
DEFINE_PROP_STRING("vext_spec", RISCVCPU, cfg.vext_spec),
|
||||
|
@ -308,6 +308,9 @@ struct CPUArchState {
|
||||
uint64_t mfromhost;
|
||||
uint64_t mtohost;
|
||||
|
||||
/* Sstc CSRs */
|
||||
uint64_t stimecmp;
|
||||
|
||||
/* physical memory protection */
|
||||
pmp_table_t pmp_state;
|
||||
target_ulong mseccfg;
|
||||
@ -361,6 +364,7 @@ struct CPUArchState {
|
||||
float_status fp_status;
|
||||
|
||||
/* Fields from here on are preserved across CPU reset. */
|
||||
QEMUTimer *stimer; /* Internal timer for S-mode interrupt */
|
||||
|
||||
hwaddr kernel_addr;
|
||||
hwaddr fdt_addr;
|
||||
@ -424,6 +428,7 @@ struct RISCVCPUConfig {
|
||||
bool ext_ifencei;
|
||||
bool ext_icsr;
|
||||
bool ext_zihintpause;
|
||||
bool ext_sstc;
|
||||
bool ext_svinval;
|
||||
bool ext_svnapot;
|
||||
bool ext_svpbmt;
|
||||
|
@ -206,6 +206,10 @@
|
||||
#define CSR_STVAL 0x143
|
||||
#define CSR_SIP 0x144
|
||||
|
||||
/* Sstc supervisor CSRs */
|
||||
#define CSR_STIMECMP 0x14D
|
||||
#define CSR_STIMECMPH 0x15D
|
||||
|
||||
/* Supervisor Protection and Translation */
|
||||
#define CSR_SPTBR 0x180
|
||||
#define CSR_SATP 0x180
|
||||
|
@ -22,6 +22,7 @@
|
||||
#include "qemu/timer.h"
|
||||
#include "cpu.h"
|
||||
#include "pmu.h"
|
||||
#include "time_helper.h"
|
||||
#include "qemu/main-loop.h"
|
||||
#include "exec/exec-all.h"
|
||||
#include "sysemu/cpu-timers.h"
|
||||
@ -815,6 +816,81 @@ static RISCVException read_timeh(CPURISCVState *env, int csrno,
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
static RISCVException sstc(CPURISCVState *env, int csrno)
|
||||
{
|
||||
CPUState *cs = env_cpu(env);
|
||||
RISCVCPU *cpu = RISCV_CPU(cs);
|
||||
|
||||
if (!cpu->cfg.ext_sstc || !env->rdtime_fn) {
|
||||
return RISCV_EXCP_ILLEGAL_INST;
|
||||
}
|
||||
|
||||
if (env->priv == PRV_M) {
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
/*
|
||||
* No need of separate function for rv32 as menvcfg stores both menvcfg
|
||||
* menvcfgh for RV32.
|
||||
*/
|
||||
if (!(get_field(env->mcounteren, COUNTEREN_TM) &&
|
||||
get_field(env->menvcfg, MENVCFG_STCE))) {
|
||||
return RISCV_EXCP_ILLEGAL_INST;
|
||||
}
|
||||
|
||||
return smode(env, csrno);
|
||||
}
|
||||
|
||||
static RISCVException sstc_32(CPURISCVState *env, int csrno)
|
||||
{
|
||||
if (riscv_cpu_mxl(env) != MXL_RV32) {
|
||||
return RISCV_EXCP_ILLEGAL_INST;
|
||||
}
|
||||
|
||||
return sstc(env, csrno);
|
||||
}
|
||||
|
||||
static RISCVException read_stimecmp(CPURISCVState *env, int csrno,
|
||||
target_ulong *val)
|
||||
{
|
||||
*val = env->stimecmp;
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
static RISCVException read_stimecmph(CPURISCVState *env, int csrno,
|
||||
target_ulong *val)
|
||||
{
|
||||
*val = env->stimecmp >> 32;
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
static RISCVException write_stimecmp(CPURISCVState *env, int csrno,
|
||||
target_ulong val)
|
||||
{
|
||||
RISCVCPU *cpu = env_archcpu(env);
|
||||
|
||||
if (riscv_cpu_mxl(env) == MXL_RV32) {
|
||||
env->stimecmp = deposit64(env->stimecmp, 0, 32, (uint64_t)val);
|
||||
} else {
|
||||
env->stimecmp = val;
|
||||
}
|
||||
|
||||
riscv_timer_write_timecmp(cpu, env->stimer, env->stimecmp, 0, MIP_STIP);
|
||||
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
static RISCVException write_stimecmph(CPURISCVState *env, int csrno,
|
||||
target_ulong val)
|
||||
{
|
||||
RISCVCPU *cpu = env_archcpu(env);
|
||||
|
||||
env->stimecmp = deposit64(env->stimecmp, 32, 32, (uint64_t)val);
|
||||
riscv_timer_write_timecmp(cpu, env->stimer, env->stimecmp, 0, MIP_STIP);
|
||||
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
/* Machine constants */
|
||||
|
||||
#define M_MODE_INTERRUPTS ((uint64_t)(MIP_MSIP | MIP_MTIP | MIP_MEIP))
|
||||
@ -1723,6 +1799,12 @@ static RISCVException rmw_mip64(CPURISCVState *env, int csrno,
|
||||
new_val |= env->external_seip * MIP_SEIP;
|
||||
}
|
||||
|
||||
if (cpu->cfg.ext_sstc && (env->priv == PRV_M) &&
|
||||
get_field(env->menvcfg, MENVCFG_STCE)) {
|
||||
/* sstc extension forbids STIP & VSTIP to be writeable in mip */
|
||||
mask = mask & ~(MIP_STIP | MIP_VSTIP);
|
||||
}
|
||||
|
||||
if (mask) {
|
||||
old_mip = riscv_cpu_update_mip(cpu, mask, (new_val & mask));
|
||||
} else {
|
||||
@ -3594,6 +3676,10 @@ riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = {
|
||||
[CSR_SCAUSE] = { "scause", smode, read_scause, write_scause },
|
||||
[CSR_STVAL] = { "stval", smode, read_stval, write_stval },
|
||||
[CSR_SIP] = { "sip", smode, NULL, NULL, rmw_sip },
|
||||
[CSR_STIMECMP] = { "stimecmp", sstc, read_stimecmp, write_stimecmp,
|
||||
.min_priv_ver = PRIV_VERSION_1_12_0 },
|
||||
[CSR_STIMECMPH] = { "stimecmph", sstc_32, read_stimecmph, write_stimecmph,
|
||||
.min_priv_ver = PRIV_VERSION_1_12_0 },
|
||||
|
||||
/* Supervisor Protection and Translation */
|
||||
[CSR_SATP] = { "satp", smode, read_satp, write_satp },
|
||||
|
@ -359,6 +359,7 @@ const VMStateDescription vmstate_riscv_cpu = {
|
||||
VMSTATE_UINTTL(env.mscratch, RISCVCPU),
|
||||
VMSTATE_UINT64(env.mfromhost, RISCVCPU),
|
||||
VMSTATE_UINT64(env.mtohost, RISCVCPU),
|
||||
VMSTATE_UINT64(env.stimecmp, RISCVCPU),
|
||||
|
||||
VMSTATE_END_OF_LIST()
|
||||
},
|
||||
|
@ -29,7 +29,8 @@ riscv_softmmu_ss.add(files(
|
||||
'debug.c',
|
||||
'monitor.c',
|
||||
'machine.c',
|
||||
'pmu.c'
|
||||
'pmu.c',
|
||||
'time_helper.c'
|
||||
))
|
||||
|
||||
target_arch += {'riscv': riscv_ss}
|
||||
|
98
target/riscv/time_helper.c
Normal file
98
target/riscv/time_helper.c
Normal file
@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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_stimer_cb(void *opaque)
|
||||
{
|
||||
RISCVCPU *cpu = opaque;
|
||||
riscv_cpu_update_mip(cpu, 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(RISCVCPU *cpu, QEMUTimer *timer,
|
||||
uint64_t timecmp, uint64_t delta,
|
||||
uint32_t timer_irq)
|
||||
{
|
||||
uint64_t diff, ns_diff, next;
|
||||
CPURISCVState *env = &cpu->env;
|
||||
RISCVAclintMTimerState *mtimer = env->rdtime_fn_arg;
|
||||
uint32_t timebase_freq = mtimer->timebase_freq;
|
||||
uint64_t 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
|
||||
*/
|
||||
riscv_cpu_update_mip(cpu, timer_irq, BOOL_TO_MASK(1));
|
||||
return;
|
||||
}
|
||||
|
||||
/* Clear the [V]STIP bit in mip */
|
||||
riscv_cpu_update_mip(cpu, timer_irq, BOOL_TO_MASK(0));
|
||||
|
||||
/* 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);
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
}
|
30
target/riscv/time_helper.h
Normal file
30
target/riscv/time_helper.h
Normal file
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* RISC-V timer header file.
|
||||
*
|
||||
* 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/>.
|
||||
*/
|
||||
|
||||
#ifndef RISCV_TIME_HELPER_H
|
||||
#define RISCV_TIME_HELPER_H
|
||||
|
||||
#include "cpu.h"
|
||||
#include "qemu/timer.h"
|
||||
|
||||
void riscv_timer_write_timecmp(RISCVCPU *cpu, QEMUTimer *timer,
|
||||
uint64_t timecmp, uint64_t delta,
|
||||
uint32_t timer_irq);
|
||||
void riscv_timer_init(RISCVCPU *cpu);
|
||||
|
||||
#endif
|
Loading…
Reference in New Issue
Block a user