From 7da9309a490c5397cf8937c08e830f04fdc03e07 Mon Sep 17 00:00:00 2001 From: Michael Clark Date: Sat, 23 Jun 2018 13:06:13 -0700 Subject: [PATCH] SiFive CLIC (Core Level Interrupt Controller) test-beta1 - Implements draft clic-spec (20180728) - Implements non-vectored mode and vectored mode - Implements mode+level+priority configuration - Implements mode+level+priority preemption model - Seperated M-mode (mtvec) and S-mode (stvec) delivery - CLIC supports backwards compatible CLINT mode for legacy interrupts using MIE/MIP,SIE/SIP (irq < 16) depending on mtvec (MTI,MSI) and stvec (STI,SSI) - CLINT mode supports S-mode stimecmp{h} and ssip{h} - QEMU CLINT/CLIC Test Cases - https://github.com/michaeljclark/qemu-riscv-tests - Adds two experimental machines - SiFive Freedom E-Series with CLIC - Implements M-mode CLINT/CLIC config memory map - Parameters - CLICINTBITS=4 - CLICCFGMBITS=0 - CLICCFGLBITS=4 - Invocation - qemu-system-riscv{32,64} -machine sifive_ex - SiFive Freedom U-Series with CLIC - Implements M-mode and S-mode CLINT/CLIC memory map - Parameters - CLICINTBITS=8 - CLICCFGMBITS=2 - CLICCFGLBITS=4 - Invocation - qemu-system-riscv{32,64} -machine sifive_ux - CLIC combined CLINT/CLIC memory map - M-Mode CLINT = 0x02000000 - msip = 0x02000000 + hartid * 4 - mtimecmp = 0x02004000 + hartid * 4 - mtime = 0x0200bff8 - S-Mode CLINT = 0x02020000 - M-mode CLIC = 0x02080000 - clicintip = 0x02080000 + hartid * 0x1000 + 0x000 - clicintie = 0x02080000 + hartid * 0x1000 + 0x400 - clicintcfg = 0x02080000 + hartid * 0x1000 + 0x800 - cliccfg = 0x02080000 + hartid * 0x1000 + 0xc00 - S-Mode CLIC = 0x020c0000 - clicintip = 0x020c0000 + hartid * 0x1000 + 0x000 - clicintie = 0x020c0000 + hartid * 0x1000 + 0x400 - clicintcfg = 0x020c0000 + hartid * 0x1000 + 0x800 - Adds CLIC interrupt tracing (`-d trace:riscv_trap,...`) - riscv_trap # existing core interrupt tracing - sifive_clic_cfg # CLIC global configuration - sifive_clic_intcfg # CLIC interrupt configuration - sifive_clic_intie # CliC interrupt enable - sifive_clic_intip # CLIC interrupt pending - sifive_clic_irq # CLIC irq entry - Notes / Limitations - Enforces clicintcfg writes based on cliccfg and mode - Reads/writes to intcfg/intie/intip in lower mode MMIO apetures are currently allowed. Access checks need to be added to suppress writes and hardwire read reults to zero for any entries that where mode < clicintcfg.mode - Interrupts pending bits are writable by software. Edge/Level configuration needs to be added to control software access to interrupt pending bits - Selective vectoring in non-vectored mode is unimplemented - PLIC is currently not routed via the CLIC however pending bits can be written by software to test pre-emption. - mnxti/snxti sets mstatus flags but returns 0 (slow path). The CLIC state is currenetly not accessible from target/riscv as cpu implementations can't include anything from include/hw so the CLIC state needs to be in a CPU accessible structure. - Potential race condition if an interrupt is posted before the CPU has received and processed an outstanding interrupt due to env->exccode being overwritten. Needs changes to the interface from the CLIC so that the CPU interrupt handler pulls the highest priority interrupt from the CLIC at the time it is woken up. This requires the CLIC state to be accessible from the CPU similarly to mnxti - CPU core changes are relatively intrusive. The CPU interrupt handling requires some abstraction/hooks for a more modular CLIC implementation. CLIC state needs to be attached to the CPU, and accessible to the MMIO device with hooks in riscv_cpu_exec_interrupt and riscv_cpu_do_interrupt Changes since v0 - Fix array index calculation in sifive_clic_realize - Raise CLIC_LEVEL_BITS to 8 and fix assertion - Move CLIC parameterization constants to its header --- Makefile.objs | 1 + hw/riscv/Makefile.objs | 1 + hw/riscv/sifive_clic.c | 824 +++++++++++++++++++++++++++++++++ hw/riscv/sifive_clint.c | 112 +++-- hw/riscv/sifive_e.c | 54 ++- hw/riscv/sifive_plic.c | 23 +- hw/riscv/sifive_u.c | 52 ++- hw/riscv/trace-events | 6 + include/hw/riscv/sifive_clic.h | 127 +++++ include/hw/riscv/sifive_e.h | 9 + include/hw/riscv/sifive_u.h | 9 + target/riscv/cpu.c | 3 +- target/riscv/cpu.h | 19 +- target/riscv/cpu_bits.h | 34 +- target/riscv/cpu_helper.c | 132 +++++- target/riscv/csr.c | 183 +++++++- target/riscv/op_helper.c | 12 + target/riscv/trace-events | 2 +- 18 files changed, 1507 insertions(+), 96 deletions(-) create mode 100644 hw/riscv/sifive_clic.c create mode 100644 hw/riscv/trace-events create mode 100644 include/hw/riscv/sifive_clic.h diff --git a/Makefile.objs b/Makefile.objs index adbba37d65c..f2b38e379ad 100644 --- a/Makefile.objs +++ b/Makefile.objs @@ -228,6 +228,7 @@ trace-events-subdirs += hw/nvram trace-events-subdirs += hw/pci trace-events-subdirs += hw/pci-host trace-events-subdirs += hw/ppc +trace-events-subdirs += hw/riscv trace-events-subdirs += hw/rdma trace-events-subdirs += hw/rdma/vmw trace-events-subdirs += hw/s390x diff --git a/hw/riscv/Makefile.objs b/hw/riscv/Makefile.objs index d36b004ab0f..d903933dc74 100644 --- a/hw/riscv/Makefile.objs +++ b/hw/riscv/Makefile.objs @@ -2,6 +2,7 @@ obj-y += boot.o obj-y += riscv_htif.o obj-y += riscv_hart.o obj-y += sifive_e.o +obj-y += sifive_clic.o obj-y += sifive_clint.o obj-y += sifive_prci.o obj-y += sifive_plic.o diff --git a/hw/riscv/sifive_clic.c b/hw/riscv/sifive_clic.c new file mode 100644 index 00000000000..f7638b237b0 --- /dev/null +++ b/hw/riscv/sifive_clic.c @@ -0,0 +1,824 @@ +/* + * SiFive CLIC (Core Local Interrupt Controller) + * + * Copyright (c) 2016-2017 Sagar Karandikar, sagark@eecs.berkeley.edu + * Copyright (c) 2017-2018 SiFive, Inc. + * + * This provides real-time clock, timer, interprocessor interrupts + * and pre-emptable local 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 . + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "hw/sysbus.h" +#include "target/riscv/cpu.h" +#include "sysemu/sysemu.h" +#include "hw/riscv/sifive_clic.h" +#include "hw/riscv/sifive_clint.h" +#include "qemu/timer.h" +#include "trace.h" + +static const bool debug = false; + +static void intcfg_decode(SiFiveCLICState *clic, int hartid, int intcfg, + int *mode, int *level, int *priority) +{ + int nmbits = clic->nmbits[hartid]; + int nlbits = clic->nlbits[hartid]; + int npbits = clic->npbits[hartid]; + int nmshift = SIFIVE_CLIC_MAX_INT_BITS - nmbits; + int nlshift = SIFIVE_CLIC_MAX_INT_BITS - nmbits - nlbits; + int npshift = SIFIVE_CLIC_MAX_INT_BITS - nmbits - nlbits - npbits; + int decoded_mode = (intcfg >> nmshift) & ((1 << nmbits) - 1); + int decoded_level = (intcfg >> nlshift) & ((1 << nlbits) - 1); + int decoded_priority = (intcfg >> npshift) & ((1 << npbits) - 1); + + switch (nmbits) { + case 0: /* if nmbits == 0; then mode is PRV_M */ + *mode = PRV_M; + break; + case 1: /* if nmbits == 1; then mode = intcfg[8] ? PRV_M : PRV_U */ + *mode = decoded_mode ? PRV_M : PRV_U; + break; + case 2: /* if nmbits == 2, then mode = intcfg[8:7]*/ + *mode = decoded_mode; + break; + } + + /* unused level bits are set to 1 */ + *level = decoded_level << (SIFIVE_CLIC_MAX_LEVEL_BITS - nlbits) | + ((1 << (SIFIVE_CLIC_MAX_LEVEL_BITS - nlbits)) - 1); + + /* unused priority bits are set to 1 */ + *priority = decoded_priority << (SIFIVE_CLIC_MAX_PRIORITY_BITS - nlbits) | + ((1 << (SIFIVE_CLIC_MAX_PRIORITY_BITS - npbits)) - 1); +} + +static void sifive_clic_next_interrupt(SiFiveCLICState *clic, int hartid) +{ + /* + * Scan active list for highest priority pending interrupts + * comparing against this harts mintstatus register and interrupt + * the core if we have a higher priority interrupt to deliver + */ + RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(hartid)); + CPURISCVState *env = &cpu->env; + + int il[4] = { + get_field(env->mintstatus, MINTSTATUS_UIL), /* PRV_U */ + get_field(env->mintstatus, MINTSTATUS_SIL), /* PRV_S */ + 0, /* reserverd */ + get_field(env->mintstatus, MINTSTATUS_MIL) /* PRV_M */ + }; + + /* get sorted list of enabled interrupts for this hart */ + size_t hart_offset = hartid * clic->num_sources; + CLICActiveInterrupt *active = &clic->active_list[hart_offset]; + size_t active_count = clic->active_count[hartid]; + int mode = 0, level = 0, priority = 0; + + /* loop through the enabled interrupts sorted by mode+priority+level */ + while (active_count) { + intcfg_decode(clic, hartid, active->intcfg, &mode, &level, &priority); + if (mode < env->priv || (mode == env->priv && level <= il[mode])) { + /* no pending interrupts with high enough mode+priority+level + * break and clear pending interrupt for this hart*/ + break; + } + /* check pending interrupt with high enough mode+priority+level */ + if (clic->clicintip[hartid * clic->num_sources + active->irq]) { + /* post pending interrupt for this hart */ + riscv_cpu_clic_interrupt(cpu, active->irq | mode<<10 | level<<12); + return; + } + /* check next enabled interrupt */ + active_count--; + active++; + } + + /* clear pending interrupt for this hart */ + riscv_cpu_clic_interrupt(cpu, -1); +} + +static void sifive_clic_update_intip(SiFiveCLICState *clic, int mode, + int hartid, int irq, int new_intip); + +static void sifive_clic_clint_irq(RISCVCPU *cpu, int irq, int level) +{ + /* + * CLIC vs CLINT timer/software interrupt dispatch + * + * - check mtvec/stvec for CLINT mode vs CLIC mode + * - CLINT mode uses mip/mie + * - riscv_cpu_update_mip(cpu, irq, BOOL_TO_MASK(level)); + * - CLIC mode uses clicintie/clicintip + * - sifive_clic_update_intip(clic, mode, hartid, irq, level); + * - sifive_clic_next_interrupt(clic, hartid); + */ + + CPURISCVState *env = &cpu->env; + SiFiveCLICState *clic = env->clic; + uint32_t mmode_clic_mask = BOOL_TO_MASK((env->mtvec & 0b111110) == 0b10); + uint32_t smode_clic_mask = BOOL_TO_MASK((env->stvec & 0b111110) == 0b10); + uint32_t mmode_intrs = (MIP_MTIP | MIP_MSIP | MIP_MEIP); + uint32_t smode_intrs = (MIP_STIP | MIP_SSIP | MIP_SEIP); + uint32_t mmode_clic_intrs = mmode_intrs & mmode_clic_mask; + uint32_t smode_clic_intrs = smode_intrs & smode_clic_mask; + + if ((irq & mmode_clic_intrs) || (irq & smode_clic_intrs)) { + sifive_clic_update_intip(clic, PRV_M, env->mhartid, ctz32(irq), level); + } else { + riscv_cpu_update_mip(cpu, irq, BOOL_TO_MASK(level)); + } +} + +typedef QEMUTimer *(*timer_fn)(CPURISCVState *env); +typedef uint64_t *(*timecmp_fn)(CPURISCVState *env); + +static QEMUTimer *mtimer(CPURISCVState *env) { return env->mtimer; } +static QEMUTimer *stimer(CPURISCVState *env) { return env->stimer; } + +static uint64_t *mtimecmp(CPURISCVState *env) { return &env->mtimecmp; } +static uint64_t *stimecmp(CPURISCVState *env) { return &env->stimecmp; } + +static void sifive_clic_mtimecmp_cb(void *cpu) +{ + sifive_clic_clint_irq(cpu, MIP_MTIP, 1); +} + +static void sifive_clic_stimecmp_cb(void *cpu) +{ + sifive_clic_clint_irq(cpu, MIP_STIP, 1); +} + +static uint64_t cpu_riscv_read_rtc(void) +{ + return muldiv64(qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL), + SIFIVE_CLINT_TIMEBASE_FREQ, NANOSECONDS_PER_SECOND); +} + +static void sifive_clic_write_timecmp(RISCVCPU *cpu, uint64_t value, + timecmp_fn timecmp, timer_fn timer, + uint32_t timerirq) +{ + CPURISCVState *env = &cpu->env; + + uint64_t rtc = cpu_riscv_read_rtc(); + uint64_t cmp = *timecmp(env) = value; + uint64_t diff = cmp - rtc; + uint64_t next_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(diff, NANOSECONDS_PER_SECOND, SIFIVE_CLINT_TIMEBASE_FREQ); + + if (cmp <= rtc) { + /* if we're setting a timecmp value in the "past", + immediately raise the timer interrupt */ + sifive_clic_clint_irq(cpu, timerirq, 1); + } else { + /* otherwise, set up the future timer interrupt */ + sifive_clic_clint_irq(cpu, timerirq, 0); + timer_mod(timer(env), next_ns); + } +} + +static uint64_t sifive_clic_clint_read(SiFiveCLICState *clic, hwaddr addr, + unsigned size, timecmp_fn timecmp) +{ + /* reads must be 4 byte aligned words */ + if ((addr & 0x3) != 0 || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid read size %u: 0x%" HWADDR_PRIx "\n", size, addr); + return 0; + } + + if (addr >= clic->sip_base && + addr < clic->sip_base + (clic->num_harts << 2)) { + size_t hartid = (addr - clic->sip_base) >> 2; + CPUState *cpu = qemu_get_cpu(hartid); + CPURISCVState *env = cpu ? cpu->env_ptr : NULL; + if (!env) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid sip hartid: %zu\n", hartid); + } else if ((addr & 0x3) == 0) { + return (env->mip & MIP_MSIP) > 0; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid sip read: 0x%" HWADDR_PRIx "\n", addr); + return 0; + } + } else if (addr >= clic->timecmp_base && + addr < clic->timecmp_base + (clic->num_harts << 3)) { + size_t hartid = (addr - clic->timecmp_base) >> 3; + CPUState *cpu = qemu_get_cpu(hartid); + CPURISCVState *env = cpu ? cpu->env_ptr : NULL; + if (!env) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid timecmp hartid: %zu\n", hartid); + } else if ((addr & 0x7) == 0) { + /* timecmp_lo */ + return *timecmp(env) & 0xFFFFFFFF; + } else if ((addr & 0x7) == 4) { + /* timecmp_hi */ + return (*timecmp(env) >> 32) & 0xFFFFFFFF; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr); + return 0; + } + } else if (addr == clic->time_base) { + /* time_lo */ + return cpu_riscv_read_rtc() & 0xFFFFFFFF; + } else if (addr == clic->time_base + 4) { + /* time_hi */ + return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr); + } + + return 0; +} + +static void sifive_clic_clint_write(SiFiveCLICState *clic, hwaddr addr, + uint64_t value, unsigned size, + timecmp_fn timecmp, timer_fn timer, + uint32_t timerirq, uint32_t softirq) +{ + /* writes must be 4 byte aligned words */ + if ((addr & 0x3) != 0 || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid write size %u: 0x%" HWADDR_PRIx "\n", size, addr); + return; + } + + if (addr >= clic->sip_base && + addr < clic->sip_base + (clic->num_harts << 2)) { + size_t hartid = (addr - clic->sip_base) >> 2; + CPUState *cpu = qemu_get_cpu(hartid); + CPURISCVState *env = cpu ? cpu->env_ptr : NULL; + if (!env) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid sip hartid: %zu\n", hartid); + } else if ((addr & 0x3) == 0) { + sifive_clic_clint_irq(RISCV_CPU(cpu), softirq, !!value); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid sip write: 0x%" HWADDR_PRIx "\n", addr); + } + return; + } else if (addr >= clic->timecmp_base && + addr < clic->timecmp_base + (clic->num_harts << 3)) { + size_t hartid = (addr - clic->timecmp_base) >> 3; + CPUState *cpu = qemu_get_cpu(hartid); + CPURISCVState *env = cpu ? cpu->env_ptr : NULL; + if (!env) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid timecmp hartid: %zu\n", hartid); + } else if ((addr & 0x7) == 0) { + /* timecmp_lo */ + uint64_t timecmp_hi = *timecmp(env) >> 32; + sifive_clic_write_timecmp(RISCV_CPU(cpu), + timecmp_hi << 32 | (value & 0xFFFFFFFF), + timecmp, timer, timerirq); + return; + } else if ((addr & 0x7) == 4) { + /* timecmp_hi */ + uint64_t timecmp_lo = *timecmp(env); + sifive_clic_write_timecmp(RISCV_CPU(cpu), + value << 32 | (timecmp_lo & 0xFFFFFFFF), + timecmp, timer, timerirq); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid timecmp write: 0x%" HWADDR_PRIx "\n", addr); + } + return; + } else if (addr == clic->time_base) { + /* time_lo */ + qemu_log_mask(LOG_UNIMP, "clic: time_lo write not implemented\n"); + } else if (addr == clic->time_base + 4) { + /* time_hi */ + qemu_log_mask(LOG_UNIMP, "clic: time_hi write not implemented"); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr); + } +} + +static uint64_t sifive_clic_hart_read(SiFiveCLICState *clic, hwaddr addr, + unsigned size, int mode, int hartid) +{ + int req = extract32(addr, 10, 2); + int irq = extract32(addr, 0, 10); + size_t irq_offset = hartid * clic->num_sources + irq; + + if (hartid >= clic->num_harts) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n", hartid, addr); + return 0; + } + + if (irq >= clic->num_sources) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr); + return 0; + } + + switch (req) { + case 0: /* clicintip[i] */ + /* TODO - need to check mode has access to pending bit */ + return clic->clicintip[irq_offset]; + case 1: /* clicintie[i] */ + /* TODO - need to check mode has access to enable bit */ + return clic->clicintie[irq_offset]; + case 2: /* clicintcfg[i] */ + /* TODO - need to check mode has access to config bit */ + return clic->clicintcfg[irq_offset]; + case 3: /* cliccfg */ + if (irq == 0 && mode == PRV_M) { + return clic->nvbits[hartid] | + (clic->nlbits[hartid] << 1) | + (clic->nmbits[hartid] << 4); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid cliccfg read: 0x%" HWADDR_PRIx "\n", addr); + return 0; + } + break; + } + + return 0; +} + +static inline int sifive_clic_encode_priority(const CLICActiveInterrupt *i) +{ + return ((0xff - i->intcfg) << 12) |/* highest mode+level+priority */ + (((0x3ff - i->irq) & 0x3ff) << 0); /* highest irq number */ +} + +static int sifive_clic_active_compare(const void *a, const void *b) +{ + return sifive_clic_encode_priority(a) - sifive_clic_encode_priority(b); +} + +static void sifive_clic_print_active_irqs(SiFiveCLICState *clic) +{ + int hartid, i; + for (hartid = 0; hartid < clic->num_harts; hartid++) { + size_t hart_offset = hartid * clic->num_sources; + CLICActiveInterrupt *active_list = &clic->active_list[hart_offset]; + size_t *active_count = &clic->active_count[hartid]; + for (i = 0; i < *active_count; i++) { + CLICActiveInterrupt *active = active_list + i; + printf("hartid=%d intcfg=0x%02hhx, irq=%d\n", + hartid, active->intcfg, active->irq); + } + } +} + +static bool sifive_clic_validate_intip(SiFiveCLICState *clic, int mode, + int hartid, int irq, int new_intcfg) +{ + /* + * TODO - current implementation allows software control of pending bits + * TODO - need to check whether the mode has access to this pending bit + */ + + return true; +} + +static void sifive_clic_update_intip(SiFiveCLICState *clic, int mode, + int hartid, int irq, int new_intip) +{ + size_t irq_offset = hartid * clic->num_sources + irq; + clic->clicintip[irq_offset] = !!new_intip; + trace_sifive_clic_intip(mode, hartid, irq, new_intip); + sifive_clic_next_interrupt(clic, hartid); +} + +static bool sifive_clic_validate_intie(SiFiveCLICState *clic, int mode, + int hartid, int irq, int new_intcfg) +{ + /* + * TODO - need to check whether the mode has access to this enable bit + */ + + return true; +} + +static void sifive_clic_update_intie(SiFiveCLICState *clic, int mode, + int hartid, int irq, int new_intie) +{ + size_t hart_offset = hartid * clic->num_sources; + size_t irq_offset = hartid * clic->num_sources + irq; + CLICActiveInterrupt *active_list = &clic->active_list[hart_offset]; + size_t *active_count = &clic->active_count[hartid]; + + uint8_t old_intie = clic->clicintie[irq_offset]; + clic->clicintie[irq_offset] = !!new_intie; + + /* add to or remove from list of active interrupts */ + if (new_intie && !old_intie) { + active_list[*active_count].intcfg = clic->clicintcfg[irq_offset]; + active_list[*active_count].irq = irq; + (*active_count)++; + } else if (!new_intie && old_intie) { + CLICActiveInterrupt key = { + clic->clicintcfg[irq_offset], irq + }; + CLICActiveInterrupt *result = bsearch(&key, + active_list, *active_count, + sizeof(CLICActiveInterrupt), + sifive_clic_active_compare); + size_t elem = (result - active_list) / sizeof(CLICActiveInterrupt); + size_t sz = (--(*active_count) - elem) * sizeof(CLICActiveInterrupt); + assert(result); + memmove(&result[0], &result[1], sz); + } + + /* sort list of active interrupts */ + qsort(active_list, *active_count, + sizeof(CLICActiveInterrupt), + sifive_clic_active_compare); + + trace_sifive_clic_intie(mode, hartid, irq, new_intie); + sifive_clic_next_interrupt(clic, hartid); +} + +static bool sifive_clic_validate_intcfg(SiFiveCLICState *clic, int mode, + int hartid, int irq, int intcfg) +{ + int nmbits = clic->nmbits[hartid], nmshift = 8 - nmbits; + int decoded_mode = (intcfg >> nmshift) & ((1 << nmbits) - 1); + + switch (nmbits) { + case 0: /* if nmbits == 0; then PRV_M <= mode */ + return PRV_M <= mode; + case 1: /* if nmbits == 1; then intcfg[8] ? PRV_M : PRV_U <= mode */ + return (decoded_mode ? PRV_M : PRV_U) <= mode; + case 2: /* if nmbits == 2; then intcfg[8:7] <= mode */ + return decoded_mode <= mode; + } + return false; +} + +static void sifive_clic_update_intcfg(SiFiveCLICState *clic, int mode, + int hartid, int irq, int new_intcfg) +{ + size_t hart_offset = hartid * clic->num_sources; + size_t irq_offset = hartid * clic->num_sources + irq; + CLICActiveInterrupt *active_list = &clic->active_list[hart_offset]; + size_t *active_count = &clic->active_count[hartid]; + + new_intcfg &= (((1 << clic->int_bits) - 1) << (8 - clic->int_bits)); + + uint8_t old_intcfg = clic->clicintcfg[irq_offset]; + clic->clicintcfg[irq_offset] = new_intcfg; + + /* sort list of active interrupts based on new intcfg */ + if (clic->clicintie[irq_offset] && old_intcfg != new_intcfg) { + CLICActiveInterrupt key = { old_intcfg, irq }; + CLICActiveInterrupt *result = bsearch(&key, + active_list, *active_count, + sizeof(CLICActiveInterrupt), + sifive_clic_active_compare); + assert(result); + result->intcfg = new_intcfg; + qsort(active_list, *active_count, + sizeof(CLICActiveInterrupt), + sifive_clic_active_compare); + } + + trace_sifive_clic_intcfg(mode, hartid, irq, new_intcfg); + sifive_clic_next_interrupt(clic, hartid); +} + +static void sifive_clic_hart_write(SiFiveCLICState *clic, hwaddr addr, + uint64_t value, unsigned size, + int mode, int hartid) +{ + int req = extract32(addr, 10, 2); + int irq = extract32(addr, 0, 10); + + if (hartid >= clic->num_harts) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid hartid %u: 0x%" HWADDR_PRIx "\n", hartid, addr); + return; + } + + if (irq >= clic->num_sources) { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid irq %u: 0x%" HWADDR_PRIx "\n", irq, addr); + return; + } + + switch (req) { + case 0: /* clicintip[i] */ + if (sifive_clic_validate_intip(clic, mode, hartid, irq, value)) { + sifive_clic_update_intip(clic, mode, hartid, irq, value); + } + break; + case 1: /* clicintie[i] */ + if (sifive_clic_validate_intie(clic, mode, hartid, irq, value)) { + sifive_clic_update_intie(clic, mode, hartid, irq, value); + } + if (debug) { + sifive_clic_print_active_irqs(clic); + } + break; + case 2: /* clicintcfg[i] */ + if (sifive_clic_validate_intcfg(clic, mode, hartid, irq, value)) { + sifive_clic_update_intcfg(clic, mode, hartid, irq, value); + } + if (debug) { + sifive_clic_print_active_irqs(clic); + } + break; + case 3: /* cliccfg */ + if (irq == 0 && mode == PRV_M) { + clic->nvbits[hartid] = MIN(extract32(value, 0, 1), + clic->vec_bits); + clic->nlbits[hartid] = MIN(extract32(value, 1, 3), + clic->level_bits); + clic->nmbits[hartid] = MIN(extract32(value, 4, 2), + clic->mode_bits); + trace_sifive_clic_cfg(hartid, + clic->nmbits[hartid], + clic->nlbits[hartid], + clic->nvbits[hartid]); + clic->npbits[hartid] = clic->int_bits - + clic->nmbits[hartid] - + clic->nlbits[hartid]; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid cliccfg write: 0x%" HWADDR_PRIx "\n", addr); + return; + } + break; + } +} + +static uint64_t sifive_clic_read(void *opaque, hwaddr addr, unsigned size) +{ + SiFiveCLICState *clic = opaque; + hwaddr clic_size = clic->num_harts * SIFIVE_CLIC_HART_SIZE; + int hartid = 0; + + if (addr >= clic->clint_mmode_base && + addr < clic->clint_mmode_base + SIFIVE_CLIC_CLINT_SIZE) { + /* M-mode CLINT aperture */ + addr -= clic->clint_mmode_base; + return sifive_clic_clint_read(clic, addr, size, mtimecmp); + } else if (addr >= clic->clint_smode_base && + addr < clic->clint_smode_base + SIFIVE_CLIC_CLINT_SIZE) { + /* S-mode CLINT aperture */ + addr -= clic->clint_smode_base; + return sifive_clic_clint_read(clic, addr, size, stimecmp); + } else if (addr >= clic->clic_mmode_base && + addr < clic->clic_mmode_base + clic_size) { + /* M-mode CLIC per hart apertures */ + addr -= clic->clic_mmode_base; + hartid = addr / SIFIVE_CLIC_HART_SIZE; + addr -= hartid * SIFIVE_CLIC_HART_SIZE; + return sifive_clic_hart_read(clic, addr, size, PRV_M, hartid); + } else if (addr >= clic->clic_smode_base && + addr < clic->clic_smode_base + clic_size) { + /* S-mode CLIC per hart apertures */ + addr -= clic->clic_smode_base; + hartid = addr / SIFIVE_CLIC_HART_SIZE; + addr -= hartid * SIFIVE_CLIC_HART_SIZE; + return sifive_clic_hart_read(clic, addr, size, PRV_S, hartid); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid read: 0x%" HWADDR_PRIx "\n", addr); + } + return 0; +} + +static void sifive_clic_write(void *opaque, hwaddr addr, uint64_t value, + unsigned size) +{ + SiFiveCLICState *clic = opaque; + hwaddr clic_size = clic->num_harts * SIFIVE_CLIC_HART_SIZE; + int hartid = 0; + + if (addr >= clic->clint_mmode_base && + addr < clic->clint_mmode_base + SIFIVE_CLIC_CLINT_SIZE) { + /* M-mode CLINT aperture */ + addr -= clic->clint_mmode_base; + sifive_clic_clint_write(clic, addr, value, size, + mtimecmp, mtimer, MIP_MTIP, MIP_MSIP); + } else if (addr >= clic->clint_smode_base && + addr < clic->clint_smode_base + SIFIVE_CLIC_CLINT_SIZE) { + /* S-mode CLINT aperture */ + addr -= clic->clint_smode_base; + sifive_clic_clint_write(clic, addr, value, size, + stimecmp, stimer, MIP_STIP, MIP_SSIP); + } else if (addr >= clic->clic_mmode_base && + addr < clic->clic_mmode_base + clic_size) { + /* M-mode CLIC per hart apertures */ + addr -= clic->clic_mmode_base; + hartid = addr / SIFIVE_CLIC_HART_SIZE; + addr -= hartid * SIFIVE_CLIC_HART_SIZE; + sifive_clic_hart_write(clic, addr, value, size, PRV_M, hartid); + } else if (addr >= clic->clic_smode_base && + addr < clic->clic_smode_base + clic_size) { + /* S-mode CLIC per hart apertures */ + addr -= clic->clic_smode_base; + hartid = addr / SIFIVE_CLIC_HART_SIZE; + addr -= hartid * SIFIVE_CLIC_HART_SIZE; + sifive_clic_hart_write(clic, addr, value, size, PRV_S, hartid); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clic: invalid write: 0x%" HWADDR_PRIx "\n", addr); + } +} + +static const MemoryRegionOps sifive_clic_ops = { + .read = sifive_clic_read, + .write = sifive_clic_write, + .endianness = DEVICE_LITTLE_ENDIAN, + .valid = { + .min_access_size = 4, + .max_access_size = 4 + } +}; + +static Property sifive_clic_properties[] = { + DEFINE_PROP_UINT32("num-harts", SiFiveCLICState, num_harts, 0), + DEFINE_PROP_UINT32("sip-base", SiFiveCLICState, sip_base, 0), + DEFINE_PROP_UINT32("timecmp-base", SiFiveCLICState, timecmp_base, 0), + DEFINE_PROP_UINT32("time-base", SiFiveCLICState, time_base, 0), + DEFINE_PROP_UINT32("aperture-size", SiFiveCLICState, aperture_size, 0), + DEFINE_PROP_UINT32("num-sources", SiFiveCLICState, num_sources, 0), + DEFINE_PROP_UINT8("int-bits", SiFiveCLICState, int_bits, 0), + DEFINE_PROP_UINT8("mode-bits", SiFiveCLICState, mode_bits, 0), + DEFINE_PROP_UINT8("level-bits", SiFiveCLICState, level_bits, 0), + DEFINE_PROP_UINT8("vec-bits", SiFiveCLICState, vec_bits, 0), + DEFINE_PROP_UINT32("clint-mmode-base", SiFiveCLICState, clint_mmode_base, 0), + DEFINE_PROP_UINT32("clint-smode-base", SiFiveCLICState, clint_smode_base, 0), + DEFINE_PROP_UINT32("clic-mmode-base", SiFiveCLICState, clic_mmode_base, 0), + DEFINE_PROP_UINT32("clic-smode-base", SiFiveCLICState, clic_smode_base, 0), + DEFINE_PROP_END_OF_LIST(), +}; + +static int sifive_clic_encode_irq_id(int irq, int hartid) +{ + return ((irq & 0x3ff) << 10) | ((hartid & 0x3ff) << 0); +} + +static void sifive_clic_decode_irq_id(int *irq, int *hartid, int id) +{ + *irq = (id >> 10) & 0x3ff; /* 1024 interrupt sources */ + *hartid = (id >> 0) & 0x3ff; /* 1024 harts */ +} + +static void sifive_clic_irq(void *opaque, int id, int level) +{ + SiFiveCLICState *clic = opaque; + int irq, hartid; + sifive_clic_decode_irq_id(&hartid, &irq, id); + trace_sifive_clic_irq(hartid, irq, level); + sifive_clic_update_intip(clic, PRV_M, hartid, irq, level); +} + +qemu_irq sifive_clic_get_irq(DeviceState *dev, int hartid, int irq) +{ + SiFiveCLICState *clic = SIFIVE_CLIC(dev); + size_t irq_offset = hartid * clic->num_sources + irq; + return clic->irqs[irq_offset]; +} + +static void sifive_clic_realize(DeviceState *dev, Error **errp) +{ + SiFiveCLICState *clic = SIFIVE_CLIC(dev); + size_t harts_x_sources = clic->num_harts * clic->num_sources; + int i, irq, hartid; + + memory_region_init_io(&clic->mmio, OBJECT(dev), &sifive_clic_ops, clic, + TYPE_SIFIVE_CLIC, clic->aperture_size); + + clic->clicintip = g_new0(uint8_t, harts_x_sources); + clic->clicintie = g_new0(uint8_t, harts_x_sources); + clic->clicintcfg = g_new0(uint8_t, harts_x_sources); + clic->active_list = g_new0(CLICActiveInterrupt, harts_x_sources); + clic->active_count = g_new0(size_t, clic->num_harts); + clic->nmbits = g_new0(uint8_t, clic->num_harts); + clic->nlbits = g_new0(uint8_t, clic->num_harts); + clic->nvbits = g_new0(uint8_t, clic->num_harts); + clic->npbits = g_new0(uint8_t, clic->num_harts); + sysbus_init_mmio(SYS_BUS_DEVICE(dev), &clic->mmio); + clic->irqs = g_new0(qemu_irq, harts_x_sources); + for (hartid = 0; hartid < clic->num_harts; hartid++) { + for (irq = 0; irq < clic->num_sources; irq++) { + int id = sifive_clic_encode_irq_id(hartid, irq); + size_t irq_offset = hartid * clic->num_sources + irq; + clic->irqs[irq_offset] = qemu_allocate_irq(sifive_clic_irq, + clic, id); + } + } + + /* The CLIC controls SSIP and STIP */ + for (i = 0; i < smp_cpus; i++) { + RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i)); + cpu->env.clic = clic; + if (riscv_cpu_claim_interrupts(cpu, MIP_STIP | MIP_SSIP) < 0) { + qemu_log_mask(LOG_GUEST_ERROR, + "sifive_plic_realize: STIP and SSIP already claimed\n"); + exit(1); + } + } +} + +static void sifive_clic_class_init(ObjectClass *klass, void *data) +{ + DeviceClass *dc = DEVICE_CLASS(klass); + dc->realize = sifive_clic_realize; + dc->props = sifive_clic_properties; +} + +static const TypeInfo sifive_clic_info = { + .name = TYPE_SIFIVE_CLIC, + .parent = TYPE_SYS_BUS_DEVICE, + .instance_size = sizeof(SiFiveCLICState), + .class_init = sifive_clic_class_init, +}; + +static void sifive_clic_register_types(void) +{ + type_register_static(&sifive_clic_info); +} + +type_init(sifive_clic_register_types) + + +/* + * Create CLIC device. + */ +DeviceState *sifive_clic_create(hwaddr addr, hwaddr size, + uint32_t num_harts, + uint32_t sip_base, + uint32_t timecmp_base, + uint32_t time_base, + uint32_t num_sources, + uint8_t int_bits, + uint8_t mode_bits, + uint8_t level_bits, + uint8_t vec_bits, + uint32_t clint_mmode_base, + uint32_t clint_smode_base, + uint32_t clic_mmode_base, + uint32_t clic_smode_base) +{ + int i; + + assert(num_sources >= SIFIVE_CLIC_MIN_SOURCES); + assert(num_sources <= SIFIVE_CLIC_MAX_SOURCES); + assert(int_bits >= SIFIVE_CLIC_MIN_INT_BITS); + assert(int_bits <= SIFIVE_CLIC_MAX_INT_BITS); + assert(mode_bits <= SIFIVE_CLIC_MAX_MODE_BITS); + assert(level_bits <= SIFIVE_CLIC_MAX_LEVEL_BITS); + assert(vec_bits <= SIFIVE_CLIC_MAX_VEC_BITS); + + DeviceState *dev = qdev_create(NULL, TYPE_SIFIVE_CLIC); + + for (i = 0; i < num_harts; i++) { + RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i)); + CPURISCVState *env = &cpu->env; + env->features |= (1ULL << RISCV_FEATURE_CLIC); + env->mtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + &sifive_clic_mtimecmp_cb, cpu); + env->stimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + &sifive_clic_stimecmp_cb, cpu); + env->mtimecmp = 0; + env->stimecmp = 0; + } + + qdev_prop_set_uint32(dev, "num-harts", num_harts); + qdev_prop_set_uint32(dev, "sip-base", sip_base); + 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, "num-sources", num_sources); + qdev_prop_set_uint8(dev, "int-bits", int_bits); + qdev_prop_set_uint8(dev, "mode-bits", mode_bits); + qdev_prop_set_uint8(dev, "level-bits", level_bits); + qdev_prop_set_uint8(dev, "vec-bits", vec_bits); + qdev_prop_set_uint32(dev, "clint-mmode-base", clint_mmode_base); + qdev_prop_set_uint32(dev, "clint-smode-base", clint_smode_base); + qdev_prop_set_uint32(dev, "clic-mmode-base", clic_mmode_base); + qdev_prop_set_uint32(dev, "clic-smode-base", clic_smode_base); + qdev_init_nofail(dev); + sysbus_mmio_map(SYS_BUS_DEVICE(dev), 0, addr); + return dev; +} diff --git a/hw/riscv/sifive_clint.c b/hw/riscv/sifive_clint.c index d4c159e9373..041b7038ffc 100644 --- a/hw/riscv/sifive_clint.c +++ b/hw/riscv/sifive_clint.c @@ -20,7 +20,7 @@ */ #include "qemu/osdep.h" -#include "qemu/error-report.h" +#include "qemu/log.h" #include "hw/sysbus.h" #include "target/riscv/cpu.h" #include "hw/riscv/sifive_clint.h" @@ -32,59 +32,61 @@ static uint64_t cpu_riscv_read_rtc(void) SIFIVE_CLINT_TIMEBASE_FREQ, NANOSECONDS_PER_SECOND); } +static void sifive_clint_mtimecmp_cb(void *cpu) +{ + riscv_cpu_update_mip((RISCVCPU *)cpu, MIP_MTIP, BOOL_TO_MASK(1)); +} + /* * Called when timecmp is written to update the QEMU timer or immediately * trigger timer interrupt if mtimecmp <= current timer value. */ static void sifive_clint_write_timecmp(RISCVCPU *cpu, uint64_t value) { - uint64_t next; - uint64_t diff; + CPURISCVState *env = &cpu->env; - uint64_t rtc_r = cpu_riscv_read_rtc(); + uint64_t rtc = cpu_riscv_read_rtc(); + uint64_t cmp = env->mtimecmp = value; + uint64_t diff = cmp - rtc; + uint64_t next_ns = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + + muldiv64(diff, NANOSECONDS_PER_SECOND, SIFIVE_CLINT_TIMEBASE_FREQ); - cpu->env.timecmp = value; - if (cpu->env.timecmp <= rtc_r) { - /* if we're setting an MTIMECMP value in the "past", + if (cmp <= rtc) { + /* if we're setting a timecmp value in the "past", immediately raise the timer interrupt */ riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(1)); - return; + } else { + /* otherwise, set up the future timer interrupt */ + riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(0)); + timer_mod(env->mtimer, next_ns); } - - /* otherwise, set up the future timer interrupt */ - riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(0)); - diff = cpu->env.timecmp - rtc_r; - /* back to ns (note args switched in muldiv64) */ - next = qemu_clock_get_ns(QEMU_CLOCK_VIRTUAL) + - muldiv64(diff, NANOSECONDS_PER_SECOND, SIFIVE_CLINT_TIMEBASE_FREQ); - timer_mod(cpu->env.timer, next); -} - -/* - * Callback used when the timer set using timer_mod expires. - * Should raise the timer interrupt line - */ -static void sifive_clint_timer_cb(void *opaque) -{ - RISCVCPU *cpu = opaque; - riscv_cpu_update_mip(cpu, MIP_MTIP, BOOL_TO_MASK(1)); } /* CPU wants to read rtc or timecmp register */ static uint64_t sifive_clint_read(void *opaque, hwaddr addr, unsigned size) { SiFiveCLINTState *clint = opaque; + + /* reads must be 4 byte aligned words */ + if ((addr & 0x3) != 0 || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid read size %u: 0x%" HWADDR_PRIx "\n", size, addr); + return 0; + } + if (addr >= clint->sip_base && addr < clint->sip_base + (clint->num_harts << 2)) { size_t hartid = (addr - clint->sip_base) >> 2; CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { - error_report("clint: invalid timecmp hartid: %zu", hartid); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid sip hartid: %zu\n", hartid); } else if ((addr & 0x3) == 0) { return (env->mip & MIP_MSIP) > 0; } else { - error_report("clint: invalid read: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid sip read: 0x%" HWADDR_PRIx "\n", addr); return 0; } } else if (addr >= clint->timecmp_base && @@ -93,17 +95,19 @@ static uint64_t sifive_clint_read(void *opaque, hwaddr addr, unsigned size) CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { - error_report("clint: invalid timecmp hartid: %zu", hartid); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid timecmp hartid: %zu\n", hartid); } else if ((addr & 0x7) == 0) { /* timecmp_lo */ - uint64_t timecmp = env->timecmp; + uint64_t timecmp = env->mtimecmp; return timecmp & 0xFFFFFFFF; } else if ((addr & 0x7) == 4) { /* timecmp_hi */ - uint64_t timecmp = env->timecmp; + uint64_t timecmp = env->mtimecmp; return (timecmp >> 32) & 0xFFFFFFFF; } else { - error_report("clint: invalid read: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid read: 0x%" HWADDR_PRIx "\n", addr); return 0; } } else if (addr == clint->time_base) { @@ -112,9 +116,11 @@ static uint64_t sifive_clint_read(void *opaque, hwaddr addr, unsigned size) } else if (addr == clint->time_base + 4) { /* time_hi */ return (cpu_riscv_read_rtc() >> 32) & 0xFFFFFFFF; + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid read: 0x%" HWADDR_PRIx "\n", addr); } - error_report("clint: invalid read: %08x", (uint32_t)addr); return 0; } @@ -124,17 +130,26 @@ static void sifive_clint_write(void *opaque, hwaddr addr, uint64_t value, { SiFiveCLINTState *clint = opaque; + /* writes must be 4 byte aligned words */ + if ((addr & 0x3) != 0 || size != 4) { + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid write size %u: 0x%" HWADDR_PRIx "\n", size, addr); + return; + } + if (addr >= clint->sip_base && addr < clint->sip_base + (clint->num_harts << 2)) { size_t hartid = (addr - clint->sip_base) >> 2; CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { - error_report("clint: invalid timecmp hartid: %zu", hartid); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid sip hartid: %zu\n", hartid); } else if ((addr & 0x3) == 0) { riscv_cpu_update_mip(RISCV_CPU(cpu), MIP_MSIP, BOOL_TO_MASK(value)); } else { - error_report("clint: invalid sip write: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid sip write: 0x%" HWADDR_PRIx "\n", addr); } return; } else if (addr >= clint->timecmp_base && @@ -143,33 +158,34 @@ static void sifive_clint_write(void *opaque, hwaddr addr, uint64_t value, CPUState *cpu = qemu_get_cpu(hartid); CPURISCVState *env = cpu ? cpu->env_ptr : NULL; if (!env) { - error_report("clint: invalid timecmp hartid: %zu", hartid); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid timecmp hartid: %zu\n", hartid); } else if ((addr & 0x7) == 0) { /* timecmp_lo */ - uint64_t timecmp_hi = env->timecmp >> 32; + uint64_t timecmp_hi = env->mtimecmp >> 32; sifive_clint_write_timecmp(RISCV_CPU(cpu), timecmp_hi << 32 | (value & 0xFFFFFFFF)); return; } else if ((addr & 0x7) == 4) { /* timecmp_hi */ - uint64_t timecmp_lo = env->timecmp; + uint64_t timecmp_lo = env->mtimecmp; sifive_clint_write_timecmp(RISCV_CPU(cpu), value << 32 | (timecmp_lo & 0xFFFFFFFF)); } else { - error_report("clint: invalid timecmp write: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid timecmp write: 0x%" HWADDR_PRIx "\n", addr); } return; } else if (addr == clint->time_base) { /* time_lo */ - error_report("clint: time_lo write not implemented"); - return; + qemu_log_mask(LOG_UNIMP, "clint: time_lo write not implemented\n"); } else if (addr == clint->time_base + 4) { /* time_hi */ - error_report("clint: time_hi write not implemented"); - return; + qemu_log_mask(LOG_UNIMP, "clint: time_hi write not implemented"); + } else { + qemu_log_mask(LOG_GUEST_ERROR, + "clint: invalid write: 0x%" HWADDR_PRIx "\n", addr); } - - error_report("clint: invalid write: %08x", (uint32_t)addr); } static const MemoryRegionOps sifive_clint_ops = { @@ -234,9 +250,9 @@ DeviceState *sifive_clint_create(hwaddr addr, hwaddr size, uint32_t num_harts, if (!env) { continue; } - env->timer = timer_new_ns(QEMU_CLOCK_VIRTUAL, - &sifive_clint_timer_cb, cpu); - env->timecmp = 0; + env->mtimer = timer_new_ns(QEMU_CLOCK_VIRTUAL, + &sifive_clint_mtimecmp_cb, cpu); + env->mtimecmp = 0; } DeviceState *dev = qdev_create(NULL, TYPE_SIFIVE_CLINT); diff --git a/hw/riscv/sifive_e.c b/hw/riscv/sifive_e.c index 16b547eec81..350ae25672b 100644 --- a/hw/riscv/sifive_e.c +++ b/hw/riscv/sifive_e.c @@ -41,6 +41,7 @@ #include "hw/riscv/riscv_hart.h" #include "hw/riscv/sifive_plic.h" #include "hw/riscv/sifive_clint.h" +#include "hw/riscv/sifive_clic.h" #include "hw/riscv/sifive_test.h" #include "hw/riscv/sifive_prci.h" #include "hw/riscv/sifive_uart.h" @@ -58,7 +59,8 @@ static const struct MemmapEntry { [SIFIVE_E_MROM] = { 0x1000, 0x2000 }, [SIFIVE_E_OTP] = { 0x20000, 0x2000 }, [SIFIVE_E_TEST] = { 0x100000, 0x1000 }, - [SIFIVE_E_CLINT] = { 0x2000000, 0x10000 }, + [SIFIVE_E_CLINT] = { 0x2000000, 0x10000 }, /* sifive_e */ + [SIFIVE_E_CLIC] = { 0x2000000, 0x1000000 }, /* sifive_ex */ [SIFIVE_E_PLIC] = { 0xc000000, 0x4000000 }, [SIFIVE_E_AON] = { 0x10000000, 0x8000 }, [SIFIVE_E_PRCI] = { 0x10008000, 0x8000 }, @@ -97,7 +99,7 @@ static void sifive_mmio_emulate(MemoryRegion *parent, const char *name, memory_region_add_subregion(parent, offset, mock_mmio); } -static void riscv_sifive_e_init(MachineState *machine) +static void riscv_sifive_e_generic_init(MachineState *machine, bool has_clic) { const struct MemmapEntry *memmap = sifive_e_memmap; SiFiveEState *s = g_new0(SiFiveEState, 1); @@ -160,12 +162,30 @@ static void riscv_sifive_e_init(MachineState *machine) SIFIVE_E_PLIC_CONTEXT_BASE, SIFIVE_E_PLIC_CONTEXT_STRIDE, memmap[SIFIVE_E_PLIC].size); - sifive_clint_create(memmap[SIFIVE_E_CLINT].base, - memmap[SIFIVE_E_CLINT].size, - smp_cpus, - SIFIVE_SIP_BASE, - SIFIVE_TIMECMP_BASE, - SIFIVE_TIME_BASE); + if (has_clic) { + sifive_clic_create(memmap[SIFIVE_E_CLIC].base, + memmap[SIFIVE_E_CLIC].size, + smp_cpus, + SIFIVE_SIP_BASE, + SIFIVE_TIMECMP_BASE, + SIFIVE_TIME_BASE, + SIFIVE_E_CLIC_NUM_SOURCES, + SIFIVE_E_CLIC_MAX_INT_BITS, + SIFIVE_E_CLIC_MAX_MODE_BITS, + SIFIVE_E_CLIC_MAX_LEVEL_BITS, + SIFIVE_E_CLIC_MAX_VEC_BITS, + SIFIVE_CLIC_CLINT_MMODE_OFFSET, + SIFIVE_CLIC_CLINT_SMODE_OFFSET, + SIFIVE_CLIC_CLIC_MMODE_OFFSET, + SIFIVE_CLIC_CLIC_SMODE_OFFSET); + } else { + sifive_clint_create(memmap[SIFIVE_E_CLINT].base, + memmap[SIFIVE_E_CLINT].size, + smp_cpus, + SIFIVE_SIP_BASE, + SIFIVE_TIMECMP_BASE, + SIFIVE_TIME_BASE); + } sifive_test_create(memmap[SIFIVE_E_TEST].base); sifive_mmio_emulate(sys_mem, "riscv.sifive.e.aon", memmap[SIFIVE_E_AON].base, memmap[SIFIVE_E_AON].size); @@ -196,6 +216,16 @@ static void riscv_sifive_e_init(MachineState *machine) memory_region_add_subregion(sys_mem, memmap[SIFIVE_E_XIP].base, xip_mem); } +static void riscv_sifive_e_init(MachineState *machine) +{ + riscv_sifive_e_generic_init(machine, false); +} + +static void riscv_sifive_ex_init(MachineState *machine) +{ + riscv_sifive_e_generic_init(machine, true); +} + static void riscv_sifive_e_machine_init(MachineClass *mc) { mc->desc = "RISC-V Board compatible with SiFive E SDK"; @@ -203,4 +233,12 @@ static void riscv_sifive_e_machine_init(MachineClass *mc) mc->max_cpus = 1; } +static void riscv_sifive_ex_machine_init(MachineClass *mc) +{ + mc->desc = "RISC-V Board compatible with SiFive E SDK (x-clic-spec)"; + mc->init = riscv_sifive_ex_init; + mc->max_cpus = 1; +} + DEFINE_MACHINE("sifive_e", riscv_sifive_e_machine_init) +DEFINE_MACHINE("sifive_ex", riscv_sifive_ex_machine_init) diff --git a/hw/riscv/sifive_plic.c b/hw/riscv/sifive_plic.c index bcde8cc7585..86685034a7c 100644 --- a/hw/riscv/sifive_plic.c +++ b/hw/riscv/sifive_plic.c @@ -20,7 +20,6 @@ #include "qemu/osdep.h" #include "qemu/log.h" -#include "qemu/error-report.h" #include "hw/sysbus.h" #include "target/riscv/cpu.h" #include "sysemu/sysemu.h" @@ -36,7 +35,7 @@ static PLICMode char_to_mode(char c) case 'H': return PLICMode_H; case 'M': return PLICMode_M; default: - error_report("plic: invalid mode '%c'", c); + qemu_log_mask(LOG_GUEST_ERROR, "plic: invalid mode '%c'\n", c); exit(1); } } @@ -198,7 +197,7 @@ static uint64_t sifive_plic_read(void *opaque, hwaddr addr, unsigned size) { SiFivePLICState *plic = opaque; - /* writes must be 4 byte words */ + /* reads must be 4 byte words */ if ((addr & 0x3) != 0) { goto err; } @@ -262,7 +261,9 @@ static uint64_t sifive_plic_read(void *opaque, hwaddr addr, unsigned size) } err: - error_report("plic: invalid register read: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "plic: invalid read: 0x%" HWADDR_PRIx "\n", addr); + return 0; } @@ -289,7 +290,8 @@ static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value, } else if (addr >= plic->pending_base && /* 1 bit per source */ addr < plic->pending_base + (plic->num_sources >> 3)) { - error_report("plic: invalid pending write: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "plic: invalid pending write: 0x%" HWADDR_PRIx "\n", addr); return; } else if (addr >= plic->enable_base && /* 1 bit per source */ addr < plic->enable_base + plic->num_addrs * plic->enable_stride) @@ -339,7 +341,8 @@ static void sifive_plic_write(void *opaque, hwaddr addr, uint64_t value, } err: - error_report("plic: invalid register write: %08x", (uint32_t)addr); + qemu_log_mask(LOG_GUEST_ERROR, + "plic: invalid write: 0x%" HWADDR_PRIx "\n", addr); } static const MemoryRegionOps sifive_plic_ops = { @@ -390,8 +393,9 @@ static void parse_hart_config(SiFivePLICState *plic) } else { int m = 1 << char_to_mode(c); if (modes == (modes | m)) { - error_report("plic: duplicate mode '%c' in config: %s", - c, plic->hart_config); + qemu_log_mask(LOG_GUEST_ERROR, + "plic: duplicate mode '%c' in config: %s", c, + plic->hart_config); exit(1); } modes |= m; @@ -453,7 +457,8 @@ static void sifive_plic_realize(DeviceState *dev, Error **errp) for (i = 0; i < smp_cpus; i++) { RISCVCPU *cpu = RISCV_CPU(qemu_get_cpu(i)); if (riscv_cpu_claim_interrupts(cpu, MIP_SEIP) < 0) { - error_report("sifive_plic_realize: SEIP already claimed"); + qemu_log_mask(LOG_GUEST_ERROR, + "sifive_plic_realize: SEIP already claimed\n"); exit(1); } } diff --git a/hw/riscv/sifive_u.c b/hw/riscv/sifive_u.c index fc84ab42e91..b3a43529861 100644 --- a/hw/riscv/sifive_u.c +++ b/hw/riscv/sifive_u.c @@ -39,6 +39,7 @@ #include "hw/riscv/riscv_hart.h" #include "hw/riscv/sifive_plic.h" #include "hw/riscv/sifive_clint.h" +#include "hw/riscv/sifive_clic.h" #include "hw/riscv/sifive_test.h" #include "hw/riscv/sifive_uart.h" #include "hw/riscv/sifive_prci.h" @@ -58,7 +59,8 @@ static const struct MemmapEntry { [SIFIVE_U_DEBUG] = { 0x0, 0x100 }, [SIFIVE_U_MROM] = { 0x1000, 0x11000 }, [SIFIVE_U_TEST] = { 0x100000, 0x1000 }, - [SIFIVE_U_CLINT] = { 0x2000000, 0x10000 }, + [SIFIVE_U_CLINT] = { 0x2000000, 0x10000 }, /* sifive_u */ + [SIFIVE_U_CLIC] = { 0x2000000, 0x1000000 }, /* sifive_ux */ [SIFIVE_U_PLIC] = { 0xc000000, 0x4000000 }, [SIFIVE_U_UART0] = { 0x10013000, 0x1000 }, [SIFIVE_U_UART1] = { 0x10023000, 0x1000 }, @@ -247,7 +249,7 @@ static void create_fdt(SiFiveUState *s, const struct MemmapEntry *memmap, g_free(nodename); } -static void riscv_sifive_u_init(MachineState *machine) +static void riscv_sifive_u_generic_init(MachineState *machine, bool has_clic) { const struct MemmapEntry *memmap = sifive_u_memmap; SiFiveUState *s = g_new0(SiFiveUState, 1); @@ -339,11 +341,29 @@ static void riscv_sifive_u_init(MachineState *machine) serial_hd(0), qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_U_UART0_IRQ)); sifive_uart_create(system_memory, memmap[SIFIVE_U_UART1].base, serial_hd(1), qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_U_UART1_IRQ)); - sifive_clint_create(memmap[SIFIVE_U_CLINT].base, - memmap[SIFIVE_U_CLINT].size, smp_cpus, - SIFIVE_SIP_BASE, - SIFIVE_TIMECMP_BASE, - SIFIVE_TIME_BASE); + if (has_clic) { + sifive_clic_create(memmap[SIFIVE_U_CLIC].base, + memmap[SIFIVE_U_CLIC].size, + smp_cpus, + SIFIVE_SIP_BASE, + SIFIVE_TIMECMP_BASE, + SIFIVE_TIME_BASE, + SIFIVE_U_CLIC_NUM_SOURCES, + SIFIVE_U_CLIC_MAX_INT_BITS, + SIFIVE_U_CLIC_MAX_MODE_BITS, + SIFIVE_U_CLIC_MAX_LEVEL_BITS, + SIFIVE_U_CLIC_MAX_VEC_BITS, + SIFIVE_CLIC_CLINT_MMODE_OFFSET, + SIFIVE_CLIC_CLINT_SMODE_OFFSET, + SIFIVE_CLIC_CLIC_MMODE_OFFSET, + SIFIVE_CLIC_CLIC_SMODE_OFFSET); + } else { + sifive_clint_create(memmap[SIFIVE_U_CLINT].base, + memmap[SIFIVE_U_CLINT].size, smp_cpus, + SIFIVE_SIP_BASE, + SIFIVE_TIMECMP_BASE, + SIFIVE_TIME_BASE); + } sifive_test_create(memmap[SIFIVE_U_TEST].base); /* Initialize gem ethernet */ @@ -362,6 +382,16 @@ static void riscv_sifive_u_init(MachineState *machine) qdev_get_gpio_in(DEVICE(s->plic), SIFIVE_U_GEM_IRQ)); } +static void riscv_sifive_u_init(MachineState *machine) +{ + riscv_sifive_u_generic_init(machine, false); +} + +static void riscv_sifive_ux_init(MachineState *machine) +{ + riscv_sifive_u_generic_init(machine, true); +} + static void riscv_sifive_u_machine_init(MachineClass *mc) { mc->desc = "RISC-V Board compatible with SiFive U SDK"; @@ -369,4 +399,12 @@ static void riscv_sifive_u_machine_init(MachineClass *mc) mc->max_cpus = 1; } +static void riscv_sifive_ux_machine_init(MachineClass *mc) +{ + mc->desc = "RISC-V Board compatible with SiFive U SDK (x-clic-spec)"; + mc->init = riscv_sifive_ux_init; + mc->max_cpus = 1; +} + DEFINE_MACHINE("sifive_u", riscv_sifive_u_machine_init) +DEFINE_MACHINE("sifive_ux", riscv_sifive_ux_machine_init) diff --git a/hw/riscv/trace-events b/hw/riscv/trace-events new file mode 100644 index 00000000000..41b964d3efb --- /dev/null +++ b/hw/riscv/trace-events @@ -0,0 +1,6 @@ +# hw/riscv/sifive_clic.c +sifive_clic_intip(int mode, int hartid, int irq, int val) "mode:%d hart:%d, irq:%d, val:%d" +sifive_clic_intie(int mode, int hartid, int irq, int val) "mode:%d hart:%d, irq:%d, val:%d" +sifive_clic_intcfg(int mode, int hartid, int irq, int val) "mode:%d hart:%d, irq:%d, val:%d" +sifive_clic_cfg(int hartid, int nmbits, int nlbits, int nvbits) "hart:%d, nmbits:%d, nlbits:%d, nvbits:%d" +sifive_clic_irq(int hartid, int irq, int level) "hartid:%d irq:%d level:%d" diff --git a/include/hw/riscv/sifive_clic.h b/include/hw/riscv/sifive_clic.h new file mode 100644 index 00000000000..ce5ad22a684 --- /dev/null +++ b/include/hw/riscv/sifive_clic.h @@ -0,0 +1,127 @@ +/* + * SiFive CLIC (Core Local Interrupt Controller) interface + * + * Copyright (c) 2017 SiFive, 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 . + */ + +#ifndef HW_SIFIVE_CLIC_H +#define HW_SIFIVE_CLIC_H + +#define TYPE_SIFIVE_CLIC "riscv.sifive.clic" + +#define SIFIVE_CLIC(obj) \ + OBJECT_CHECK(SiFiveCLICState, (obj), TYPE_SIFIVE_CLIC) + +typedef struct CLICActiveInterrupt { + uint8_t intcfg; + uint16_t irq; +} CLICActiveInterrupt; + +typedef struct SiFiveCLICState { + /*< private >*/ + SysBusDevice parent_obj; + + /*< public >*/ + MemoryRegion mmio; + uint32_t num_harts; + uint32_t aperture_size; + + /* CLINT/CLIC shared state */ + uint32_t sip_base; + uint32_t timecmp_base; + uint32_t time_base; + + /* CLINT/CLIC base addresses */ + uint32_t clint_mmode_base; + uint32_t clint_smode_base; + uint32_t clic_mmode_base; + uint32_t clic_smode_base; + + /* CLIC parameters */ + uint32_t num_sources; /* 4-1024 */ + uint8_t int_bits; /* 2-8 */ + uint8_t mode_bits; /* 0-2 */ + uint8_t level_bits; /* 0-4 */ + uint8_t vec_bits; /* 0-1 */ + + /* CLIC configuration (decoded from cliccfg) */ + uint8_t *nmbits; /* nmbits (2-bit), M (0), M/U (0-1), M/S/U (0-2) */ + uint8_t *nlbits; /* nlbits (3-bit), 0-4 */ + uint8_t *nvbits; /* nvbits (1-bit), 0-1 */ + uint8_t *npbits; /* min(8 - nmbits - nlbits, int_bits) */ + + /* CLIC State */ + uint8_t *clicintip; /* mode_base + hart * 0x1000 + 0x000 + i */ + uint8_t *clicintie; /* mode_base + hart * 0x1000 + 0x400 + i */ + uint8_t *clicintcfg; /* mode_base + hart * 0x1000 + 0x800 + i */ + + /* + * CLIC per hart active interrupts + * + * We maintain per hart lists of enabled interrupts sorted by + * mode+level+priority. The sorting is done on the configuration path + * so that the interrupt delivery fastpath can linear scan enabled + * interrupts in priority order. + */ + CLICActiveInterrupt *active_list; + size_t *active_count; + + /* CLIC IRQ handlers */ + qemu_irq *irqs; + +} SiFiveCLICState; + +enum { + SIFIVE_CLIC_CLINT_SIZE = 0x10000, + SIFIVE_CLIC_HART_SIZE = 0x1000, + + SIFIVE_CLICINTIP_OFFSET = 0x0, + SIFIVE_CLICINTIE_OFFSET = 0x400, + SIFIVE_CLICINTCFG_OFFSET = 0x800, + SIFIVE_CLICCFG_OFFSET = 0xc00, + + SIFIVE_CLIC_CLINT_MMODE_OFFSET = 0x0, + SIFIVE_CLIC_CLINT_SMODE_OFFSET = 0x20000, + SIFIVE_CLIC_CLIC_MMODE_OFFSET = 0x800000, + SIFIVE_CLIC_CLIC_SMODE_OFFSET = 0xc00000, + + SIFIVE_CLIC_MIN_SOURCES = 4, + SIFIVE_CLIC_MAX_SOURCES = 1024, + SIFIVE_CLIC_MIN_INT_BITS = 2, + SIFIVE_CLIC_MAX_INT_BITS = 8, + SIFIVE_CLIC_MAX_MODE_BITS = 8, + SIFIVE_CLIC_MAX_LEVEL_BITS = 8, + SIFIVE_CLIC_MAX_PRIORITY_BITS = 8, + SIFIVE_CLIC_MAX_VEC_BITS = 1 +}; + +DeviceState *sifive_clic_create(hwaddr addr, hwaddr size, + uint32_t num_harts, + uint32_t sip_base, + uint32_t timecmp_base, + uint32_t time_base, + uint32_t num_sources, + uint8_t int_bits, + uint8_t mode_bits, + uint8_t level_bits, + uint8_t vec_bits, + uint32_t clint_mmode_base, + uint32_t clint_smode_base, + uint32_t clic_mmode_base, + uint32_t clic_smode_base); + +qemu_irq sifive_clic_get_irq(DeviceState *dev, int hartid, int irq); + +#endif diff --git a/include/hw/riscv/sifive_e.h b/include/hw/riscv/sifive_e.h index 0d3666bb124..fcc56aa1a6f 100644 --- a/include/hw/riscv/sifive_e.h +++ b/include/hw/riscv/sifive_e.h @@ -34,6 +34,7 @@ enum { SIFIVE_E_OTP, SIFIVE_E_TEST, SIFIVE_E_CLINT, + SIFIVE_E_CLIC, SIFIVE_E_PLIC, SIFIVE_E_AON, SIFIVE_E_PRCI, @@ -56,6 +57,14 @@ enum { SIFIVE_E_UART1_IRQ = 4 }; +enum { + SIFIVE_E_CLIC_NUM_SOURCES = 128, + SIFIVE_E_CLIC_MAX_INT_BITS = 4, + SIFIVE_E_CLIC_MAX_MODE_BITS = 0, + SIFIVE_E_CLIC_MAX_LEVEL_BITS = 4, + SIFIVE_E_CLIC_MAX_VEC_BITS = 1 +}; + #define SIFIVE_E_PLIC_HART_CONFIG "M" #define SIFIVE_E_PLIC_NUM_SOURCES 127 #define SIFIVE_E_PLIC_NUM_PRIORITIES 7 diff --git a/include/hw/riscv/sifive_u.h b/include/hw/riscv/sifive_u.h index 5aab5351c60..0c4e43b72e5 100644 --- a/include/hw/riscv/sifive_u.h +++ b/include/hw/riscv/sifive_u.h @@ -36,6 +36,7 @@ enum { SIFIVE_U_MROM, SIFIVE_U_TEST, SIFIVE_U_CLINT, + SIFIVE_U_CLIC, SIFIVE_U_PLIC, SIFIVE_U_UART0, SIFIVE_U_UART1, @@ -53,6 +54,14 @@ enum { SIFIVE_U_CLOCK_FREQ = 1000000000 }; +enum { + SIFIVE_U_CLIC_NUM_SOURCES = 128, + SIFIVE_U_CLIC_MAX_INT_BITS = 8, + SIFIVE_U_CLIC_MAX_MODE_BITS = 2, + SIFIVE_U_CLIC_MAX_LEVEL_BITS = 4, + SIFIVE_U_CLIC_MAX_VEC_BITS = 1 +}; + #define SIFIVE_U_PLIC_HART_CONFIG "MS" #define SIFIVE_U_PLIC_NUM_SOURCES 127 #define SIFIVE_U_PLIC_NUM_PRIORITIES 7 diff --git a/target/riscv/cpu.c b/target/riscv/cpu.c index 953d7d7b53b..77f882bee1d 100644 --- a/target/riscv/cpu.c +++ b/target/riscv/cpu.c @@ -261,7 +261,7 @@ static bool riscv_cpu_has_work(CPUState *cs) * Definition of the WFI instruction requires it to ignore the privilege * mode and delegation registers, but respect individual enables */ - return (atomic_read(&env->mip) & env->mie) != 0; + return (atomic_read(&env->mip) & env->mie) != 0 || (env->exccode != -1); #else return true; #endif @@ -285,6 +285,7 @@ static void riscv_cpu_reset(CPUState *cs) env->mstatus &= ~(MSTATUS_MIE | MSTATUS_MPRV); env->mcause = 0; env->pc = env->resetvec; + env->exccode = -1; /* current CLIC interrupt */ #endif cs->exception_index = EXCP_NONE; set_default_nan_mode(1, &env->fp_status); diff --git a/target/riscv/cpu.h b/target/riscv/cpu.h index 7b3ac29fe75..c2ff0d30fb9 100644 --- a/target/riscv/cpu.h +++ b/target/riscv/cpu.h @@ -87,7 +87,8 @@ enum { RISCV_FEATURE_MMU, RISCV_FEATURE_PMP, - RISCV_FEATURE_MISA + RISCV_FEATURE_MISA, + RISCV_FEATURE_CLIC }; #define USER_VERSION_2_02_0 0x00020200 @@ -101,6 +102,8 @@ enum { #define MAX_RISCV_PMPS (16) +#define CPU_INTERRUPT_CLIC CPU_INTERRUPT_TGT_EXT_0 + typedef struct CPURISCVState CPURISCVState; #include "pmp.h" @@ -145,6 +148,8 @@ struct CPURISCVState { */ uint32_t mip; uint32_t miclaim; + uint32_t mintstatus; /* clic-spec */ + uint32_t exccode; /* clic-qemu */ target_ulong mie; target_ulong mideleg; @@ -156,10 +161,12 @@ struct CPURISCVState { target_ulong medeleg; target_ulong stvec; + target_ulong stvt; /* clic-spec */ target_ulong sepc; target_ulong scause; target_ulong mtvec; + target_ulong mtvt; /* clic-spec */ target_ulong mepc; target_ulong mcause; target_ulong mtval; /* since: priv-1.10.0 */ @@ -173,7 +180,10 @@ struct CPURISCVState { /* temporary htif regs */ uint64_t mfromhost; uint64_t mtohost; - uint64_t timecmp; + + /* timer comparators */ + uint64_t mtimecmp; + uint64_t stimecmp; /* physical memory protection */ pmp_table_t pmp_state; @@ -185,7 +195,9 @@ struct CPURISCVState { CPU_COMMON /* Fields from here on are preserved across CPU reset. */ - QEMUTimer *timer; /* Internal timer */ + QEMUTimer *mtimer; /* Internal timer */ + QEMUTimer *stimer; /* Internal timer */ + void *clic; }; #define RISCV_CPU_CLASS(klass) \ @@ -272,6 +284,7 @@ void riscv_cpu_list(FILE *f, fprintf_function cpu_fprintf); #ifndef CONFIG_USER_ONLY int riscv_cpu_claim_interrupts(RISCVCPU *cpu, uint32_t interrupts); uint32_t riscv_cpu_update_mip(RISCVCPU *cpu, uint32_t mask, uint32_t value); +void riscv_cpu_clic_interrupt(RISCVCPU *cpu, int exccode); #define BOOL_TO_MASK(x) (-!!(x)) /* helper for riscv_cpu_update_mip value */ #endif void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv); diff --git a/target/riscv/cpu_bits.h b/target/riscv/cpu_bits.h index 7afcb2468d8..926afd142dd 100644 --- a/target/riscv/cpu_bits.h +++ b/target/riscv/cpu_bits.h @@ -131,6 +131,7 @@ #define CSR_MIE 0x304 #define CSR_MTVEC 0x305 #define CSR_MCOUNTEREN 0x306 +#define CSR_MTVT 0x307 /* clic-spec-draft */ /* Legacy Counter Setup (priv v1.9.1) */ #define CSR_MUCOUNTEREN 0x320 @@ -142,12 +143,16 @@ #define CSR_MCAUSE 0x342 #define CSR_MBADADDR 0x343 #define CSR_MIP 0x344 +#define CSR_MNXTI 0x345 /* clic-spec-draft */ +#define CSR_MINTSTATUS 0x346 /* clic-spec-draft */ +#define CSR_MSCRATCHCSW 0x348 /* clic-spec-draft */ /* Supervisor Trap Setup */ #define CSR_SSTATUS 0x100 #define CSR_SIE 0x104 #define CSR_STVEC 0x105 #define CSR_SCOUNTEREN 0x106 +#define CSR_STVT 0x107 /* clic-spec-draft */ /* Supervisor Trap Handling */ #define CSR_SSCRATCH 0x140 @@ -155,6 +160,9 @@ #define CSR_SCAUSE 0x142 #define CSR_SBADADDR 0x143 #define CSR_SIP 0x144 +#define CSR_SNXTI 0x145 /* clic-spec-draft */ +#define CSR_SINTSTATUS 0x146 /* clic-spec-draft */ +#define CSR_SSCRATCHCSW 0x148 /* clic-spec-draft */ /* Supervisor Protection and Translation */ #define CSR_SPTBR 0x180 @@ -430,8 +438,9 @@ #define RISCV_EXCP_LOAD_PAGE_FAULT 0xd /* since: priv-1.10.0 */ #define RISCV_EXCP_STORE_PAGE_FAULT 0xf /* since: priv-1.10.0 */ +#define RISCV_EXCP_INT_CLIC 0x40000000 #define RISCV_EXCP_INT_FLAG 0x80000000 -#define RISCV_EXCP_INT_MASK 0x7fffffff +#define RISCV_EXCP_INT_MASK 0x3fffffff /* Interrupt causes */ #define IRQ_U_SOFT 0 @@ -465,3 +474,26 @@ #define SIP_SSIP MIP_SSIP #define SIP_STIP MIP_STIP #define SIP_SEIP MIP_SEIP + +/* mintstatus */ +#define MINTSTATUS_MIL 0x0000f000 /* mil[3:0] */ +#define MINTSTATUS_SIL 0x000000f0 /* sil[3:0] */ +#define MINTSTATUS_UIL 0x0000000f /* uil[3:0] */ + +/* mcause */ +#define MCAUSE_MINHV 0x40000000 /* minhv */ +#define MCAUSE_MPP 0x30000000 /* mpp[1:0] */ +#define MCAUSE_MPIL 0x0f000000 /* mpil[3:0] */ +#define MCAUSE_MPIE 0x00800000 /* mpie */ +#define MCAUSE_EXCCODE 0x000003ff /* exccode[9:0] */ + +/* sintstatus */ +#define SINTSTATUS_SIL 0x000000f0 /* sil[3:0] */ +#define SINTSTATUS_UIL 0x0000000f /* uil[3:0] */ + +/* scause */ +#define SCAUSE_SINHV 0x40000000 /* sinhv */ +#define SCAUSE_SPP 0x30000000 /* spp[1:0] */ +#define SCAUSE_SPIL 0x0f000000 /* spil[3:0] */ +#define SCAUSE_SPIE 0x00800000 /* spie */ +#define SCAUSE_EXCCODE 0x000003ff /* exccode[9:0] */ diff --git a/target/riscv/cpu_helper.c b/target/riscv/cpu_helper.c index 61b2a3e1ae1..ab63569719a 100644 --- a/target/riscv/cpu_helper.c +++ b/target/riscv/cpu_helper.c @@ -34,6 +34,20 @@ int riscv_cpu_mmu_index(CPURISCVState *env, bool ifetch) } #ifndef CONFIG_USER_ONLY +static int riscv_cpu_local_irq_mode_enabled(CPURISCVState *env, int mode) +{ + switch (mode) { + case PRV_M: + return env->priv < PRV_M || + (env->priv == PRV_M && get_field(env->mstatus, MSTATUS_MIE)); + case PRV_S: + return env->priv < PRV_S || + (env->priv == PRV_S && get_field(env->mstatus, MSTATUS_SIE)); + default: + return false; + } +} + static int riscv_cpu_local_irq_pending(CPURISCVState *env) { target_ulong mstatus_mie = get_field(env->mstatus, MSTATUS_MIE); @@ -65,6 +79,17 @@ bool riscv_cpu_exec_interrupt(CPUState *cs, int interrupt_request) return true; } } + if (interrupt_request & CPU_INTERRUPT_CLIC) { + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + int mode = (env->exccode >> 10) & 0b11; + int enabled = riscv_cpu_local_irq_mode_enabled(env, mode); + if (enabled && env->exccode) { + cs->exception_index = RISCV_EXCP_INT_CLIC | env->exccode; + riscv_cpu_do_interrupt(cs); + return true; + } + } #endif return false; } @@ -103,6 +128,19 @@ uint32_t riscv_cpu_update_mip(RISCVCPU *cpu, uint32_t mask, uint32_t value) return old; } +/* iothread_mutex must be held */ +void riscv_cpu_clic_interrupt(RISCVCPU *cpu, int exccode) +{ + CPURISCVState *env = &cpu->env; + env->exccode = exccode; + if (exccode != -1) { + cpu_interrupt(CPU(cpu), CPU_INTERRUPT_CLIC); + } else { + cpu_reset_interrupt(CPU(cpu), CPU_INTERRUPT_CLIC); + } +} + + void riscv_cpu_set_mode(CPURISCVState *env, target_ulong newpriv) { if (newpriv > PRV_M) { @@ -452,11 +490,51 @@ int riscv_cpu_handle_mmu_fault(CPUState *cs, vaddr address, int size, return ret; } +#if !defined(CONFIG_USER_ONLY) +static target_ulong riscv_intr_pc(CPURISCVState *env, + target_ulong tvec, target_ulong tvt, + bool async, bool clic, int cause) +{ + int mode1 = tvec & 0b11, mode2 = tvec & 0b111111; + target_ulong vec_addr; + + if (!(async || clic)) { + return tvec & ~0b11; + } + + /* bits [1:0] encode mode; 0 = direct, 1 = vectored, 2 >= reserved */ + switch (mode1) { + case 0b00: + return tvec & ~0b11; + case 0b01: + return (tvec & ~0b11) + cause * 4; + default: + /* bits [5:0] encode extended modes currently used by the CLIC */ + switch (mode2) { + case 0b000010: /* CLIC standard mode */ + /* + * TODO - check selective vectoring bit if nvbits=1 + */ + return tvec & ~0b111111; + case 0b000011: /* CLIC vectored mode */ + vec_addr = (tvt & ~0b111111) + (TARGET_LONG_BITS/8) * cause; + switch(TARGET_LONG_BITS) { + case 32: + return ldl_phys(CPU(riscv_env_get_cpu(env))->as, vec_addr); + case 64: + return ldq_phys(CPU(riscv_env_get_cpu(env))->as, vec_addr); + default: + g_assert_not_reached(); + } + default: + g_assert_not_reached(); + } + } +} +#endif + /* * Handle Traps - * - * Adapted from Spike's processor_t::take_trap. - * */ void riscv_cpu_do_interrupt(CPUState *cs) { @@ -468,9 +546,12 @@ void riscv_cpu_do_interrupt(CPUState *cs) /* cs->exception is 32-bits wide unlike mcause which is XLEN-bits wide so we mask off the MSB and separate into trap type and cause */ bool async = !!(cs->exception_index & RISCV_EXCP_INT_FLAG); + bool clic = !!(cs->exception_index & RISCV_EXCP_INT_CLIC); target_ulong cause = cs->exception_index & RISCV_EXCP_INT_MASK; target_ulong deleg = async ? deleg = env->mideleg : env->medeleg; target_ulong tval = 0; + int mode, level; + const char *desc; static const int ecall_cause_map[] = { [PRV_U] = RISCV_EXCP_U_ECALL, @@ -479,7 +560,7 @@ void riscv_cpu_do_interrupt(CPUState *cs) [PRV_M] = RISCV_EXCP_M_ECALL }; - if (!async) { + if (!(async || clic)) { /* set tval to badaddr for traps with address information */ switch (cause) { case RISCV_EXCP_INST_ADDR_MIS: @@ -503,11 +584,36 @@ void riscv_cpu_do_interrupt(CPUState *cs) } } - trace_riscv_trap(env->mhartid, async, cause, env->pc, tval, cause < 16 ? - (async ? riscv_intr_names : riscv_excp_names)[cause] : "(unknown)"); + if (clic) { + mode = (cause >> 10) & 3; + level = (cause >> 12) & 15; + cause &= 0x3ff; + cause |= get_field(env->mstatus, MSTATUS_MPP) << 28; + switch (mode) { + case PRV_M: + cause |= get_field(env->mintstatus, MINTSTATUS_MIL) << 24; + cause |= get_field(env->mstatus, MSTATUS_MPIE) << 23; + env->mintstatus = set_field(env->mcause, MCAUSE_MPIL, + get_field(env->mintstatus, MINTSTATUS_MIL)); + env->mintstatus = set_field(env->mintstatus, MINTSTATUS_MIL, level); + break; + case PRV_S: + cause |= get_field(env->mintstatus, MINTSTATUS_SIL) << 24; + cause |= get_field(env->mstatus, MSTATUS_SPIE) << 23; + env->mintstatus = set_field(env->mcause, MCAUSE_MPIL, + get_field(env->mintstatus, MINTSTATUS_SIL)); + env->mintstatus = set_field(env->mintstatus, MINTSTATUS_SIL, level); + break; + } + } else { + mode = env->priv <= PRV_S && ((deleg >> cause) & 1) ? PRV_S : PRV_M; + } + + desc = clic ? "(clic-interrupt)" : (cause < 16) ? + (async ? riscv_intr_names : riscv_excp_names)[cause] : "(unknown)"; + trace_riscv_trap(env->mhartid, async, cause, env->pc, tval, desc); - if (env->priv <= PRV_S && - cause < TARGET_LONG_BITS && ((deleg >> cause) & 1)) { + if (mode == PRV_S) { /* handle the trap in S-mode */ target_ulong s = env->mstatus; s = set_field(s, MSTATUS_SPIE, env->priv_ver >= PRIV_VERSION_1_10_0 ? @@ -515,11 +621,10 @@ void riscv_cpu_do_interrupt(CPUState *cs) s = set_field(s, MSTATUS_SPP, env->priv); s = set_field(s, MSTATUS_SIE, 0); env->mstatus = s; - env->scause = cause | ~(((target_ulong)-1) >> async); + env->scause = cause | ~(((target_ulong)-1) >> (async | clic)); env->sepc = env->pc; env->sbadaddr = tval; - env->pc = (env->stvec >> 2 << 2) + - ((async && (env->stvec & 3) == 1) ? cause * 4 : 0); + env->pc = riscv_intr_pc(env, env->stvec, env->stvt, async, clic, cause); riscv_cpu_set_mode(env, PRV_S); } else { /* handle the trap in M-mode */ @@ -529,11 +634,10 @@ void riscv_cpu_do_interrupt(CPUState *cs) s = set_field(s, MSTATUS_MPP, env->priv); s = set_field(s, MSTATUS_MIE, 0); env->mstatus = s; - env->mcause = cause | ~(((target_ulong)-1) >> async); + env->mcause = cause | ~(((target_ulong)-1) >> (async | clic)); env->mepc = env->pc; env->mbadaddr = tval; - env->pc = (env->mtvec >> 2 << 2) + - ((async && (env->mtvec & 3) == 1) ? cause * 4 : 0); + env->pc = riscv_intr_pc(env, env->mtvec, env->mtvt, async, clic, cause); riscv_cpu_set_mode(env, PRV_M); } diff --git a/target/riscv/csr.c b/target/riscv/csr.c index 90f68663f3c..67381e765a4 100644 --- a/target/riscv/csr.c +++ b/target/riscv/csr.c @@ -84,6 +84,11 @@ static int pmp(CPURISCVState *env, int csrno) { return -!riscv_feature(env, RISCV_FEATURE_PMP); } + +static int clic(CPURISCVState *env, int csrno) +{ + return -!riscv_feature(env, RISCV_FEATURE_CLIC); +} #endif /* User Floating-Point CSRs */ @@ -435,11 +440,21 @@ static int read_mtvec(CPURISCVState *env, int csrno, target_ulong *val) static int write_mtvec(CPURISCVState *env, int csrno, target_ulong val) { + int mode1 = val & 0b11, mode2 = val & 0b111111; + /* bits [1:0] encode mode; 0 = direct, 1 = vectored, 2 >= reserved */ - if ((val & 3) < 2) { + if (mode1 < 0b10) { env->mtvec = val; } else { - qemu_log_mask(LOG_UNIMP, "CSR_MTVEC: reserved mode not supported\n"); + /* bits [5:0] encode extended modes currently used by the CLIC */ + switch (mode2) { + case 0b000010: /* CLIC standard mode */ + case 0b000011: /* CLIC vectored mode */ + env->mtvec = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "CSR_MTVEC: mode not supported\n"); + } } return 0; } @@ -462,6 +477,18 @@ static int write_mcounteren(CPURISCVState *env, int csrno, target_ulong val) return 0; } +static int read_mtvt(CPURISCVState *env, int csrno, target_ulong *val) +{ + *val = env->mtvt; + return 0; +} + +static int write_mtvt(CPURISCVState *env, int csrno, target_ulong val) +{ + env->mtvt = val & ~((1ULL << 6) - 1); + return 0; +} + static int read_mscounteren(CPURISCVState *env, int csrno, target_ulong *val) { if (env->priv_ver > PRIV_VERSION_1_09_1) { @@ -573,6 +600,72 @@ static int rmw_mip(CPURISCVState *env, int csrno, target_ulong *ret_value, return 0; } +static int rmw_mnxti(CPURISCVState *env, int csrno, target_ulong *ret_value, + target_ulong new_value, target_ulong write_mask) +{ + /* + * TODO - implement complete mnxti semantics + * + * the CLIC state is currenetly not accessible from target/riscv + * as cpu implementations can't include anything from include/hw + * so the CLIC state needs to be in a CPU accessible structure. + * + * Pseudocode for csrrsi rd, mnxti, uimm[4:0] in M mode. + * + * mstatus |= uimm[4:0]; // Performed regardless of interrupt readiness. + * if (clic.priv==M && clic.level > mcause.pil + * && (cliccfg.nvbits==0 || clicintcfg[clic.id][8-CLICINTBITS]==0)) + * { + * // The CLIC interrupt should be serviced before returning to the saved + * // context, unless it's a selectively hardware vectored interupt. + * minstatus.mil = clic.level; // Update hart's interrupt level. + * mcause.exccode = clic.id; // Update interrupt id. + * rd = TBASE + XLEN/8 * clic.id; // Return pointer to trap handler entry. + * } else { + * // No interrupt, or a selectively hardware vectored interrupt, + * // or in non-CLIC mode. + * rd = 0; + * } + */ + + /* tail chain implementation returns 0, causing slow path + * delivery of any remaining pending interrupts */ + env->mstatus |= (new_value & write_mask) & 0b11111; + if (ret_value) { + *ret_value = 0; + } + + return 0; +} + +static int read_mintstatus(CPURISCVState *env, int csrno, target_ulong *val) +{ + *val = env->mintstatus; + + return 0; +} + +static int rmw_mscratchcsw(CPURISCVState *env, int csrno, + target_ulong *ret_value, + target_ulong new_value, + target_ulong write_mask) +{ + target_ulong rs1 = (env->mscratch & ~write_mask) | (new_value & write_mask); + target_ulong mpp = get_field(env->mcause, MSTATUS_MPP); + + if (mpp != PRV_M) { + if (ret_value) { + *ret_value = env->mscratch; + } + env->mscratch = rs1; + } else if (ret_value) { + *ret_value = rs1; + } + + return 0; +} + + /* Supervisor Trap Setup */ static int read_sstatus(CPURISCVState *env, int csrno, target_ulong *val) @@ -611,11 +704,21 @@ static int read_stvec(CPURISCVState *env, int csrno, target_ulong *val) static int write_stvec(CPURISCVState *env, int csrno, target_ulong val) { + int mode1 = val & 0b11, mode2 = val & 0b111111; + /* bits [1:0] encode mode; 0 = direct, 1 = vectored, 2 >= reserved */ - if ((val & 3) < 2) { + if (mode1 < 0b10) { env->stvec = val; } else { - qemu_log_mask(LOG_UNIMP, "CSR_STVEC: reserved mode not supported\n"); + /* bits [5:0] encode extended modes currently used by the CLIC */ + switch (mode2) { + case 0b000010: /* CLIC standard mode */ + case 0b000011: /* CLIC vectored mode */ + env->stvec = val; + break; + default: + qemu_log_mask(LOG_UNIMP, "CSR_STVEC: mode not supported\n"); + } } return 0; } @@ -638,6 +741,19 @@ static int write_scounteren(CPURISCVState *env, int csrno, target_ulong val) return 0; } +static int read_stvt(CPURISCVState *env, int csrno, target_ulong *val) +{ + *val = env->stvt; + return 0; +} + +static int write_stvt(CPURISCVState *env, int csrno, target_ulong val) +{ + env->stvt = val & ~((1ULL << 6) - 1); + return 0; +} + + /* Supervisor Trap Handling */ static int read_sscratch(CPURISCVState *env, int csrno, target_ulong *val) @@ -695,6 +811,53 @@ static int rmw_sip(CPURISCVState *env, int csrno, target_ulong *ret_value, write_mask & env->mideleg); } +static int rmw_snxti(CPURISCVState *env, int csrno, target_ulong *ret_value, + target_ulong new_value, target_ulong write_mask) +{ + /* + * TODO - implement complete mnxti semantics + */ + + /* tail chain implementation returns 0, causing slow path + * delivery of any remaining pending interrupts */ + env->mstatus |= (new_value & write_mask) & 0b11111; + if (ret_value) { + *ret_value = 0; + } + + return 0; +} + +static int read_sintstatus(CPURISCVState *env, int csrno, target_ulong *val) +{ + target_ulong mask = SINTSTATUS_SIL | SINTSTATUS_UIL; + + *val = env->mintstatus & mask; + + return 0; +} + +static int rmw_sscratchcsw(CPURISCVState *env, int csrno, + target_ulong *ret_value, + target_ulong new_value, + target_ulong write_mask) +{ + target_ulong rs1 = (env->sscratch & ~write_mask) | (new_value & write_mask); + target_ulong mpp = get_field(env->mcause, MSTATUS_MPP); + + if (mpp != PRV_S) { + if (ret_value) { + *ret_value = env->sscratch; + } + env->sscratch = rs1; + } else if (ret_value) { + *ret_value = rs1; + } + + return 0; +} + + /* Supervisor Protection and Translation */ static int read_satp(CPURISCVState *env, int csrno, target_ulong *val) @@ -917,5 +1080,17 @@ static riscv_csr_operations csr_ops[CSR_TABLE_SIZE] = { [CSR_HPMCOUNTER3H ... CSR_HPMCOUNTER31H] = { ctr, read_zero }, [CSR_MHPMCOUNTER3H ... CSR_MHPMCOUNTER31H] = { any, read_zero }, #endif + + /* Machine Mode Core Level Interrupt Controller */ + [CSR_MTVT] = { clic, read_mtvt, write_mtvt, }, + [CSR_MNXTI] = { clic, NULL, NULL, rmw_mnxti }, + [CSR_MINTSTATUS] = { clic, read_mintstatus }, + [CSR_MSCRATCHCSW] = { clic, NULL, NULL, rmw_mscratchcsw }, + + /* Supervisor Mode Core Level Interrupt Controller */ + [CSR_STVT] = { clic, read_stvt, write_stvt, }, + [CSR_SNXTI] = { clic, NULL, NULL, rmw_snxti }, + [CSR_SINTSTATUS] = { clic, read_sintstatus }, + [CSR_SSCRATCHCSW] = { clic, NULL, NULL, rmw_sscratchcsw }, #endif }; diff --git a/target/riscv/op_helper.c b/target/riscv/op_helper.c index b7dc18a41e2..ec63fd3d9bd 100644 --- a/target/riscv/op_helper.c +++ b/target/riscv/op_helper.c @@ -87,6 +87,12 @@ target_ulong helper_sret(CPURISCVState *env, target_ulong cpu_pc_deb) riscv_raise_exception(env, RISCV_EXCP_ILLEGAL_INST, GETPC()); } + /* if CLIC mode, copy mcause.mpil into minstatus.mil */ + if ((env->stvec & 0b111110) == 0b000010) { + env->mintstatus = set_field(env->mintstatus, SINTSTATUS_SIL, + get_field(env->mcause, SCAUSE_SPIL)); + } + target_ulong mstatus = env->mstatus; target_ulong prev_priv = get_field(mstatus, MSTATUS_SPP); mstatus = set_field(mstatus, @@ -112,6 +118,12 @@ target_ulong helper_mret(CPURISCVState *env, target_ulong cpu_pc_deb) riscv_raise_exception(env, RISCV_EXCP_INST_ADDR_MIS, GETPC()); } + /* if CLIC mode, copy mcause.mpil into minstatus.mil */ + if ((env->mtvec & 0b111110) == 0b000010) { + env->mintstatus = set_field(env->mintstatus, MINTSTATUS_MIL, + get_field(env->mcause, MCAUSE_MPIL)); + } + target_ulong mstatus = env->mstatus; target_ulong prev_priv = get_field(mstatus, MSTATUS_MPP); mstatus = set_field(mstatus, diff --git a/target/riscv/trace-events b/target/riscv/trace-events index 48af0373df6..1be65d151c8 100644 --- a/target/riscv/trace-events +++ b/target/riscv/trace-events @@ -1,2 +1,2 @@ # target/riscv/cpu_helper.c -riscv_trap(uint64_t hartid, bool async, uint64_t cause, uint64_t epc, uint64_t tval, const char *desc) "hart:%"PRId64", async:%d, cause:%"PRId64", epc:0x%"PRIx64", tval:0x%"PRIx64", desc=%s" +riscv_trap(uint64_t hartid, bool async, uint64_t cause, uint64_t epc, uint64_t tval, const char *desc) "hart:%"PRId64", async:%d, cause:0x%"PRIx64", epc:0x%"PRIx64", tval:0x%"PRIx64", desc=%s"