diff options
| author | Richard Henderson <richard.henderson@linaro.org> | 2022-04-21 22:03:34 -0700 |
|---|---|---|
| committer | Richard Henderson <richard.henderson@linaro.org> | 2022-04-21 22:03:34 -0700 |
| commit | 10cd282ee44e4bc4a4b9751bccfcc597b4e7f830 (patch) | |
| tree | 2f644d3da0c593aee6eb7b73272920e02e87e967 /target/riscv/debug.c | |
| parent | a74782936dc6e979ce371dabda4b1c05624ea87f (diff) | |
| parent | faee5441a038898f64b335dbaecab102ba406552 (diff) | |
| download | focaccia-qemu-10cd282ee44e4bc4a4b9751bccfcc597b4e7f830.tar.gz focaccia-qemu-10cd282ee44e4bc4a4b9751bccfcc597b4e7f830.zip | |
Merge tag 'pull-riscv-to-apply-20220422-1' of github.com:alistair23/qemu into staging
First RISC-V PR for QEMU 7.1 * Add support for Ibex SPI to OpenTitan * Add support for privileged spec version 1.12.0 * Use privileged spec version 1.12.0 for virt machine by default * Allow software access to MIP SEIP * Add initial support for the Sdtrig extension * Optimisations for vector extensions * Improvements to the misa ISA string * Add isa extenstion strings to the device tree * Don't allow `-bios` options with KVM machines * Fix NAPOT range computation overflow * Fix DT property mmu-type when CPU mmu option is disabled * Make RISC-V ACLINT mtime MMIO register writable * Add and enable native debug feature * Support 64bit fdt address. # -----BEGIN PGP SIGNATURE----- # # iQEzBAABCAAdFiEE9sSsRtSTSGjTuM6PIeENKd+XcFQFAmJh+GQACgkQIeENKd+X # cFTKZQf/UQ8yb5DozdeNbm2pmfjJnEEsnXB6k95wIX9pjrJ3HkypHzoRpLbIDzET # KsPjRW6N5SLPINrYfgBuxUv0A/6jOG7cTC/Bimu16wPyS2zQopiTTgiJv6qLkO5G # QUBWz/6kaXNT+fQiTnXXqjViADO49FigYRWUmRfNabeUwb6YoQwoBY6B5jpwZlbI # B9qDdcKnYet5zwi1rGFedRC1XtP7ZDF1lylqNS2nnfr1ZvOWYkAJb5TJDi/4qUpz # i/wGRx/8KaYD5ehGe7Xd50sMM9lLlzNgOnZL0F5cRnA8e/3nRFjTeQ7RoSKGBdaS # 7J4RqA9YMhuPL2tTq95wof6EpVsSNw== # =yLIg # -----END PGP SIGNATURE----- # gpg: Signature made Thu 21 Apr 2022 05:35:48 PM PDT # gpg: using RSA key F6C4AC46D4934868D3B8CE8F21E10D29DF977054 # gpg: Good signature from "Alistair Francis <alistair@alistair23.me>" [undefined] # gpg: WARNING: This key is not certified with a trusted signature! # gpg: There is no indication that the signature belongs to the owner. # Primary key fingerprint: F6C4 AC46 D493 4868 D3B8 CE8F 21E1 0D29 DF97 7054 * tag 'pull-riscv-to-apply-20220422-1' of github.com:alistair23/qemu: (31 commits) hw/riscv: boot: Support 64bit fdt address. hw/core: tcg-cpu-ops.h: Update comments of debug_check_watchpoint() target/riscv: cpu: Enable native debug feature target/riscv: machine: Add debug state description target/riscv: csr: Hook debug CSR read/write target/riscv: cpu: Add a config option for native debug target/riscv: debug: Implement debug related TCGCPUOps hw/intc: riscv_aclint: Add reset function of ACLINT devices hw/intc: Make RISC-V ACLINT mtime MMIO register writable hw/intc: Support 32/64-bit mtimecmp and mtime accesses in RISC-V ACLINT hw/intc: Add .impl.[min|max]_access_size declaration in RISC-V ACLINT hw/riscv: virt: fix DT property mmu-type when CPU mmu option is disabled target/riscv/pmp: fix NAPOT range computation overflow hw/riscv: virt: Exit if the user provided -bios in combination with KVM target/riscv: Use cpu_loop_exit_restore directly from mmu faults target/riscv: fix start byte for vmv<nf>r.v when vstart != 0 target/riscv: Add isa extenstion strings to the device tree target/riscv: misa to ISA string conversion fix target/riscv: optimize helper for vmv<nr>r.v target/riscv: optimize condition assign for scale < 0 ... Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Diffstat (limited to 'target/riscv/debug.c')
| -rw-r--r-- | target/riscv/debug.c | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/target/riscv/debug.c b/target/riscv/debug.c new file mode 100644 index 0000000000..2f2a51c732 --- /dev/null +++ b/target/riscv/debug.c @@ -0,0 +1,441 @@ +/* + * QEMU RISC-V Native Debug Support + * + * Copyright (c) 2022 Wind River Systems, Inc. + * + * Author: + * Bin Meng <bin.meng@windriver.com> + * + * This provides the native debug support via the Trigger Module, as defined + * in the RISC-V Debug Specification: + * https://github.com/riscv/riscv-debug-spec/raw/master/riscv-debug-stable.pdf + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2 or later, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include "qemu/osdep.h" +#include "qemu/log.h" +#include "qapi/error.h" +#include "cpu.h" +#include "trace.h" +#include "exec/exec-all.h" + +/* + * The following M-mode trigger CSRs are implemented: + * + * - tselect + * - tdata1 + * - tdata2 + * - tdata3 + * + * We don't support writable 'type' field in the tdata1 register, so there is + * no need to implement the "tinfo" CSR. + * + * The following triggers are implemented: + * + * Index | Type | tdata mapping | Description + * ------+------+------------------------+------------ + * 0 | 2 | tdata1, tdata2 | Address / Data Match + * 1 | 2 | tdata1, tdata2 | Address / Data Match + */ + +/* tdata availability of a trigger */ +typedef bool tdata_avail[TDATA_NUM]; + +static tdata_avail tdata_mapping[TRIGGER_NUM] = { + [TRIGGER_TYPE2_IDX_0 ... TRIGGER_TYPE2_IDX_1] = { true, true, false }, +}; + +/* only breakpoint size 1/2/4/8 supported */ +static int access_size[SIZE_NUM] = { + [SIZE_ANY] = 0, + [SIZE_1B] = 1, + [SIZE_2B] = 2, + [SIZE_4B] = 4, + [SIZE_6B] = -1, + [SIZE_8B] = 8, + [6 ... 15] = -1, +}; + +static inline target_ulong trigger_type(CPURISCVState *env, + trigger_type_t type) +{ + target_ulong tdata1; + + switch (riscv_cpu_mxl(env)) { + case MXL_RV32: + tdata1 = RV32_TYPE(type); + break; + case MXL_RV64: + tdata1 = RV64_TYPE(type); + break; + default: + g_assert_not_reached(); + } + + return tdata1; +} + +bool tdata_available(CPURISCVState *env, int tdata_index) +{ + if (unlikely(tdata_index >= TDATA_NUM)) { + return false; + } + + if (unlikely(env->trigger_cur >= TRIGGER_NUM)) { + return false; + } + + return tdata_mapping[env->trigger_cur][tdata_index]; +} + +target_ulong tselect_csr_read(CPURISCVState *env) +{ + return env->trigger_cur; +} + +void tselect_csr_write(CPURISCVState *env, target_ulong val) +{ + /* all target_ulong bits of tselect are implemented */ + env->trigger_cur = val; +} + +static target_ulong tdata1_validate(CPURISCVState *env, target_ulong val, + trigger_type_t t) +{ + uint32_t type, dmode; + target_ulong tdata1; + + switch (riscv_cpu_mxl(env)) { + case MXL_RV32: + type = extract32(val, 28, 4); + dmode = extract32(val, 27, 1); + tdata1 = RV32_TYPE(t); + break; + case MXL_RV64: + type = extract64(val, 60, 4); + dmode = extract64(val, 59, 1); + tdata1 = RV64_TYPE(t); + break; + default: + g_assert_not_reached(); + } + + if (type != t) { + qemu_log_mask(LOG_GUEST_ERROR, + "ignoring type write to tdata1 register\n"); + } + if (dmode != 0) { + qemu_log_mask(LOG_UNIMP, "debug mode is not supported\n"); + } + + return tdata1; +} + +static inline void warn_always_zero_bit(target_ulong val, target_ulong mask, + const char *msg) +{ + if (val & mask) { + qemu_log_mask(LOG_UNIMP, "%s bit is always zero\n", msg); + } +} + +static uint32_t type2_breakpoint_size(CPURISCVState *env, target_ulong ctrl) +{ + uint32_t size, sizelo, sizehi = 0; + + if (riscv_cpu_mxl(env) == MXL_RV64) { + sizehi = extract32(ctrl, 21, 2); + } + sizelo = extract32(ctrl, 16, 2); + size = (sizehi << 2) | sizelo; + + return size; +} + +static inline bool type2_breakpoint_enabled(target_ulong ctrl) +{ + bool mode = !!(ctrl & (TYPE2_U | TYPE2_S | TYPE2_M)); + bool rwx = !!(ctrl & (TYPE2_LOAD | TYPE2_STORE | TYPE2_EXEC)); + + return mode && rwx; +} + +static target_ulong type2_mcontrol_validate(CPURISCVState *env, + target_ulong ctrl) +{ + target_ulong val; + uint32_t size; + + /* validate the generic part first */ + val = tdata1_validate(env, ctrl, TRIGGER_TYPE_AD_MATCH); + + /* validate unimplemented (always zero) bits */ + warn_always_zero_bit(ctrl, TYPE2_MATCH, "match"); + warn_always_zero_bit(ctrl, TYPE2_CHAIN, "chain"); + warn_always_zero_bit(ctrl, TYPE2_ACTION, "action"); + warn_always_zero_bit(ctrl, TYPE2_TIMING, "timing"); + warn_always_zero_bit(ctrl, TYPE2_SELECT, "select"); + warn_always_zero_bit(ctrl, TYPE2_HIT, "hit"); + + /* validate size encoding */ + size = type2_breakpoint_size(env, ctrl); + if (access_size[size] == -1) { + qemu_log_mask(LOG_UNIMP, "access size %d is not supported, using SIZE_ANY\n", + size); + } else { + val |= (ctrl & TYPE2_SIZELO); + if (riscv_cpu_mxl(env) == MXL_RV64) { + val |= (ctrl & TYPE2_SIZEHI); + } + } + + /* keep the mode and attribute bits */ + val |= (ctrl & (TYPE2_U | TYPE2_S | TYPE2_M | + TYPE2_LOAD | TYPE2_STORE | TYPE2_EXEC)); + + return val; +} + +static void type2_breakpoint_insert(CPURISCVState *env, target_ulong index) +{ + target_ulong ctrl = env->type2_trig[index].mcontrol; + target_ulong addr = env->type2_trig[index].maddress; + bool enabled = type2_breakpoint_enabled(ctrl); + CPUState *cs = env_cpu(env); + int flags = BP_CPU | BP_STOP_BEFORE_ACCESS; + uint32_t size; + + if (!enabled) { + return; + } + + if (ctrl & TYPE2_EXEC) { + cpu_breakpoint_insert(cs, addr, flags, &env->type2_trig[index].bp); + } + + if (ctrl & TYPE2_LOAD) { + flags |= BP_MEM_READ; + } + if (ctrl & TYPE2_STORE) { + flags |= BP_MEM_WRITE; + } + + if (flags & BP_MEM_ACCESS) { + size = type2_breakpoint_size(env, ctrl); + if (size != 0) { + cpu_watchpoint_insert(cs, addr, size, flags, + &env->type2_trig[index].wp); + } else { + cpu_watchpoint_insert(cs, addr, 8, flags, + &env->type2_trig[index].wp); + } + } +} + +static void type2_breakpoint_remove(CPURISCVState *env, target_ulong index) +{ + CPUState *cs = env_cpu(env); + + if (env->type2_trig[index].bp) { + cpu_breakpoint_remove_by_ref(cs, env->type2_trig[index].bp); + env->type2_trig[index].bp = NULL; + } + + if (env->type2_trig[index].wp) { + cpu_watchpoint_remove_by_ref(cs, env->type2_trig[index].wp); + env->type2_trig[index].wp = NULL; + } +} + +static target_ulong type2_reg_read(CPURISCVState *env, + target_ulong trigger_index, int tdata_index) +{ + uint32_t index = trigger_index - TRIGGER_TYPE2_IDX_0; + target_ulong tdata; + + switch (tdata_index) { + case TDATA1: + tdata = env->type2_trig[index].mcontrol; + break; + case TDATA2: + tdata = env->type2_trig[index].maddress; + break; + default: + g_assert_not_reached(); + } + + return tdata; +} + +static void type2_reg_write(CPURISCVState *env, target_ulong trigger_index, + int tdata_index, target_ulong val) +{ + uint32_t index = trigger_index - TRIGGER_TYPE2_IDX_0; + target_ulong new_val; + + switch (tdata_index) { + case TDATA1: + new_val = type2_mcontrol_validate(env, val); + if (new_val != env->type2_trig[index].mcontrol) { + env->type2_trig[index].mcontrol = new_val; + type2_breakpoint_remove(env, index); + type2_breakpoint_insert(env, index); + } + break; + case TDATA2: + if (val != env->type2_trig[index].maddress) { + env->type2_trig[index].maddress = val; + type2_breakpoint_remove(env, index); + type2_breakpoint_insert(env, index); + } + break; + default: + g_assert_not_reached(); + } + + return; +} + +typedef target_ulong (*tdata_read_func)(CPURISCVState *env, + target_ulong trigger_index, + int tdata_index); + +static tdata_read_func trigger_read_funcs[TRIGGER_NUM] = { + [TRIGGER_TYPE2_IDX_0 ... TRIGGER_TYPE2_IDX_1] = type2_reg_read, +}; + +typedef void (*tdata_write_func)(CPURISCVState *env, + target_ulong trigger_index, + int tdata_index, + target_ulong val); + +static tdata_write_func trigger_write_funcs[TRIGGER_NUM] = { + [TRIGGER_TYPE2_IDX_0 ... TRIGGER_TYPE2_IDX_1] = type2_reg_write, +}; + +target_ulong tdata_csr_read(CPURISCVState *env, int tdata_index) +{ + tdata_read_func read_func = trigger_read_funcs[env->trigger_cur]; + + return read_func(env, env->trigger_cur, tdata_index); +} + +void tdata_csr_write(CPURISCVState *env, int tdata_index, target_ulong val) +{ + tdata_write_func write_func = trigger_write_funcs[env->trigger_cur]; + + return write_func(env, env->trigger_cur, tdata_index, val); +} + +void riscv_cpu_debug_excp_handler(CPUState *cs) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + + if (cs->watchpoint_hit) { + if (cs->watchpoint_hit->flags & BP_CPU) { + cs->watchpoint_hit = NULL; + riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0); + } + } else { + if (cpu_breakpoint_test(cs, env->pc, BP_CPU)) { + riscv_raise_exception(env, RISCV_EXCP_BREAKPOINT, 0); + } + } +} + +bool riscv_cpu_debug_check_breakpoint(CPUState *cs) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + CPUBreakpoint *bp; + target_ulong ctrl; + target_ulong pc; + int i; + + QTAILQ_FOREACH(bp, &cs->breakpoints, entry) { + for (i = 0; i < TRIGGER_TYPE2_NUM; i++) { + ctrl = env->type2_trig[i].mcontrol; + pc = env->type2_trig[i].maddress; + + if ((ctrl & TYPE2_EXEC) && (bp->pc == pc)) { + /* check U/S/M bit against current privilege level */ + if ((ctrl >> 3) & BIT(env->priv)) { + return true; + } + } + } + } + + return false; +} + +bool riscv_cpu_debug_check_watchpoint(CPUState *cs, CPUWatchpoint *wp) +{ + RISCVCPU *cpu = RISCV_CPU(cs); + CPURISCVState *env = &cpu->env; + target_ulong ctrl; + target_ulong addr; + int flags; + int i; + + for (i = 0; i < TRIGGER_TYPE2_NUM; i++) { + ctrl = env->type2_trig[i].mcontrol; + addr = env->type2_trig[i].maddress; + flags = 0; + + if (ctrl & TYPE2_LOAD) { + flags |= BP_MEM_READ; + } + if (ctrl & TYPE2_STORE) { + flags |= BP_MEM_WRITE; + } + + if ((wp->flags & flags) && (wp->vaddr == addr)) { + /* check U/S/M bit against current privilege level */ + if ((ctrl >> 3) & BIT(env->priv)) { + return true; + } + } + } + + return false; +} + +void riscv_trigger_init(CPURISCVState *env) +{ + target_ulong type2 = trigger_type(env, TRIGGER_TYPE_AD_MATCH); + int i; + + /* type 2 triggers */ + for (i = 0; i < TRIGGER_TYPE2_NUM; i++) { + /* + * type = TRIGGER_TYPE_AD_MATCH + * dmode = 0 (both debug and M-mode can write tdata) + * maskmax = 0 (unimplemented, always 0) + * sizehi = 0 (match against any size, RV64 only) + * hit = 0 (unimplemented, always 0) + * select = 0 (always 0, perform match on address) + * timing = 0 (always 0, trigger before instruction) + * sizelo = 0 (match against any size) + * action = 0 (always 0, raise a breakpoint exception) + * chain = 0 (unimplemented, always 0) + * match = 0 (always 0, when any compare value equals tdata2) + */ + env->type2_trig[i].mcontrol = type2; + env->type2_trig[i].maddress = 0; + env->type2_trig[i].bp = NULL; + env->type2_trig[i].wp = NULL; + } +} |