From ad532236ae00c07f9cdfc53b5217edb23fe97730 Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Wed, 6 Jan 2021 00:26:07 +0100 Subject: [PATCH 1/2] vfs: add support for semihosting on ESP32-C3 --- components/app_trace/port/riscv/port.c | 5 +- .../esp_system/port/arch/riscv/debug_stubs.c | 10 +- components/hal/esp32c3/include/hal/cpu_ll.h | 30 +-- components/hal/esp32h2/include/hal/cpu_ll.h | 30 +-- components/hal/include/hal/cpu_hal.h | 7 +- components/riscv/include/riscv/semihosting.h | 68 +++++ components/vfs/openocd_semihosting.h | 150 +++++++++++ components/vfs/vfs_semihost.c | 233 +++++++----------- .../xtensa/include/xtensa/semihosting.h | 60 +++++ examples/storage/semihost_vfs/README.md | 126 ++++++---- .../main/semihost_vfs_example_main.c | 2 +- 11 files changed, 463 insertions(+), 258 deletions(-) create mode 100644 components/riscv/include/riscv/semihosting.h create mode 100644 components/vfs/openocd_semihosting.h create mode 100644 components/xtensa/include/xtensa/semihosting.h diff --git a/components/app_trace/port/riscv/port.c b/components/app_trace/port/riscv/port.c index 7986e81508..78887124f2 100644 --- a/components/app_trace/port/riscv/port.c +++ b/components/app_trace/port/riscv/port.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -7,6 +7,7 @@ #include "esp_log.h" #include "esp_app_trace_membufs_proto.h" #include "esp_app_trace_port.h" +#include "riscv/semihosting.h" /** RISCV HW transport data */ typedef struct { @@ -103,7 +104,7 @@ __attribute__((weak)) int esp_apptrace_advertise_ctrl_block(void *ctrl_block_add if (!esp_cpu_in_ocd_debug_mode()) { return 0; } - return cpu_hal_syscall(RISCV_APPTRACE_SYSNR, (int)ctrl_block_addr, 0, 0, 0, NULL); + return (int) semihosting_call_noerrno(RISCV_APPTRACE_SYSNR, (long*)ctrl_block_addr); } /* Returns up buffers config. diff --git a/components/esp_system/port/arch/riscv/debug_stubs.c b/components/esp_system/port/arch/riscv/debug_stubs.c index a8b0ddd0b2..e4ba4f66c0 100644 --- a/components/esp_system/port/arch/riscv/debug_stubs.c +++ b/components/esp_system/port/arch/riscv/debug_stubs.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -9,9 +9,9 @@ // #include "esp_cpu.h" -#include "hal/cpu_hal.h" - #include "esp_log.h" +#include "riscv/semihosting.h" + const static char *TAG = "esp_dbg_stubs"; #define RISCV_DBG_STUBS_SYSNR 0x65 @@ -22,13 +22,13 @@ static int esp_dbg_stubs_advertise_table(void *stub_table_addr) if (!esp_cpu_in_ocd_debug_mode()) { return 0; } - return cpu_hal_syscall(RISCV_DBG_STUBS_SYSNR, (int)stub_table_addr, 0, 0, 0, NULL); + return (int) semihosting_call_noerrno(RISCV_DBG_STUBS_SYSNR, (long*)stub_table_addr); } void esp_dbg_stubs_ll_init(void *stub_table_addr) { // notify host about control block address int res = esp_dbg_stubs_advertise_table(stub_table_addr); - assert(res == 0 && "Falied to send debug stubs table address to host!"); + assert(res == 0 && "Failed to send debug stubs table address to host!"); ESP_LOGV(TAG, "%s stubs %x", __func__, stub_table_addr); } diff --git a/components/hal/esp32c3/include/hal/cpu_ll.h b/components/hal/esp32c3/include/hal/cpu_ll.h index bce6757911..8e9d5cdc01 100644 --- a/components/hal/esp32c3/include/hal/cpu_ll.h +++ b/components/hal/esp32c3/include/hal/cpu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -143,34 +143,6 @@ static inline void cpu_ll_break(void) return; } -static inline int cpu_ll_syscall(int sys_nr, int arg1, int arg2, int arg3, int arg4, int* ret_errno) -{ - int host_ret, host_errno; - - asm volatile ( \ - ".option push\n" \ - ".option norvc\n" \ - "mv a0, %[sys_nr]\n" \ - "mv a1, %[arg1]\n" \ - "mv a2, %[arg2]\n" \ - "mv a3, %[arg3]\n" \ - "mv a4, %[arg4]\n" \ - "slli zero,zero,0x1f\n" \ - "ebreak\n" \ - "srai zero,zero,0x7\n" \ - "mv %[host_ret], a0\n" \ - "mv %[host_errno], a1\n" \ - ".option pop\n" \ - :[host_ret]"=r"(host_ret),[host_errno]"=r"(host_errno) - :[sys_nr]"r"(sys_nr),[arg1]"r"(arg1),[arg2]"r"(arg2),[arg3]"r"(arg3),[arg4]"r"(arg4) - :"a0","a1","a2","a3","a4"); - - if (ret_errno) { - *ret_errno = host_errno; - } - return host_ret; -} - static inline void cpu_ll_set_vecbase(const void* vecbase) { uintptr_t vecbase_int = (uintptr_t)vecbase; diff --git a/components/hal/esp32h2/include/hal/cpu_ll.h b/components/hal/esp32h2/include/hal/cpu_ll.h index b3e1d88721..4acc529c06 100644 --- a/components/hal/esp32h2/include/hal/cpu_ll.h +++ b/components/hal/esp32h2/include/hal/cpu_ll.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -141,34 +141,6 @@ static inline void cpu_ll_break(void) return; } -static inline int cpu_ll_syscall(int sys_nr, int arg1, int arg2, int arg3, int arg4, int* ret_errno) -{ - int host_ret, host_errno; - - asm volatile ( \ - ".option push\n" \ - ".option norvc\n" \ - "mv a0, %[sys_nr]\n" \ - "mv a1, %[arg1]\n" \ - "mv a2, %[arg2]\n" \ - "mv a3, %[arg3]\n" \ - "mv a4, %[arg4]\n" \ - "slli zero,zero,0x1f\n" \ - "ebreak\n" \ - "srai zero,zero,0x7\n" \ - "mv %[host_ret], a0\n" \ - "mv %[host_errno], a1\n" \ - ".option pop\n" \ - :[host_ret]"=r"(host_ret),[host_errno]"=r"(host_errno) - :[sys_nr]"r"(sys_nr),[arg1]"r"(arg1),[arg2]"r"(arg2),[arg3]"r"(arg3),[arg4]"r"(arg4) - :"a0","a1","a2","a3","a4"); - - if (ret_errno) { - *ret_errno = host_errno; - } - return host_ret; -} - static inline void cpu_ll_set_vecbase(const void* vecbase) { uintptr_t vecbase_int = (uintptr_t)vecbase; diff --git a/components/hal/include/hal/cpu_hal.h b/components/hal/include/hal/cpu_hal.h index 824095871d..0e7e6f53a7 100644 --- a/components/hal/include/hal/cpu_hal.h +++ b/components/hal/include/hal/cpu_hal.h @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ @@ -70,11 +70,6 @@ extern "C" { */ #define cpu_hal_waiti() cpu_ll_waiti() -/** - * Trigger a syscall. - */ -#define cpu_hal_syscall(sys_nr, arg1, arg2, arg3, arg4, ret_errno) cpu_ll_syscall(sys_nr, arg1, arg2, arg3, arg4, ret_errno) - #if SOC_CPU_BREAKPOINTS_NUM > 0 /** diff --git a/components/riscv/include/riscv/semihosting.h b/components/riscv/include/riscv/semihosting.h new file mode 100644 index 0000000000..3539a3cd68 --- /dev/null +++ b/components/riscv/include/riscv/semihosting.h @@ -0,0 +1,68 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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 diff --git a/components/vfs/openocd_semihosting.h b/components/vfs/openocd_semihosting.h new file mode 100644 index 0000000000..fe829210e8 --- /dev/null +++ b/components/vfs/openocd_semihosting.h @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#include +#include + +#ifdef __XTENSA__ +#include "xtensa/semihosting.h" +#elif __riscv +#include "riscv/semihosting.h" +#else +#error Unsupported architecture +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Semihosting call numbers and functions for OpenOCD. + * In OpenOCD, ARM semihosting call numbers and parameters are used for + * RISC-V and Xtensa targets. + * + * These conventions are not compatible with Xtensa ISS and QEMU for Xtensa, + * which the actual Xtensa-specific semihosting call numbers and formats; + * these are not supported in ESP-IDF yet. + */ + +#define SEMIHOSTING_SYS_CLOCK 0x10 +#define SEMIHOSTING_SYS_CLOSE 0x02 +#define SEMIHOSTING_SYS_ERRNO 0x13 +#define SEMIHOSTING_SYS_EXIT 0x18 +#define SEMIHOSTING_SYS_EXIT_EXTENDED 0x20 +#define SEMIHOSTING_SYS_FLEN 0x0C +#define SEMIHOSTING_SYS_GET_CMDLINE 0x15 +#define SEMIHOSTING_SYS_HEAPINFO 0x16 +#define SEMIHOSTING_SYS_ISERROR 0x08 +#define SEMIHOSTING_SYS_ISTTY 0x09 +#define SEMIHOSTING_SYS_OPEN 0x01 +#define SEMIHOSTING_SYS_READ 0x06 +#define SEMIHOSTING_SYS_READC 0x07 +#define SEMIHOSTING_SYS_REMOVE 0x0E +#define SEMIHOSTING_SYS_RENAME 0x0F +#define SEMIHOSTING_SYS_SEEK 0x0A +#define SEMIHOSTING_SYS_SYSTEM 0x12 +#define SEMIHOSTING_SYS_TIME 0x11 +#define SEMIHOSTING_SYS_WRITE 0x05 +#define SEMIHOSTING_SYS_WRITEC 0x03 +#define SEMIHOSTING_SYS_WRITE0 0x04 + +/* This call is an Espressif OpenOCD extension to send the version + * information to the host. This lets the host support different IDF versions, + * allowing semihosting interface to be modified over time. + * + * The parameters of this call are: + * - pointer to the version info structure, + * - size of the version info structure. + * + * At present, the structure should contain a single word, indicating + * the semihosting interface version used by the target. + * + * If the syscall is recognized, the return value is zero. + */ +#define SEMIHOSTING_SYS_DRVINFO 0xE0 + + +static inline int semihosting_open(const char *path, int open_mode, int mode) +{ + int host_errno = 0; + long args[] = {(long) path, open_mode, strlen(path), 0}; + (void) mode; // unused in OpenOCD + int result = (int) semihosting_call(SEMIHOSTING_SYS_OPEN, args, &host_errno); + if (result < 0) { + errno = host_errno; + } + return result; +} + +static inline ssize_t semihosting_write(int fd, const void *data, size_t size) +{ + int host_errno = 0; + long args[] = {fd, (long) data, size, 0}; + ssize_t ret = (ssize_t) semihosting_call(SEMIHOSTING_SYS_WRITE, args, &host_errno); + if (ret < 0) { + errno = host_errno; + return ret; + } + /* On success, write syscall returns the number of bytes NOT written, + * adjust the return value to match POSIX. + */ + return size - (ssize_t)ret; +} + +static inline ssize_t semihosting_read(int fd, void *data, size_t size) +{ + int host_errno = 0; + long args[] = {fd, (long) data, size, 0}; + ssize_t ret = (ssize_t) semihosting_call(SEMIHOSTING_SYS_READ, args, &host_errno); + if (ret < 0) { + errno = host_errno; + return ret; + } + /* On success, read syscall returns the number of bytes NOT read, + * adjust the return value to match POSIX. + */ + return size - (ssize_t)ret; +} + +static inline int semihosting_close(int fd) +{ + int host_errno = 0; + long args[] = {fd, 0, 0, 0}; + int ret = (int) semihosting_call(SEMIHOSTING_SYS_CLOSE, args, &host_errno); + if (ret < 0) { + errno = host_errno; + } + return ret; +} + +static inline off_t semihosting_seek(int fd, off_t offset, int mode) +{ + int host_errno = 0; + long args[] = {fd, offset, mode, 0}; + off_t ret = (off_t) semihosting_call(SEMIHOSTING_SYS_SEEK, args, &host_errno); + if (ret == -1) { + errno = host_errno; + } + return ret; +} + +static inline int semihosting_ver_info(void) +{ + int host_errno = 0; + struct { + int version; + } ver_info = { 1 }; + long args[] = {(long) &ver_info, sizeof(ver_info), 0, 0}; + int ret = (int) semihosting_call(SEMIHOSTING_SYS_DRVINFO, args, &host_errno); + (void) host_errno; /* errno not set by this call */ + return ret; +} + +#ifdef __cplusplus +} +#endif diff --git a/components/vfs/vfs_semihost.c b/components/vfs/vfs_semihost.c index 971f4f7e14..3dcefd417a 100644 --- a/components/vfs/vfs_semihost.c +++ b/components/vfs/vfs_semihost.c @@ -12,9 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. -#include "esp_vfs.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" #include "soc/cpu.h" #include #include @@ -22,6 +19,10 @@ #include #include #include +#include "esp_log.h" +#include "esp_vfs.h" +#include "hal/cpu_hal.h" +#include "openocd_semihosting.h" #ifndef CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS #define CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS 1 @@ -31,33 +32,23 @@ #define CONFIG_VFS_SEMIHOSTFS_HOST_PATH_MAX_LEN 128 #endif -#ifdef VFS_SUPPRESS_SEMIHOSTING_LOG -#define LOG_LOCAL_LEVEL ESP_LOG_NONE -#endif //VFS_SUPPRESS_SEMIHOSTING_LOG - -#include "esp_log.h" const static char *TAG = "esp_semihost"; -/* current semihosting implementation version */ -#define DRIVER_SEMIHOSTING_VERSION 0x1 -/* syscalls */ -#define SYSCALL_INSTR "break 1,1\n" -#define SYS_OPEN 0x01 -#define SYS_CLOSE 0x02 -#define SYS_WRITE 0x05 -#define SYS_READ 0x06 -#define SYS_SEEK 0x0A +/* Additional open flags */ -#define SYS_DRVINFO 0xE0 -/* additional open flags */ -#define O_BINARY 0 // there is no binary flag in our toolchain, as well as in Linux OS - // but we are leaving it to have an identical to OOCD flags table -/** ESP-specific file open flag. Indicates that path passed to open() is absolute host path. */ +/* ESP-specific file open flag. + * Indicates that path passed to open() is absolute host path. + */ #define ESP_O_SEMIHOST_ABSPATH 0x80000000 -/* The table is identical to semihosting_common's one from OpenOCD */ +/* There is no O_BINARY flag defined in newlib, as well as on Linux, + * but we are leaving it to have the flags table identical to OpenOCD. + */ +#define O_BINARY 0 + +/* The table is identical to the one in OpenOCD semihosting_common.c */ static const int open_modeflags[12] = { O_RDONLY, O_RDONLY | O_BINARY, @@ -73,33 +64,22 @@ static const int open_modeflags[12] = { O_RDWR | O_CREAT | O_APPEND | O_BINARY }; -/** - * @brief semihosting driver information - * - */ -typedef struct { - int ver; -} drv_info_t; - /** * @brief Get the number of appropriate file open mode set from open_modeflags and add some esp flags to them * * @param flags value, every bit of which reflects state of some open-file flag - * @return int -* -1 - there is no appropriate entry of open_modeflags[] - * esp_flags | (0...11) - esp-specific flags and number of flag set for oocd from @ref open_modeflags[] + * @return index of the flag from @ref open_modeflags[], or -1 if invalid flags combination is given. */ static inline int get_o_mode(int flags) { - uint32_t esp_flags = flags & 0xfff00000; // that bits are not used, so let's use it for our espressif's purposes - uint32_t semi_comm_flags = flags & 0x000fffff; - if (semi_comm_flags & O_EXCL) { // bypassing lacking of this at table above - semi_comm_flags &= ~(O_EXCL); - semi_comm_flags |= O_CREAT; + if (flags & O_EXCL) { // bypassing lacking of this at table above + flags &= ~(O_EXCL); + flags |= O_CREAT; } for (int i = 0; i < sizeof(open_modeflags) / sizeof(open_modeflags[0]); i++) { - if (semi_comm_flags == open_modeflags[i]) - return (esp_flags | i); + if (flags == open_modeflags[i]) { + return i; + } } return -1; // there is no corresponding mode in the table } @@ -111,91 +91,55 @@ typedef struct { static vfs_semihost_ctx_t s_semhost_ctx[CONFIG_VFS_SEMIHOSTFS_MAX_MOUNT_POINTS]; - -static inline int generic_syscall(int sys_nr, int arg1, int arg2, int arg3, int arg4, int* ret_errno) -{ -#if !CONFIG_IDF_TARGET_ESP32C3 && !CONFIG_IDF_TARGET_ESP32H2 // TODO ESP32-C3 reenable semihost in C3 IDF-2287 - int host_ret, host_errno; - - if (!esp_cpu_in_ocd_debug_mode()) { - *ret_errno = EIO; - return -1; - } - __asm__ volatile ( - "mov a2, %[sys_nr]\n" \ - "mov a3, %[arg1]\n" \ - "mov a4, %[arg2]\n" \ - "mov a5, %[arg3]\n" \ - "mov a6, %[arg4]\n" \ - SYSCALL_INSTR \ - "mov %[host_ret], a2\n" \ - "mov %[host_errno], a3\n" \ - :[host_ret]"=r"(host_ret),[host_errno]"=r"(host_errno) - :[sys_nr]"r"(sys_nr),[arg1]"r"(arg1),[arg2]"r"(arg2),[arg3]"r"(arg3),[arg4]"r"(arg4) - :"a2","a3","a4","a5","a6"); - *ret_errno = host_errno; - return host_ret; -#else - return 0; -#endif - -} - -inline bool ctx_is_unused(const vfs_semihost_ctx_t* ctx) +static inline bool ctx_is_unused(const vfs_semihost_ctx_t* ctx) { return ctx->base_path[0] == 0; } -inline bool ctx_uses_abspath(const vfs_semihost_ctx_t* ctx) +static inline bool ctx_uses_abspath(const vfs_semihost_ctx_t* ctx) { return ctx->host_path[0]; } -/** - * @brief Send a custom syscall SYS_DRVINFO to the host for determining - * - * @param ctx context - * @return error - */ -static esp_err_t vfs_semihost_drvinfo(vfs_semihost_ctx_t *ctx) { - drv_info_t drv_info = { - .ver = DRIVER_SEMIHOSTING_VERSION - }; +#define FAIL_IF_NO_DEBUGGER() \ + do { \ + if (!cpu_hal_is_debugger_attached()) { \ + errno = EIO; \ + return -1; \ + } \ + } while(0) - int host_err = 0; - size_t ret = -1; +#if __XTENSA__ +static esp_err_t vfs_semihost_drvinfo(vfs_semihost_ctx_t *ctx) +{ + FAIL_IF_NO_DEBUGGER(); - ESP_LOGV(TAG, "%s: s_ver: %x, flags: %x, par3: %x, par4: %x", __func__, (int)&drv_info, sizeof(drv_info), 0, 0); - - ret = generic_syscall(SYS_DRVINFO, (int)&drv_info, sizeof(drv_info), 0, 0, &host_err); - - /* Recognizing the version */ - ESP_LOGV(TAG, "Trying to determine semihosting's version..."); - if (ret == -1) { /* there is no such syscall - old semihosting */ - ret = ESP_ERR_INVALID_VERSION; - } else { - ESP_LOGI(TAG, "OpenOCD Semihosting v.%d [Read from an OpenOCD response]", drv_info.ver); - ESP_LOGV(TAG, "[Version was read from an OpenOCD response]"); + int ret = semihosting_ver_info(); + if (ret == -1) { + /* Unsupported syscall - old version of OpenOCD */ + return ESP_ERR_INVALID_VERSION; } - return ret; + return ESP_OK; } +#endif // __XTENSA__ -static int vfs_semihost_open(void* ctx, const char* path, int flags, int mode) { - int ret_fd = -1, o_mode = 0, host_err = 0; +static int vfs_semihost_open(void* ctx, const char* path, int flags, int mode) +{ + int ret_fd = -1; char *host_path; vfs_semihost_ctx_t *semi_ctx = ctx; + FAIL_IF_NO_DEBUGGER(); + ESP_LOGV(TAG, "%s: %p '%s 0x%x 0x%x'", __func__, semi_ctx, path, flags, mode); - /* flags processing */ - if (ctx_uses_abspath(semi_ctx)) { - flags |= ESP_O_SEMIHOST_ABSPATH; - } - o_mode = get_o_mode(flags); - + int o_mode = get_o_mode(flags); if (o_mode == -1) { /* if wrong flags - error */ errno = EINVAL; - } else { /* if ok - host_path processing */ + } else { if (ctx_uses_abspath(semi_ctx)) { + /* Create full absolute path on the host by concatenating host base + * path and file path relative to the filesystem root. + */ host_path = malloc(strlen(semi_ctx->host_path) + strlen(path) + 1); if (host_path == NULL) { /* if no valid pointer - error and return */ errno = ENOMEM; @@ -203,14 +147,36 @@ static int vfs_semihost_open(void* ctx, const char* path, int flags, int mode) { } strcpy(host_path, semi_ctx->host_path); strcat(host_path, path); +#ifdef __XTENSA__ + /* By default, OpenOCD for Xtensa prepends ESP_SEMIHOST_BASEDIR to + * the path passed from the target. Adding this special flag to o_mode + * inhibits this behavior. + * This is not necessary for RISC-V since standard semihosting + * implementation is used there and paths aren't mangled on OpenOCD side. + */ + if (ctx_uses_abspath(semi_ctx)) { + o_mode |= ESP_O_SEMIHOST_ABSPATH; + } +#endif // __XTENSA__ } else { host_path = (char *)path; + /* For Xtensa targets in OpenOCD there is additional logic related to + * semihosting paths handling that isn't there for other targets. + * When ESP_SEMIHOST_BASEDIR OpenOCD variable is not set, OpenOCD will + * by default prepend '.' to the path passed from the target. + * By contrast, for RISC-V there is no such logic and the path will be + * used as is, no matter whether it is absolute or relative. + * See esp_xtensa_semihosting_get_file_name in esp_xtensa_semihosting.c + * for details. + */ +#ifndef __XTENSA__ + if (*host_path == '/') { + ++host_path; + } +#endif // !__XTENSA__ } /* everything is ready: syscall and cleanup */ - ret_fd = generic_syscall(SYS_OPEN, (int)host_path, o_mode, strlen(host_path), mode, &host_err); - if (ret_fd == -1) { - errno = host_err; - } + ret_fd = semihosting_open(host_path, o_mode, mode); if (ctx_uses_abspath(semi_ctx)) { free(host_path); } @@ -220,55 +186,34 @@ static int vfs_semihost_open(void* ctx, const char* path, int flags, int mode) { static ssize_t vfs_semihost_write(void* ctx, int fd, const void * data, size_t size) { - int host_err = 0; - size_t ret = -1; + FAIL_IF_NO_DEBUGGER(); ESP_LOGV(TAG, "%s: %d %u bytes", __func__, fd, size); - ret = generic_syscall(SYS_WRITE, fd, (int)data, size, 0, &host_err); - if (ret == -1) { - errno = host_err; - } - return size - (ssize_t)ret; /* Write syscall returns the number of bytes NOT written */ + return semihosting_write(fd, data, size); } static ssize_t vfs_semihost_read(void* ctx, int fd, void* data, size_t size) { - int host_err = 0; - size_t ret = -1; + FAIL_IF_NO_DEBUGGER(); ESP_LOGV(TAG, "%s: %d %u bytes", __func__, fd, size); - ret = generic_syscall(SYS_READ, fd, (int)data, size, 0, &host_err); - if (ret == -1) { - errno = host_err; - return ret; - } - return size - (ssize_t)ret; /* Read syscall returns the number of bytes NOT read */ - + return semihosting_read(fd, data, size); } static int vfs_semihost_close(void* ctx, int fd) { - int ret = -1, host_err = 0; + FAIL_IF_NO_DEBUGGER(); ESP_LOGV(TAG, "%s: %d", __func__, fd); - ret = generic_syscall(SYS_CLOSE, fd, 0, 0, 0, &host_err); - if (ret == -1) { - errno = host_err; - } - return ret; + return semihosting_close(fd); } static off_t vfs_semihost_lseek(void* ctx, int fd, off_t offset, int mode) { - int ret = -1, host_err = 0; + FAIL_IF_NO_DEBUGGER(); - ESP_LOGV(TAG, "%s: %d %ld %d", __func__, fd, offset, mode); - ret = generic_syscall(SYS_SEEK, fd, offset, mode, 0, &host_err); - if (ret == -1) { - errno = host_err; - } - return (off_t)ret; + return semihosting_seek(fd, offset, mode); } esp_err_t esp_vfs_semihost_register(const char* base_path, const char* host_path) @@ -282,7 +227,7 @@ esp_err_t esp_vfs_semihost_register(const char* base_path, const char* host_path .lseek_p = &vfs_semihost_lseek, }; ESP_LOGD(TAG, "Register semihosting driver '%s' -> '%s'", base_path, host_path ? host_path : "null"); - if (!esp_cpu_in_ocd_debug_mode()) { + if (!cpu_hal_is_debugger_attached()) { ESP_LOGE(TAG, "OpenOCD is not connected!"); return ESP_ERR_NOT_SUPPORTED; } @@ -303,10 +248,16 @@ esp_err_t esp_vfs_semihost_register(const char* base_path, const char* host_path strlcpy(s_semhost_ctx[i].host_path, host_path, sizeof(s_semhost_ctx[i].host_path) - 1); } ESP_LOGD(TAG, "Register semihosting driver %d %p", i, &s_semhost_ctx[i]); - esp_err_t err = vfs_semihost_drvinfo(&s_semhost_ctx[i]); // define semihosting version + + esp_err_t err; +#if __XTENSA__ + /* Check for older OpenOCD versions */ + err = vfs_semihost_drvinfo(&s_semhost_ctx[i]); // define semihosting version if (err != ESP_OK) { ESP_LOGE(TAG, "Incompatible OpenOCD version detected. Please follow the getting started guides to install the required version."); } +#endif // __XTENSA__ + err = esp_vfs_register(base_path, &vfs, &s_semhost_ctx[i]); if (err != ESP_OK) { ESP_LOGE(TAG, "Can't register the semihosting! Error: %s", esp_err_to_name(err)); diff --git a/components/xtensa/include/xtensa/semihosting.h b/components/xtensa/include/xtensa/semihosting.h new file mode 100644 index 0000000000..6abfe52ef2 --- /dev/null +++ b/components/xtensa/include/xtensa/semihosting.h @@ -0,0 +1,60 @@ +/* + * SPDX-FileCopyrightText: 2022 Espressif Systems (Shanghai) CO LTD + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#pragma once + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @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. + * On Xtensa, this function assumes that the array contains at + * least 4 elements, but no effort is made to guarantee that. + * Passing a shorter array will still work, as long as it contains + * sufficient values for the corresponding semihosting call. + * @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) // NOLINT(readability-non-const-parameter) +{ + long host_ret; + long host_errno; + /* The break instruction operands should be (1, 14) according to the ISA manual. + * We keep (1, 1) for compatibility, until OpenOCD is updated to support both + * conventions. + */ + __asm__ __volatile__ ( + "mov a2, %[sys_nr]\n" \ + "mov a3, %[arg1]\n" \ + "mov a4, %[arg2]\n" \ + "mov a5, %[arg3]\n" \ + "mov a6, %[arg4]\n" \ + "break 1, 1\n" \ + "mov %[host_ret], a2\n" \ + "mov %[host_errno], a3\n" \ + :[host_ret]"=r"(host_ret), [host_errno]"=r"(host_errno) + :[sys_nr]"r"(id), + [arg1]"r"(data[0]), + [arg2]"r"(data[1]), + [arg3]"r"(data[2]), + [arg4]"r"(data[3]) + :"a2", "a3", "a4", "a5", "a6"); + if (host_ret < 0) { + *out_errno = host_errno; + } + return host_ret; +} + +#ifdef __cplusplus +} +#endif diff --git a/examples/storage/semihost_vfs/README.md b/examples/storage/semihost_vfs/README.md index a8d275dde3..c5c1e2e750 100644 --- a/examples/storage/semihost_vfs/README.md +++ b/examples/storage/semihost_vfs/README.md @@ -15,65 +15,101 @@ This example demonstrates how to use semihosting VFS driver with ESP32. Example ### Hardware and tools required -This example does not require any special hardware, and can be run on any common development board. -This example requires [OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd). -NOTE: In order to run this example you need OpenOCD version `v0.10.0-esp32-20190313` or later. +This example requires a development board with JTAG interface, for example: -Run OpenOCD using command: -``` -bin/openocd -s share/openocd/scripts -c 'set ESP_SEMIHOST_BASEDIR '$IDF_PATH/examples/storage/semihost_vfs/data -f board/esp32-wrover-kit-3.3v.cfg -``` -This command also configures OpenOCD to expose example project `data` subdirectory to the target's semihosting VFS driver. +- ESP32-Wrover-Kit, ESP32-Ethernet-Kit for ESP32 +- ESP32-S2-Kaluga for ESP32-S2 +- For ESP32-C3 or ESP32-S3, any board with the built-in USB interface (USB_SERIAL_JTAG) +- ESP-Prog as an external JTAG adapter with any other development board + +This example also requires [OpenOCD](https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/jtag-debugging/index.html#run-openocd) to be set up. ### Build and flash -Replace PORT with serial port name: +1. Replace PORT with serial port name and run this command to build and flash the example: + + ``` + idf.py -p PORT flash + ``` + + See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. + + +2. Go to `data` subdirectory of the project and run OpenOCD. + + ``` + cd data + openocd -f board/esp32-wrover-kit-3.3v.cfg + ``` + + Note that you need to use the correct configuration file for your board after `-f` option in the above command. Please refer to the list of configuration files available for [ESP32](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-guides/jtag-debugging/tips-and-quirks.html#jtag-debugging-tip-openocd-configure-target), [ESP32-S2](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s2/api-guides/jtag-debugging/tips-and-quirks.html#jtag-debugging-tip-openocd-configure-target), [ESP32-S3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32s3/api-guides/jtag-debugging/tips-and-quirks.html#jtag-debugging-tip-openocd-configure-target), [ESP32-C3](https://docs.espressif.com/projects/esp-idf/en/latest/esp32c3/api-guides/jtag-debugging/tips-and-quirks.html#jtag-debugging-tip-openocd-configure-target). + +3. With OpenOCD still running, open another console or terminal and run IDF monitor there: + + ``` + idf.py monitor + ``` + + (To exit the serial monitor, type ``Ctrl-]``.) + +### Overriding the base directory for semihosting + +When the example application calls `esp_vfs_semihost_register("/host", NULL)`, the path `/host` on the ESP target is mapped to the semihosting _base directory_. By default, this is the directory where OpenOCD program is started from. In the instructions above, OpenOCD is started in `data` subdirectory of the example project. + +When debugging with Xtensa based SoCs (ESP32, ESP32-S2, ESP32-S3) it is possible to override the semihosting base directory using an additional flag of `openocd` command. For example, on Linux and macOS: ``` -idf.py -p PORT flash monitor +openocd -c "set ESP_SEMIHOST_BASEDIR $IDF_PATH/examples/storage/semihost_vfs/data" -f board/esp32-wrover-kit-3.3v.cfg ``` -(To exit the serial monitor, type ``Ctrl-]``.) +or on Windows: -See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects. +``` +openocd -c "set ESP_SEMIHOST_BASEDIR %IDF_PATH%/examples/storage/semihost_vfs/data" -f board/esp32-wrover-kit-3.3v.cfg +``` + +The above command will set `ESP_SEMIHOST_BASEDIR` variable to `examples/storage/semihost_vfs/data` subdirectory of ESP-IDF. With that, it is not necessary to run OpenOCD from that specific directory. + +> Note: This feature is not available for RISC-V based SoCs (ESP32-C3, ESP32-H2). To set the semihosting base directory, change into the required directory before running `openocd` command. ## Example output -There are two types of outputs produced by example: -1. File `esp32_stdout.txt` in the host directory mounted to the target: +There are two outputs produced by example: -``` -W (274) example: Switched to semihosted stdout -Semihosted stdout write 0 -Semihosted stdout write 1 -Semihosted stdout write 2 -... -Semihosted stdout write 98 -Semihosted stdout write 99 -W (274) example: Switch to UART stdout -``` +1. The example creates and writes `esp32_stdout.txt` file in the `data` directory of the project: -2. On the boards console: + ``` + W (274) example: Switched to semihosted stdout + Semihosted stdout write 0 + Semihosted stdout write 1 + Semihosted stdout write 2 + ... + Semihosted stdout write 98 + Semihosted stdout write 99 + W (274) example: Switch to UART stdout + ``` -``` -W (274) example: Switch to semihosted stdout -W (274) example: Switched back to UART stdout -I (274) example: Wrote 2798 bytes -====================== HOST DATA START ========================= -The following are the graphical (non-control) characters defined by -ISO 8859-1 (1987). Descriptions in words aren't all that helpful, -but they're the best we can do in text. A graphics file illustrating -the character set should be available from the same archive as this -file. +2. The example reads [data/host_file.txt](data/host_file.txt) and prints its contents to the serial console: -Hex Description Hex Description - -20 SPACE -... -7D RIGHT CURLY BRACKET FD SMALL LETTER Y WITH ACUTE -7E TILDE FE SMALL LETTER THORN (Icelandic) - FF SMALL LETTER Y WITH DIAERESIS -====================== HOST DATA END ========================= -I (694) example: Read 6121 bytes -``` + ``` + W (274) example: Switch to semihosted stdout + W (274) example: Switched back to UART stdout + I (274) example: Wrote 2798 bytes + ====================== HOST DATA START ========================= + The following are the graphical (non-control) characters defined by + ISO 8859-1 (1987). Descriptions in words aren't all that helpful, + but they're the best we can do in text. A graphics file illustrating + the character set should be available from the same archive as this + file. + + Hex Description Hex Description + + 20 SPACE + ... + 7D RIGHT CURLY BRACKET FD SMALL LETTER Y WITH ACUTE + 7E TILDE FE SMALL LETTER THORN (Icelandic) + FF SMALL LETTER Y WITH DIAERESIS + ====================== HOST DATA END ========================= + I (694) example: Read 6121 bytes + ``` diff --git a/examples/storage/semihost_vfs/main/semihost_vfs_example_main.c b/examples/storage/semihost_vfs/main/semihost_vfs_example_main.c index 60031f56bc..b8bba77208 100644 --- a/examples/storage/semihost_vfs/main/semihost_vfs_example_main.c +++ b/examples/storage/semihost_vfs/main/semihost_vfs_example_main.c @@ -52,7 +52,7 @@ void app_main(void) fflush(fout); // ensure that all data are sent to the host file // ftell can also be used, get file size before closing it in `freopen` int count = ftell(fout); - stdout = freopen("/dev/uart/" STRINGIFY(CONFIG_ESP_CONSOLE_UART_NUM), "w", fout); + stdout = freopen("/dev/console", "w", fout); if (stdout == NULL) { ESP_LOGE(TAG, "Failed to reopen semihosted stdout (%d)!", errno); return; From c9db32041069bc66c1d70eb1a25c5d21eb74b23a Mon Sep 17 00:00:00 2001 From: Ivan Grokhotkov Date: Thu, 20 Jan 2022 19:18:06 +0100 Subject: [PATCH 2/2] xtensa: fix semihosting arguments potentially begin optimized out The compiler was not informed that the assembly block should be treated as a memory barrier and could optimize out the initialization of local arrays which could be used as semihosting arguments. This resulted in garbage values being passed as semihosting call arguments. Additionally this commit changes the approach for placing values into specific register. Instead of clobbers, local register variables are used. This results in simpler generated code, since the compiler is able to place values directly into the registers used for semihosting arguments, avoiding additional moves. --- .../xtensa/include/xtensa/semihosting.h | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/components/xtensa/include/xtensa/semihosting.h b/components/xtensa/include/xtensa/semihosting.h index 6abfe52ef2..71a23bb563 100644 --- a/components/xtensa/include/xtensa/semihosting.h +++ b/components/xtensa/include/xtensa/semihosting.h @@ -27,28 +27,32 @@ extern "C" { */ static inline long semihosting_call(long id, long *data, int *out_errno) // NOLINT(readability-non-const-parameter) { - long host_ret; - long host_errno; + /* GCC doesn't allow using specific register names in constraints for Xtensa. + * For this case, GCC extended inline assembly manual says the following: + * If you must use a specific register, but your Machine Constraints do not provide + * sufficient control to select the specific register you want, local register variables + * may provide a solution. + * Using local register variables results in simpler generated code than + * the previous implementation which listed a2-a6 as clobbered registers. + */ + register long a2 asm ("a2") = id; + register long a3 asm ("a3") = (long) data[0]; + register long a4 asm ("a4") = (long) data[1]; + register long a5 asm ("a5") = (long) data[2]; + register long a6 asm ("a6") = (long) data[3]; + /* The break instruction operands should be (1, 14) according to the ISA manual. * We keep (1, 1) for compatibility, until OpenOCD is updated to support both * conventions. */ __asm__ __volatile__ ( - "mov a2, %[sys_nr]\n" \ - "mov a3, %[arg1]\n" \ - "mov a4, %[arg2]\n" \ - "mov a5, %[arg3]\n" \ - "mov a6, %[arg4]\n" \ - "break 1, 1\n" \ - "mov %[host_ret], a2\n" \ - "mov %[host_errno], a3\n" \ - :[host_ret]"=r"(host_ret), [host_errno]"=r"(host_errno) - :[sys_nr]"r"(id), - [arg1]"r"(data[0]), - [arg2]"r"(data[1]), - [arg3]"r"(data[2]), - [arg4]"r"(data[3]) - :"a2", "a3", "a4", "a5", "a6"); + "break 1, 1\n" + : "+r"(a2), "+r"(a3) + : "r"(a4), "r"(a5), "r"(a6) + : "memory"); + + long host_ret = a2; + long host_errno = a3; if (host_ret < 0) { *out_errno = host_errno; }