qemu/hw/intc/riscv_aclint.c
Mayuresh Chitale 64452a09c5
hw: intc: Use cpu_by_arch_id to fetch CPU state
Qemu_get_cpu uses the logical CPU id assigned during init to fetch the
CPU state. However APLIC, IMSIC and ACLINT contain registers and states
which are specific to physical hart Ids. The hart Ids in any given system
might be sparse and hence calls to qemu_get_cpu need to be replaced by
cpu_by_arch_id which performs lookup based on the sparse physical hart IDs.

Signed-off-by: Mayuresh Chitale <mchitale@ventanamicro.com>
Signed-off-by: Anup Patel <apatel@ventanamicro.com>
Reviewed-by: Daniel Henrique Barboza <dbarboza@ventanamicro.com>
Message-ID: <20230303065055.915652-3-mchitale@ventanamicro.com>
Signed-off-by: Palmer Dabbelt <palmer@rivosinc.com>
2023-03-05 15:33:40 -08:00

566 lines
19 KiB
C

/*
* RISC-V ACLINT (Advanced Core Local Interruptor)
* URL: https://github.com/riscv/riscv-aclint
*
* Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu
* Copyright (c) 2017 SiFive, Inc.
* Copyright (c) 2021 Western Digital Corporation or its affiliates.
*
* This provides real-time clock, timer and interprocessor interrupts.
*
* 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 "qapi/error.h"
#include "qemu/error-report.h"
#include "qemu/log.h"
#include "qemu/module.h"
#include "hw/sysbus.h"
#include "target/riscv/cpu.h"
#include "hw/qdev-properties.h"
#include "hw/intc/riscv_aclint.h"
#include "qemu/timer.h"
#include "hw/irq.h"
#include "migration/vmstate.h"
typedef struct riscv_aclint_mtimer_callback {
RISCVAclintMTimerState *s;
int num;
} riscv_aclint_mtimer_callback;
static uint64_t cpu_riscv_read_rtc_raw(uint32_t timebase_freq)
{
return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL),
timebase_freq, NANOSECONDS_PER_SECOND);
}
static uint64_t cpu_riscv_read_rtc(void *opaque)
{
RISCVAclintMTimerState *mtimer = opaque;
return cpu_riscv_read_rtc_raw(mtimer->timebase_freq) + mtimer->time_delta;
}
/*
* Called when timecmp is written to update the QEMU timer or immediately
* trigger timer interrupt if mtimecmp <= current timer value.
*/
static void riscv_aclint_mtimer_write_timecmp(RISCVAclintMTimerState *mtimer,
RISCVCPU *cpu,
int hartid,
uint64_t value)
{
uint32_t timebase_freq = mtimer->timebase_freq;
uint64_t next;
uint64_t diff;
uint64_t rtc_r = cpu_riscv_read_rtc(mtimer);
/* Compute the relative hartid w.r.t the socket */
hartid = hartid - mtimer->hartid_base;
mtimer->timecmp[hartid] = value;
if (mtimer->timecmp[hartid] <= rtc_r) {
/*
* If we're setting an MTIMECMP value in the "past",
* immediately raise the timer interrupt
*/
qemu_irq_raise(mtimer->timer_irqs[hartid]);
return;
}
/* otherwise, set up the future timer interrupt */
qemu_irq_lower(mtimer->timer_irqs[hartid]);
diff = mtimer->timecmp[hartid] - rtc_r;
/* back to ns (note args switched in muldiv64) */
uint64_t 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(mtimer->timers[hartid], next);
}
/*
* Callback used when the timer set using timer_mod expires.
* Should raise the timer interrupt line
*/
static void riscv_aclint_mtimer_cb(void *opaque)
{
riscv_aclint_mtimer_callback *state = opaque;
qemu_irq_raise(state->s->timer_irqs[state->num]);
}
/* CPU read MTIMER register */
static uint64_t riscv_aclint_mtimer_read(void *opaque, hwaddr addr,
unsigned size)
{
RISCVAclintMTimerState *mtimer = opaque;
if (addr >= mtimer->timecmp_base &&
addr < (mtimer->timecmp_base + (mtimer->num_harts << 3))) {
size_t hartid = mtimer->hartid_base +
((addr - mtimer->timecmp_base) >> 3);
CPUState *cpu = cpu_by_arch_id(hartid);
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
if (!env) {
qemu_log_mask(LOG_GUEST_ERROR,
"aclint-mtimer: invalid hartid: %zu", hartid);
} else if ((addr & 0x7) == 0) {
/* timecmp_lo for RV32/RV64 or timecmp for RV64 */
uint64_t timecmp = mtimer->timecmp[hartid];
return (size == 4) ? (timecmp & 0xFFFFFFFF) : timecmp;
} else if ((addr & 0x7) == 4) {
/* timecmp_hi */
uint64_t timecmp = mtimer->timecmp[hartid];
return (timecmp >> 32) & 0xFFFFFFFF;
} else {
qemu_log_mask(LOG_UNIMP,
"aclint-mtimer: invalid read: %08x", (uint32_t)addr);
return 0;
}
} else if (addr == mtimer->time_base) {
/* time_lo for RV32/RV64 or timecmp for RV64 */
uint64_t rtc = cpu_riscv_read_rtc(mtimer);
return (size == 4) ? (rtc & 0xFFFFFFFF) : rtc;
} else if (addr == mtimer->time_base + 4) {
/* time_hi */
return (cpu_riscv_read_rtc(mtimer) >> 32) & 0xFFFFFFFF;
}
qemu_log_mask(LOG_UNIMP,
"aclint-mtimer: invalid read: %08x", (uint32_t)addr);
return 0;
}
/* CPU write MTIMER register */
static void riscv_aclint_mtimer_write(void *opaque, hwaddr addr,
uint64_t value, unsigned size)
{
RISCVAclintMTimerState *mtimer = opaque;
int i;
if (addr >= mtimer->timecmp_base &&
addr < (mtimer->timecmp_base + (mtimer->num_harts << 3))) {
size_t hartid = mtimer->hartid_base +
((addr - mtimer->timecmp_base) >> 3);
CPUState *cpu = cpu_by_arch_id(hartid);
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
if (!env) {
qemu_log_mask(LOG_GUEST_ERROR,
"aclint-mtimer: invalid hartid: %zu", hartid);
} else if ((addr & 0x7) == 0) {
if (size == 4) {
/* timecmp_lo for RV32/RV64 */
uint64_t timecmp_hi = mtimer->timecmp[hartid] >> 32;
riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid,
timecmp_hi << 32 | (value & 0xFFFFFFFF));
} else {
/* timecmp for RV64 */
riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid,
value);
}
} else if ((addr & 0x7) == 4) {
if (size == 4) {
/* timecmp_hi for RV32/RV64 */
uint64_t timecmp_lo = mtimer->timecmp[hartid];
riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu), hartid,
value << 32 | (timecmp_lo & 0xFFFFFFFF));
} else {
qemu_log_mask(LOG_GUEST_ERROR,
"aclint-mtimer: invalid timecmp_hi write: %08x",
(uint32_t)addr);
}
} else {
qemu_log_mask(LOG_UNIMP,
"aclint-mtimer: invalid timecmp write: %08x",
(uint32_t)addr);
}
return;
} else if (addr == mtimer->time_base || addr == mtimer->time_base + 4) {
uint64_t rtc_r = cpu_riscv_read_rtc_raw(mtimer->timebase_freq);
if (addr == mtimer->time_base) {
if (size == 4) {
/* time_lo for RV32/RV64 */
mtimer->time_delta = ((rtc_r & ~0xFFFFFFFFULL) | value) - rtc_r;
} else {
/* time for RV64 */
mtimer->time_delta = value - rtc_r;
}
} else {
if (size == 4) {
/* time_hi for RV32/RV64 */
mtimer->time_delta = (value << 32 | (rtc_r & 0xFFFFFFFF)) - rtc_r;
} else {
qemu_log_mask(LOG_GUEST_ERROR,
"aclint-mtimer: invalid time_hi write: %08x",
(uint32_t)addr);
return;
}
}
/* Check if timer interrupt is triggered for each hart. */
for (i = 0; i < mtimer->num_harts; i++) {
CPUState *cpu = cpu_by_arch_id(mtimer->hartid_base + i);
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
if (!env) {
continue;
}
riscv_aclint_mtimer_write_timecmp(mtimer, RISCV_CPU(cpu),
mtimer->hartid_base + i,
mtimer->timecmp[i]);
}
return;
}
qemu_log_mask(LOG_UNIMP,
"aclint-mtimer: invalid write: %08x", (uint32_t)addr);
}
static const MemoryRegionOps riscv_aclint_mtimer_ops = {
.read = riscv_aclint_mtimer_read,
.write = riscv_aclint_mtimer_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 8
},
.impl = {
.min_access_size = 4,
.max_access_size = 8,
}
};
static Property riscv_aclint_mtimer_properties[] = {
DEFINE_PROP_UINT32("hartid-base", RISCVAclintMTimerState,
hartid_base, 0),
DEFINE_PROP_UINT32("num-harts", RISCVAclintMTimerState, num_harts, 1),
DEFINE_PROP_UINT32("timecmp-base", RISCVAclintMTimerState,
timecmp_base, RISCV_ACLINT_DEFAULT_MTIMECMP),
DEFINE_PROP_UINT32("time-base", RISCVAclintMTimerState,
time_base, RISCV_ACLINT_DEFAULT_MTIME),
DEFINE_PROP_UINT32("aperture-size", RISCVAclintMTimerState,
aperture_size, RISCV_ACLINT_DEFAULT_MTIMER_SIZE),
DEFINE_PROP_UINT32("timebase-freq", RISCVAclintMTimerState,
timebase_freq, 0),
DEFINE_PROP_END_OF_LIST(),
};
static void riscv_aclint_mtimer_realize(DeviceState *dev, Error **errp)
{
RISCVAclintMTimerState *s = RISCV_ACLINT_MTIMER(dev);
int i;
memory_region_init_io(&s->mmio, OBJECT(dev), &riscv_aclint_mtimer_ops,
s, TYPE_RISCV_ACLINT_MTIMER, s->aperture_size);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &s->mmio);
s->timer_irqs = g_new(qemu_irq, s->num_harts);
qdev_init_gpio_out(dev, s->timer_irqs, s->num_harts);
s->timers = g_new0(QEMUTimer *, s->num_harts);
s->timecmp = g_new0(uint64_t, s->num_harts);
/* Claim timer interrupt bits */
for (i = 0; i < s->num_harts; i++) {
RISCVCPU *cpu = RISCV_CPU(cpu_by_arch_id(s->hartid_base + i));
if (riscv_cpu_claim_interrupts(cpu, MIP_MTIP) < 0) {
error_report("MTIP already claimed");
exit(1);
}
}
}
static void riscv_aclint_mtimer_reset_enter(Object *obj, ResetType type)
{
/*
* According to RISC-V ACLINT spec:
* - On MTIMER device reset, the MTIME register is cleared to zero.
* - On MTIMER device reset, the MTIMECMP registers are in unknown state.
*/
RISCVAclintMTimerState *mtimer = RISCV_ACLINT_MTIMER(obj);
/*
* Clear mtime register by writing to 0 it.
* Pending mtime interrupts will also be cleared at the same time.
*/
riscv_aclint_mtimer_write(mtimer, mtimer->time_base, 0, 8);
}
static const VMStateDescription vmstate_riscv_mtimer = {
.name = "riscv_mtimer",
.version_id = 1,
.minimum_version_id = 1,
.fields = (VMStateField[]) {
VMSTATE_VARRAY_UINT32(timecmp, RISCVAclintMTimerState,
num_harts, 0,
vmstate_info_uint64, uint64_t),
VMSTATE_END_OF_LIST()
}
};
static void riscv_aclint_mtimer_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = riscv_aclint_mtimer_realize;
device_class_set_props(dc, riscv_aclint_mtimer_properties);
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.enter = riscv_aclint_mtimer_reset_enter;
dc->vmsd = &vmstate_riscv_mtimer;
}
static const TypeInfo riscv_aclint_mtimer_info = {
.name = TYPE_RISCV_ACLINT_MTIMER,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(RISCVAclintMTimerState),
.class_init = riscv_aclint_mtimer_class_init,
};
/*
* Create ACLINT MTIMER device.
*/
DeviceState *riscv_aclint_mtimer_create(hwaddr addr, hwaddr size,
uint32_t hartid_base, uint32_t num_harts,
uint32_t timecmp_base, uint32_t time_base, uint32_t timebase_freq,
bool provide_rdtime)
{
int i;
DeviceState *dev = qdev_new(TYPE_RISCV_ACLINT_MTIMER);
RISCVAclintMTimerState *s = RISCV_ACLINT_MTIMER(dev);
assert(num_harts <= RISCV_ACLINT_MAX_HARTS);
assert(!(addr & 0x7));
assert(!(timecmp_base & 0x7));
assert(!(time_base & 0x7));
qdev_prop_set_uint32(dev, "hartid-base", hartid_base);
qdev_prop_set_uint32(dev, "num-harts", num_harts);
qdev_prop_set_uint32(dev, "timecmp-base", timecmp_base);
qdev_prop_set_uint32(dev, "time-base", time_base);
qdev_prop_set_uint32(dev, "aperture-size", size);
qdev_prop_set_uint32(dev, "timebase-freq", timebase_freq);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
for (i = 0; i < num_harts; i++) {
CPUState *cpu = cpu_by_arch_id(hartid_base + i);
RISCVCPU *rvcpu = RISCV_CPU(cpu);
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
riscv_aclint_mtimer_callback *cb =
g_new0(riscv_aclint_mtimer_callback, 1);
if (!env) {
g_free(cb);
continue;
}
if (provide_rdtime) {
riscv_cpu_set_rdtime_fn(env, cpu_riscv_read_rtc, dev);
}
cb->s = s;
cb->num = i;
s->timers[i] = timer_new_ns(QEMU_CLOCK_VIRTUAL,
&riscv_aclint_mtimer_cb, cb);
s->timecmp[i] = 0;
qdev_connect_gpio_out(dev, i,
qdev_get_gpio_in(DEVICE(rvcpu), IRQ_M_TIMER));
}
return dev;
}
/* CPU read [M|S]SWI register */
static uint64_t riscv_aclint_swi_read(void *opaque, hwaddr addr,
unsigned size)
{
RISCVAclintSwiState *swi = opaque;
if (addr < (swi->num_harts << 2)) {
size_t hartid = swi->hartid_base + (addr >> 2);
CPUState *cpu = cpu_by_arch_id(hartid);
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
if (!env) {
qemu_log_mask(LOG_GUEST_ERROR,
"aclint-swi: invalid hartid: %zu", hartid);
} else if ((addr & 0x3) == 0) {
return (swi->sswi) ? 0 : ((env->mip & MIP_MSIP) > 0);
}
}
qemu_log_mask(LOG_UNIMP,
"aclint-swi: invalid read: %08x", (uint32_t)addr);
return 0;
}
/* CPU write [M|S]SWI register */
static void riscv_aclint_swi_write(void *opaque, hwaddr addr, uint64_t value,
unsigned size)
{
RISCVAclintSwiState *swi = opaque;
if (addr < (swi->num_harts << 2)) {
size_t hartid = swi->hartid_base + (addr >> 2);
CPUState *cpu = cpu_by_arch_id(hartid);
CPURISCVState *env = cpu ? cpu->env_ptr : NULL;
if (!env) {
qemu_log_mask(LOG_GUEST_ERROR,
"aclint-swi: invalid hartid: %zu", hartid);
} else if ((addr & 0x3) == 0) {
if (value & 0x1) {
qemu_irq_raise(swi->soft_irqs[hartid - swi->hartid_base]);
} else {
if (!swi->sswi) {
qemu_irq_lower(swi->soft_irqs[hartid - swi->hartid_base]);
}
}
return;
}
}
qemu_log_mask(LOG_UNIMP,
"aclint-swi: invalid write: %08x", (uint32_t)addr);
}
static const MemoryRegionOps riscv_aclint_swi_ops = {
.read = riscv_aclint_swi_read,
.write = riscv_aclint_swi_write,
.endianness = DEVICE_LITTLE_ENDIAN,
.valid = {
.min_access_size = 4,
.max_access_size = 4
}
};
static Property riscv_aclint_swi_properties[] = {
DEFINE_PROP_UINT32("hartid-base", RISCVAclintSwiState, hartid_base, 0),
DEFINE_PROP_UINT32("num-harts", RISCVAclintSwiState, num_harts, 1),
DEFINE_PROP_UINT32("sswi", RISCVAclintSwiState, sswi, false),
DEFINE_PROP_END_OF_LIST(),
};
static void riscv_aclint_swi_realize(DeviceState *dev, Error **errp)
{
RISCVAclintSwiState *swi = RISCV_ACLINT_SWI(dev);
int i;
memory_region_init_io(&swi->mmio, OBJECT(dev), &riscv_aclint_swi_ops, swi,
TYPE_RISCV_ACLINT_SWI, RISCV_ACLINT_SWI_SIZE);
sysbus_init_mmio(SYS_BUS_DEVICE(dev), &swi->mmio);
swi->soft_irqs = g_new(qemu_irq, swi->num_harts);
qdev_init_gpio_out(dev, swi->soft_irqs, swi->num_harts);
/* Claim software interrupt bits */
for (i = 0; i < swi->num_harts; i++) {
RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(swi->hartid_base + i));
/* We don't claim mip.SSIP because it is writable by software */
if (riscv_cpu_claim_interrupts(cpu, swi->sswi ? 0 : MIP_MSIP) < 0) {
error_report("MSIP already claimed");
exit(1);
}
}
}
static void riscv_aclint_swi_reset_enter(Object *obj, ResetType type)
{
/*
* According to RISC-V ACLINT spec:
* - On MSWI device reset, each MSIP register is cleared to zero.
*
* p.s. SSWI device reset does nothing since SETSIP register always reads 0.
*/
RISCVAclintSwiState *swi = RISCV_ACLINT_SWI(obj);
int i;
if (!swi->sswi) {
for (i = 0; i < swi->num_harts; i++) {
/* Clear MSIP registers by lowering software interrupts. */
qemu_irq_lower(swi->soft_irqs[i]);
}
}
}
static void riscv_aclint_swi_class_init(ObjectClass *klass, void *data)
{
DeviceClass *dc = DEVICE_CLASS(klass);
dc->realize = riscv_aclint_swi_realize;
device_class_set_props(dc, riscv_aclint_swi_properties);
ResettableClass *rc = RESETTABLE_CLASS(klass);
rc->phases.enter = riscv_aclint_swi_reset_enter;
}
static const TypeInfo riscv_aclint_swi_info = {
.name = TYPE_RISCV_ACLINT_SWI,
.parent = TYPE_SYS_BUS_DEVICE,
.instance_size = sizeof(RISCVAclintSwiState),
.class_init = riscv_aclint_swi_class_init,
};
/*
* Create ACLINT [M|S]SWI device.
*/
DeviceState *riscv_aclint_swi_create(hwaddr addr, uint32_t hartid_base,
uint32_t num_harts, bool sswi)
{
int i;
DeviceState *dev = qdev_new(TYPE_RISCV_ACLINT_SWI);
assert(num_harts <= RISCV_ACLINT_MAX_HARTS);
assert(!(addr & 0x3));
qdev_prop_set_uint32(dev, "hartid-base", hartid_base);
qdev_prop_set_uint32(dev, "num-harts", num_harts);
qdev_prop_set_uint32(dev, "sswi", sswi ? true : false);
sysbus_realize_and_unref(SYS_BUS_DEVICE(dev), &error_fatal);
sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr);
for (i = 0; i < num_harts; i++) {
CPUState *cpu = cpu_by_arch_id(hartid_base + i);
RISCVCPU *rvcpu = RISCV_CPU(cpu);
qdev_connect_gpio_out(dev, i,
qdev_get_gpio_in(DEVICE(rvcpu),
(sswi) ? IRQ_S_SOFT : IRQ_M_SOFT));
}
return dev;
}
static void riscv_aclint_register_types(void)
{
type_register_static(&riscv_aclint_mtimer_info);
type_register_static(&riscv_aclint_swi_info);
}
type_init(riscv_aclint_register_types)