target/riscv: implement Espressif RISC-V CPU type
Espressif RISC-V CPU has a different has way of treating interrupts, which is not standard. Thus, this class will override the default RISC-V CPU behavior to have 31 interrupt lines.
This commit is contained in:
committed by
Ivan Grokhotkov
parent
c592afedea
commit
85af55eeea
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Espressif RISC-V CPU
|
||||
*
|
||||
* Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
#include "qemu/osdep.h"
|
||||
#include "qemu/log.h"
|
||||
#include "qemu/timer.h"
|
||||
#include "qemu/error-report.h"
|
||||
#include "qapi/error.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "hw/irq.h"
|
||||
#include "hw/qdev-properties.h"
|
||||
#include "sysemu/reset.h"
|
||||
#include "esp_cpu.h"
|
||||
|
||||
|
||||
/* CSR-related */
|
||||
#define ESP_CPU_CSR_PCER_M 0x7E0
|
||||
#define ESP_CPU_CSR_PCMR_M 0x7E1
|
||||
#define ESP_CPU_CSR_MCYCLE_M 0x7E2
|
||||
|
||||
/* The RISC-V core in QEMU doesn't support the triggers used in ESP32-C3
|
||||
* tcontrol is not supported either. So let's override all the debug registers
|
||||
*/
|
||||
#define ESP_CPU_CSR_TSELECT 0x7A0
|
||||
#define ESP_CPU_CSR_TDATA1 0x7A1
|
||||
#define ESP_CPU_CSR_TDATA2 0x7A2
|
||||
#define ESP_CPU_CSR_TDATA3 0x7A3
|
||||
#define ESP_CPU_CSR_TCONTROL 0x7A5
|
||||
|
||||
|
||||
#define ESP_CPU_CSR_PCER_U 0x800
|
||||
#define ESP_CPU_CSR_PCMR_U 0x801
|
||||
#define ESP_CPU_CSR_MCYCLE_U 0x802
|
||||
|
||||
#define ESP_CPU_CSR_GPIO_OEN 0x803
|
||||
#define ESP_CPU_CSR_GPIO_IN 0x804
|
||||
#define ESP_CPU_CSR_GPIO_OUT 0x805
|
||||
|
||||
|
||||
static RISCVException esp_cpu_csr_predicate(CPURISCVState *env, int csrno) {
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
|
||||
static uint64_t esp_cpu_get_cycles(ESPCPUCycleCounter* cc)
|
||||
{
|
||||
/* Let's simulate the cycle count between two reads of MCYCLE thanks to the time API. */
|
||||
/* Calculate the time elapsed between now and the previous call */
|
||||
uint64_t now = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL);
|
||||
uint64_t diff = 0;
|
||||
|
||||
/* If we are not in the first call, calculate the difference */
|
||||
if (cc->former_time != 0) {
|
||||
/* Let's say that we have 1 instruction/clock cycle, so 1 instruction/6.25ns */
|
||||
assert(cc->divider != 0);
|
||||
diff = (now - cc->former_time) / cc->divider;
|
||||
}
|
||||
cc->former_time = now;
|
||||
cc->cycles += diff;
|
||||
return cc->cycles;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Convert the given environment to the an ESP CPU.
|
||||
* The environment is the field part of RISCVCPU, so retrieve the RISCVCPU address.
|
||||
* In fact, RISCVCPU is overriden as EspRISCVCPU, we can then cast it safely.
|
||||
*/
|
||||
static EspRISCVCPU* esp_cpu_riscv_to_cpu(CPURISCVState *env)
|
||||
{
|
||||
// RISCVCPU* riscv = (RISCVCPU*) ((void*) env - offsetof(RISCVCPU, env));
|
||||
RISCVCPU* riscv = container_of(env, RISCVCPU, env);
|
||||
return ESP_CPU(riscv);
|
||||
}
|
||||
|
||||
|
||||
static RISCVException esp_cpu_csr_read(CPURISCVState *env, int csrno, target_ulong *ret_value) {
|
||||
EspRISCVCPU *s = esp_cpu_riscv_to_cpu(env);
|
||||
|
||||
if (csrno == ESP_CPU_CSR_MCYCLE_U) {
|
||||
*ret_value = esp_cpu_get_cycles(&s->cc_user);
|
||||
} else if (csrno == ESP_CPU_CSR_MCYCLE_M) {
|
||||
*ret_value = esp_cpu_get_cycles(&s->cc_machine);
|
||||
} else if (csrno >= ESP_CPU_CSR_TSELECT && csrno <= ESP_CPU_CSR_TCONTROL) {
|
||||
/* Nothing special to do here */
|
||||
} else {
|
||||
*ret_value = 0;
|
||||
}
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
|
||||
static RISCVException esp_cpu_csr_write(CPURISCVState *env, int csrno, target_ulong new_value) {
|
||||
EspRISCVCPU *s = esp_cpu_riscv_to_cpu(env);
|
||||
|
||||
if (csrno == ESP_CPU_CSR_MCYCLE_U) {
|
||||
s->cc_user.cycles = new_value;
|
||||
} else if (csrno == ESP_CPU_CSR_MCYCLE_M) {
|
||||
s->cc_machine.cycles = new_value;
|
||||
} else if (csrno >= ESP_CPU_CSR_TSELECT && csrno <= ESP_CPU_CSR_TCONTROL) {
|
||||
/* Nothing special to do here */
|
||||
}
|
||||
|
||||
return RISCV_EXCP_NONE;
|
||||
}
|
||||
|
||||
|
||||
static RISCVException esp_cpu_write_mstatus(CPURISCVState *env, int csrno, target_ulong val) {
|
||||
EspRISCVCPU *s = esp_cpu_riscv_to_cpu(env);
|
||||
EspRISCVCPUClass *klass = ESP_CPU_GET_CLASS(s);
|
||||
|
||||
const int previous_mie = env->mstatus & MSTATUS_MIE;
|
||||
|
||||
RISCVException excp = klass->parent_mstatus_write(env, csrno, val);
|
||||
|
||||
const int new_mie = env->mstatus & MSTATUS_MIE;
|
||||
|
||||
/* Check if the MIE bit of MSTATUS has just been enabled by the application.
|
||||
* If that's the case, the interrupts are enabled again, notify the interrupt matrix. */
|
||||
if (new_mie && !previous_mie && s->mie_enabled_callback) {
|
||||
s->mie_enabled_callback(s->mie_enabled_opaque);
|
||||
}
|
||||
|
||||
return excp;
|
||||
}
|
||||
|
||||
|
||||
static void esp_cpu_register_mie_callback(EspRISCVCPU *env, EspRISCVCallback callback, void* opaque)
|
||||
{
|
||||
assert(env != NULL);
|
||||
env->mie_enabled_callback = callback;
|
||||
env->mie_enabled_opaque = opaque;
|
||||
}
|
||||
|
||||
riscv_csr_operations esp_cpu_csr_ops = {
|
||||
.predicate = esp_cpu_csr_predicate,
|
||||
.read = esp_cpu_csr_read,
|
||||
.write = esp_cpu_csr_write
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Checks whether the CPU can accepts interrupts or not
|
||||
*/
|
||||
bool esp_cpu_accept_interrupts(EspRISCVCPU *cpu)
|
||||
{
|
||||
/* Get the MIE bit out of the MSTATUS register */
|
||||
CPURISCVState *env = &cpu->parent_obj.env;
|
||||
const bool mie = (riscv_csr_read(env, CSR_MSTATUS) & MSTATUS_MIE) != 0;
|
||||
|
||||
return !cpu->irq_pending && mie;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Function called when an interrupt is incoming.
|
||||
*/
|
||||
static void esp_cpu_irq_handler(void *opaque, int n, int level)
|
||||
{
|
||||
EspRISCVCPU *cpu = (EspRISCVCPU*) opaque;
|
||||
|
||||
/* Interrupt incoming if level is not 0, make sure we can receive interrupts */
|
||||
if (level && esp_cpu_accept_interrupts(cpu)) {
|
||||
cpu->irq_pending = true;
|
||||
cpu->irq_cause = n;
|
||||
qemu_irq_raise(cpu->parent_irq);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* TCG operation called when the CPU has to actually jump to the interrupt handler.
|
||||
*/
|
||||
static bool esp_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
|
||||
{
|
||||
/* We could re-implement the whole interrupt process from here.
|
||||
* The simplest solution however is to call the parent's implementation and
|
||||
* replace the most important part for us: the mcause. */
|
||||
EspRISCVCPU *cpu = ESP_CPU(cs);
|
||||
EspRISCVCPUClass *klass = ESP_CPU_GET_CLASS(cpu);
|
||||
|
||||
const bool accepted = klass->parent_exec_interrupt(cs, interrupt_request);
|
||||
|
||||
if (accepted) {
|
||||
CPURISCVState *env = &cpu->parent_obj.env;
|
||||
const bool vectored = (env->mtvec & 3) == 1;
|
||||
const uint32_t cause = cpu->irq_cause;
|
||||
|
||||
/* IRQ has been acknowledged by the parent CPU, it is not pending anymore */
|
||||
cpu->irq_pending = false;
|
||||
qemu_irq_lower(cpu->parent_irq);
|
||||
|
||||
/* Update the mcause and the relevant PC */
|
||||
env->mcause = RISCV_EXCP_INT_FLAG | cause;
|
||||
|
||||
/* Recalculate the PC thanks to the cause */
|
||||
env->pc = (env->mtvec >> 2 << 2) + (vectored ? cause * 4 : 0);
|
||||
}
|
||||
|
||||
return accepted;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Taken from `cpu.c`, as this function is private in that file
|
||||
*/
|
||||
static void set_misa(CPURISCVState *env, RISCVMXL mxl, uint32_t ext)
|
||||
{
|
||||
env->misa_ext_mask = env->misa_ext = ext;
|
||||
}
|
||||
|
||||
static void esp_cpu_reset(void *opaque)
|
||||
{
|
||||
EspRISCVCPU *cpu = opaque;
|
||||
cpu->irq_pending = 0;
|
||||
qemu_irq_lower(cpu->parent_irq);
|
||||
cpu_reset(CPU(cpu));
|
||||
}
|
||||
|
||||
static void esp_cpu_realize(DeviceState *dev, Error **errp)
|
||||
{
|
||||
EspRISCVCPU *espcpu = ESP_CPU(dev);
|
||||
EspRISCVCPUClass *klass = ESP_CPU_GET_CLASS(dev);
|
||||
|
||||
espcpu->parent_obj.env.mhartid = espcpu->hartid_base;
|
||||
qemu_register_reset(esp_cpu_reset, espcpu);
|
||||
|
||||
klass->parent_realize(dev, errp);
|
||||
|
||||
if (riscv_cpu_claim_interrupts(&espcpu->parent_obj, MIP_MEIP) < 0) {
|
||||
error_report("MIP_MEIP already claimed");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
static struct TCGCPUOps tcg_ops = { 0 };
|
||||
|
||||
static void esp_cpu_override_tcg_interrupts(Object *obj)
|
||||
{
|
||||
EspRISCVCPUClass *klass = ESP_CPU_GET_CLASS(obj);
|
||||
CPUClass *cc = CPU_CLASS(klass);
|
||||
EspRISCVCPUClass *cpuclass = ESP_CPU_CLASS(klass);
|
||||
|
||||
/* The goal of this RISC-V CPU child class is to override the way interrupts are handled.
|
||||
* In theory, it would be enough to override `do_interrupt` function from the CPU's TCGCPUOps
|
||||
* structure, however, in practice, we have to override `riscv_cpu_exec_interrupt` function.
|
||||
* This is due to the fact that the RISC-V implementation doesn't call the `do_interrupt` routine
|
||||
* from its TCGCPUOps routine, but directly calls its `riscv_cpu_do_interrupt` function.
|
||||
* As that structure may be constant, we have to copy it in order to replace one of its field. */
|
||||
memcpy(&tcg_ops, cc->tcg_ops, sizeof(struct TCGCPUOps));
|
||||
|
||||
/* Copy the parent's exec_interrupt function as we will execute it later */
|
||||
cpuclass->parent_exec_interrupt = tcg_ops.cpu_exec_interrupt;
|
||||
|
||||
/* Replace it with our overriden implementation */
|
||||
tcg_ops.cpu_exec_interrupt = esp_cpu_exec_interrupt;
|
||||
cc->tcg_ops = &tcg_ops;
|
||||
}
|
||||
|
||||
static void esp_cpu_init(Object *obj)
|
||||
{
|
||||
EspRISCVCPU *s = ESP_CPU(obj);
|
||||
RISCVCPU *cpu = RISCV_CPU(obj);
|
||||
CPURISCVState *env = &cpu->env;
|
||||
set_misa(env, MXL_RV32, RVI | RVM | RVC);
|
||||
/* Zawrs extension is enabled by default, but depends on "A" extension which isn't present on C3 */
|
||||
cpu->cfg.ext_zawrs = false;
|
||||
/* Zfa extension is enabled by default, but depends on "F" extension which isn't present on C3 */
|
||||
cpu->cfg.ext_zfa = false;
|
||||
|
||||
/* Since the TCG operations are now separated from the standard RISC-V CPU, we have to override
|
||||
* the TCG operations in this init function instead of the class init */
|
||||
esp_cpu_override_tcg_interrupts(obj);
|
||||
|
||||
/* Initialize the IRQ lines */
|
||||
qdev_init_gpio_in_named_with_opaque(DEVICE(s),
|
||||
esp_cpu_irq_handler, s,
|
||||
ESP_CPU_IRQ_LINES_NAME, ESP_CPU_INT_LINES + 1);
|
||||
|
||||
/* Initialize the parent IRQ line that will be used to notify the parent class when an interrupt
|
||||
* request is incoming. */
|
||||
s->parent_irq = qdev_get_gpio_in(DEVICE(s), IRQ_M_EXT);
|
||||
|
||||
/* Set the user operations */
|
||||
riscv_set_csr_ops(CSR_USTATUS, &esp_cpu_csr_ops);
|
||||
|
||||
/* Override debug CSRs as they are not all supported by QEMU's RISC-V core */
|
||||
for (int i = ESP_CPU_CSR_TSELECT; i <= ESP_CPU_CSR_TCONTROL; i++) {
|
||||
riscv_set_csr_ops(i, &esp_cpu_csr_ops);
|
||||
}
|
||||
|
||||
/* Register all non-standard Control and Status registers */
|
||||
riscv_set_csr_ops(ESP_CPU_CSR_PCER_M, &esp_cpu_csr_ops);
|
||||
riscv_set_csr_ops(ESP_CPU_CSR_PCMR_M, &esp_cpu_csr_ops);
|
||||
riscv_set_csr_ops(ESP_CPU_CSR_MCYCLE_M, &esp_cpu_csr_ops);
|
||||
|
||||
riscv_set_csr_ops(ESP_CPU_CSR_PCER_U, &esp_cpu_csr_ops);
|
||||
riscv_set_csr_ops(ESP_CPU_CSR_PCMR_U, &esp_cpu_csr_ops);
|
||||
riscv_set_csr_ops(ESP_CPU_CSR_MCYCLE_U, &esp_cpu_csr_ops);
|
||||
|
||||
s->cc_machine = (ESPCPUCycleCounter) {
|
||||
.divider = 6, /* 6.25ns per instruction at 160MHz. */
|
||||
};
|
||||
s->cc_user = (ESPCPUCycleCounter) {
|
||||
.divider = 6, /* Should be using the target configured CPU clock frequency instead. */
|
||||
};
|
||||
}
|
||||
|
||||
static Property riscv_harts_props[] = {
|
||||
DEFINE_PROP_UINT32("hartid-base", EspRISCVCPU, hartid_base, 0),
|
||||
DEFINE_PROP_END_OF_LIST(),
|
||||
};
|
||||
|
||||
|
||||
static void esp_cpu_class_init(ObjectClass *klass, void *data)
|
||||
{
|
||||
DeviceClass *dc = DEVICE_CLASS(klass);
|
||||
EspRISCVCPUClass *cpuclass = ESP_CPU_CLASS(klass);
|
||||
|
||||
device_class_set_props(dc, riscv_harts_props);
|
||||
/* Save the parent realize function in order to be able to call it later */
|
||||
device_class_set_parent_realize(dc, esp_cpu_realize,
|
||||
&cpuclass->parent_realize);
|
||||
|
||||
/* Function to register MIE callback */
|
||||
cpuclass->esp_cpu_register_mie_callback = esp_cpu_register_mie_callback;
|
||||
|
||||
/* Override the CSR write for mstatus */
|
||||
riscv_csr_operations ops;
|
||||
riscv_get_csr_ops(CSR_MSTATUS, &ops);
|
||||
cpuclass->parent_mstatus_write = ops.write;
|
||||
ops.write = esp_cpu_write_mstatus;
|
||||
riscv_set_csr_ops(CSR_MSTATUS, &ops);
|
||||
}
|
||||
|
||||
static const TypeInfo esp_cpu_info = {
|
||||
.name = TYPE_ESP_RISCV_CPU,
|
||||
.parent = TYPE_RISCV_CPU_BASE32,
|
||||
.instance_size = sizeof(EspRISCVCPU),
|
||||
.instance_align = __alignof__(EspRISCVCPU),
|
||||
.instance_init = esp_cpu_init,
|
||||
.class_size = sizeof(EspRISCVCPUClass),
|
||||
.class_init = esp_cpu_class_init,
|
||||
};
|
||||
|
||||
static void esp_cpu_register_type(void)
|
||||
{
|
||||
type_register_static(&esp_cpu_info);
|
||||
}
|
||||
|
||||
type_init(esp_cpu_register_type)
|
||||
@@ -0,0 +1,96 @@
|
||||
/*
|
||||
* Espressif RISC-V CPU
|
||||
*
|
||||
* Copyright (c) 2023 Espressif Systems (Shanghai) Co. Ltd.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 or
|
||||
* (at your option) any later version.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "hw/sysbus.h"
|
||||
#include "hw/hw.h"
|
||||
#include "hw/registerfields.h"
|
||||
#include "cpu.h"
|
||||
|
||||
/* Make sure we are not in CONFIG_USER_ONLY */
|
||||
#if defined(CONFIG_USER_ONLY) || !defined(CONFIG_SOFTMMU)
|
||||
#error "ESP RISC-V Core only works in system emulation and in SOFTMMU configuration"
|
||||
#endif
|
||||
|
||||
#include "hw/core/tcg-cpu-ops.h"
|
||||
|
||||
#define ESP_CPU_IRQ_LINES_NAME "espressif-cpu-irq-lines"
|
||||
|
||||
#define TYPE_ESP_RISCV_CPU "espressif-riscv-cpu"
|
||||
#define ESP_CPU(obj) OBJECT_CHECK(EspRISCVCPU, (obj), TYPE_ESP_RISCV_CPU)
|
||||
#define ESP_CPU_GET_CLASS(obj) OBJECT_GET_CLASS(EspRISCVCPUClass, obj, TYPE_ESP_RISCV_CPU)
|
||||
#define ESP_CPU_CLASS(klass) OBJECT_CLASS_CHECK(EspRISCVCPUClass, klass, TYPE_ESP_RISCV_CPU)
|
||||
|
||||
#define ESP_CPU_INT_LINES 31
|
||||
|
||||
|
||||
/* Define a type that will be used to generate a cycle counter */
|
||||
typedef struct {
|
||||
uint64_t former_time;
|
||||
uint64_t cycles;
|
||||
/* The number of nanosecond an instruction takes to execute */
|
||||
uint64_t divider;
|
||||
} ESPCPUCycleCounter;
|
||||
|
||||
/**
|
||||
* @brief Callback type called when MIE status bit is re-enabled
|
||||
*/
|
||||
typedef void (*EspRISCVCallback)(void*);
|
||||
|
||||
/**
|
||||
* Espressif's RISC-V core is different from standard RISC-V because of the way interrupts are handled.
|
||||
* Extend the standard RISC-V core implementation.
|
||||
*/
|
||||
typedef struct EspRISCVCPU {
|
||||
/*< private >*/
|
||||
RISCVCPU parent_obj;
|
||||
|
||||
/* Cycle counts */
|
||||
ESPCPUCycleCounter cc_user;
|
||||
ESPCPUCycleCounter cc_machine;
|
||||
|
||||
/* Callback called when the MIE bit is re-enabled in `mstatus` CSR */
|
||||
EspRISCVCallback mie_enabled_callback;
|
||||
void* mie_enabled_opaque;
|
||||
|
||||
/*< public >*/
|
||||
/* The parent object already has a reset vector property */
|
||||
uint32_t hartid_base;
|
||||
/* Parent IRQ_M line */
|
||||
qemu_irq parent_irq;
|
||||
/* Number of the IRQ that triggered the interrupt */
|
||||
uint32_t irq_cause;
|
||||
/* Interrupts are not always synchronous, so MIE may still be set to 1 while an
|
||||
* interrupt is waiting to be handled. So, keep a mirrored MIE to mark whether
|
||||
* we can receive interrupts or not. */
|
||||
bool irq_pending;
|
||||
} EspRISCVCPU;
|
||||
|
||||
|
||||
typedef struct EspRISCVCPUClass {
|
||||
/*< private >*/
|
||||
RISCVCPUClass parent_class;
|
||||
DeviceRealize parent_realize;
|
||||
DeviceReset parent_reset;
|
||||
bool (*parent_exec_interrupt)(CPUState *cpu, int interrupt_request);
|
||||
RISCVException (*parent_mstatus_write)(CPURISCVState *env, int csrno, target_ulong val);
|
||||
|
||||
/*< public >*/
|
||||
void (*esp_cpu_register_mie_callback)(EspRISCVCPU *env, EspRISCVCallback callback, void* opaque);
|
||||
} EspRISCVCPUClass;
|
||||
|
||||
/**
|
||||
* @brief Check whether the current CPU state can accept interrupts or not.
|
||||
* If an interrupt is currently pending and the MEPC was not set to the reset vector yet,
|
||||
* this function returns false.
|
||||
* If MIE bit is not set in the MSTATUS register, it will also return false.
|
||||
*/
|
||||
bool esp_cpu_accept_interrupts(EspRISCVCPU *cpu);
|
||||
@@ -24,6 +24,7 @@ riscv_ss.add(files(
|
||||
'zce_helper.c',
|
||||
'vcrypto_helper.c'
|
||||
))
|
||||
riscv_ss.add(when: 'CONFIG_RISCV_ESP32C3', if_true: files('esp_cpu.c'))
|
||||
|
||||
riscv_system_ss = ss.source_set()
|
||||
riscv_system_ss.add(files(
|
||||
|
||||
Reference in New Issue
Block a user