diff --git a/components/freertos/port/riscv/port.c b/components/freertos/port/riscv/port.c index 886d0a6a98..6918fd5c91 100644 --- a/components/freertos/port/riscv/port.c +++ b/components/freertos/port/riscv/port.c @@ -180,6 +180,9 @@ __attribute__((naked)) static void prvTaskExitError(void) ".option norvc\n" \ "nop\n" \ ".option pop"); + /* Task entry's RA will point here. Shifting RA into prvTaskExitError is necessary + to make GDB backtrace ending inside that function. + Otherwise backtrace will end in the function laying just before prvTaskExitError in address space. */ _prvTaskExitError(); } @@ -293,7 +296,7 @@ StackType_t *pxPortInitialiseStack(StackType_t *pxTopOfStack, TaskFunction_t pxC memset(frame, 0, sizeof(*frame)); /* Shifting RA into prvTaskExitError is necessary to make GDB backtrace ending inside that function. Otherwise backtrace will end in the function laying just before prvTaskExitError in address space. */ - frame->ra = (UBaseType_t)prvTaskExitError + 4/*nop size*/; + frame->ra = (UBaseType_t)prvTaskExitError + 4/*size of the nop insruction at the beginning of prvTaskExitError*/; frame->mepc = (UBaseType_t)pxCode; frame->a0 = (UBaseType_t)pvParameters; frame->gp = (UBaseType_t)&__global_pointer$; diff --git a/components/hal/esp32c3/include/hal/cpu_ll.h b/components/hal/esp32c3/include/hal/cpu_ll.h index 4e9d449409..0ac34b16e6 100644 --- a/components/hal/esp32c3/include/hal/cpu_ll.h +++ b/components/hal/esp32c3/include/hal/cpu_ll.h @@ -20,6 +20,7 @@ #include "soc/assist_debug_reg.h" #include "esp_attr.h" #include "riscv/csr.h" +#include "riscv/semihosting.h" /*performance counter*/ #define CSR_PCER_MACHINE 0x7e0 @@ -72,8 +73,29 @@ static inline void cpu_ll_init_hwloop(void) // Nothing needed here for ESP32-C3 } +static inline bool cpu_ll_is_debugger_attached(void) +{ + return REG_GET_BIT(ASSIST_DEBUG_C0RE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE); +} + static inline void cpu_ll_set_breakpoint(int id, uint32_t pc) { + if (cpu_ll_is_debugger_attached()) { + /* If we want to set breakpoint which when hit transfers control to debugger + * we need to set `action` in `mcontrol` to 1 (Enter Debug Mode). + * That `action` value is supported only when `dmode` of `tdata1` is set. + * But `dmode` can be modified by debugger only (from Debug Mode). + * + * So when debugger is connected we use special syscall to ask it to set breakpoint for us. + */ + long args[] = {true, id, (long)pc}; + int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args); + if (ret == 0) { + return; + } + } + /* The code bellow sets breakpoint which will trigger `Breakpoint` exception + * instead transfering control to debugger. */ RV_WRITE_CSR(tselect,id); RV_SET_CSR(CSR_TCONTROL,TCONTROL_MTE); RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE); @@ -83,6 +105,14 @@ static inline void cpu_ll_set_breakpoint(int id, uint32_t pc) static inline void cpu_ll_clear_breakpoint(int id) { + if (cpu_ll_is_debugger_attached()) { + /* see description in cpu_ll_set_breakpoint() */ + long args[] = {false, id}; + int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_BREAKPOINT_SET, args); + if (ret == 0){ + return; + } + } RV_WRITE_CSR(tselect,id); RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE); RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE|TDATA1_EXECUTE); @@ -106,6 +136,17 @@ static inline void cpu_ll_set_watchpoint(int id, bool on_write) { uint32_t addr_napot; + + if (cpu_ll_is_debugger_attached()) { + /* see description in cpu_ll_set_breakpoint() */ + long args[] = {true, id, (long)addr, (long)size, + (long)((on_read ? ESP_SEMIHOSTING_WP_FLG_RD : 0) | (on_write ? ESP_SEMIHOSTING_WP_FLG_WR : 0))}; + int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args); + if (ret == 0) { + return; + } + } + RV_WRITE_CSR(tselect,id); RV_SET_CSR(CSR_TCONTROL, TCONTROL_MPTE | TCONTROL_MTE); RV_SET_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE); @@ -124,6 +165,14 @@ static inline void cpu_ll_set_watchpoint(int id, static inline void cpu_ll_clear_watchpoint(int id) { + if (cpu_ll_is_debugger_attached()) { + /* see description in cpu_ll_set_breakpoint() */ + long args[] = {false, id}; + int ret = semihosting_call_noerrno(ESP_SEMIHOSTING_SYS_WATCHPOINT_SET, args); + if (ret == 0){ + return; + } + } RV_WRITE_CSR(tselect,id); RV_CLEAR_CSR(CSR_TCONTROL,TCONTROL_MTE); RV_CLEAR_CSR(CSR_TDATA1, TDATA1_USER|TDATA1_MACHINE); @@ -133,11 +182,6 @@ static inline void cpu_ll_clear_watchpoint(int id) return; } -FORCE_INLINE_ATTR bool cpu_ll_is_debugger_attached(void) -{ - return REG_GET_BIT(ASSIST_DEBUG_C0RE_0_DEBUG_MODE_REG, ASSIST_DEBUG_CORE_0_DEBUG_MODULE_ACTIVE); -} - static inline void cpu_ll_break(void) { asm volatile("ebreak\n"); diff --git a/components/riscv/include/riscv/semihosting.h b/components/riscv/include/riscv/semihosting.h new file mode 100644 index 0000000000..17eb90785b --- /dev/null +++ b/components/riscv/include/riscv/semihosting.h @@ -0,0 +1,99 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + + +/* ESP custom semihosting calls numbers */ + +/** + * @brief Set/clear breakpoint + * + * @param set if true set breakpoint, otherwise clear it + * @param id breakpoint ID + * @param addr address to set breakpoint at. Ignored if `set` is false. + * @return return 0 on sucess or non-zero error code + */ +#define ESP_SEMIHOSTING_SYS_BREAKPOINT_SET 0x66 + +/** + * @brief Set/clear watchpoint + * + * @param set if true set watchpoint, otherwise clear it + * @param id watchpoint ID + * @param addr address to set watchpoint at. Ignored if `set` is false. + * @param size size of watchpoint. Ignored if `set` is false. + * @param flags watchpoint flags, see description below. Ignored if `set` is false. + * @return return 0 on sucess or non-zero error code + */ +#define ESP_SEMIHOSTING_SYS_WATCHPOINT_SET 0x67 + +/* bit values for `flags` argument of ESP_SEMIHOSTING_SYS_WATCHPOINT_SET call. Can be ORed. */ +/* watch for 'reads' at `addr` */ +#define ESP_SEMIHOSTING_WP_FLG_RD (1UL << 0) +/* watch for 'writes' at `addr` */ +#define ESP_SEMIHOSTING_WP_FLG_WR (1UL << 1) + +/** + * @brief Perform semihosting call + * + * See https://github.com/riscv/riscv-semihosting-spec/ and the linked + * ARM semihosting spec for details. + * + * @param id semihosting call number + * @param data data block to pass to the host; number of items and their + * meaning depends on the semihosting call. See the spec for + * details. + * + * @return return value from the host + */ +static inline long semihosting_call_noerrno(long id, long *data) +{ + register long a0 asm ("a0") = id; + register long a1 asm ("a1") = (long) data; + __asm__ __volatile__ ( + ".option push\n" + ".option norvc\n" + "slli zero, zero, 0x1f\n" + "ebreak\n" + "srai zero, zero, 0x7\n" + ".option pop\n" + : "+r"(a0) : "r"(a1) : "memory"); + return a0; +} + +/** + * @brief Perform semihosting call and retrieve errno + * + * @param id semihosting call number + * @param data data block to pass to the host; number of items and their + * meaning depends on the semihosting call. See the spec for + * details. + * @param[out] out_errno output, errno value from the host. Only set if + * the return value is negative. + * @return return value from the host + */ +static inline long semihosting_call(long id, long *data, int *out_errno) +{ + long ret = semihosting_call_noerrno(id, data); + if (ret < 0) { + /* Constant also defined in openocd_semihosting.h, + * which is common for RISC-V and Xtensa; it is not included here + * to avoid a circular dependency. + */ + const int semihosting_sys_errno = 0x13; + *out_errno = (int) semihosting_call_noerrno(semihosting_sys_errno, NULL); + } + return ret; +} + +#ifdef __cplusplus +} +#endif