From 85af55eeea550f86eaeeecd260da2d2f865eeb35 Mon Sep 17 00:00:00 2001 From: Omar Chebib Date: Fri, 12 May 2023 10:25:50 +0800 Subject: [PATCH] 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. --- target/riscv/esp_cpu.c | 359 +++++++++++++++++++++++++++++++++++++++ target/riscv/esp_cpu.h | 96 +++++++++++ target/riscv/meson.build | 1 + 3 files changed, 456 insertions(+) create mode 100644 target/riscv/esp_cpu.c create mode 100644 target/riscv/esp_cpu.h diff --git a/target/riscv/esp_cpu.c b/target/riscv/esp_cpu.c new file mode 100644 index 0000000000..52bb6f5691 --- /dev/null +++ b/target/riscv/esp_cpu.c @@ -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) diff --git a/target/riscv/esp_cpu.h b/target/riscv/esp_cpu.h new file mode 100644 index 0000000000..c44fbe0cf7 --- /dev/null +++ b/target/riscv/esp_cpu.h @@ -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); diff --git a/target/riscv/meson.build b/target/riscv/meson.build index a4bd61e52a..432151298e 100644 --- a/target/riscv/meson.build +++ b/target/riscv/meson.build @@ -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(