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 2edb18bf12..3c5dfcb3ce 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 94bf5d8847..56c29f9dd2 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 0991a1ee9e..d9cd4bf65f 100644 --- a/components/vfs/vfs_semihost.c +++ b/components/vfs/vfs_semihost.c @@ -1,19 +1,19 @@ /* - * SPDX-FileCopyrightText: 2020-2021 Espressif Systems (Shanghai) CO LTD + * SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD * * SPDX-License-Identifier: Apache-2.0 */ -#include "esp_vfs.h" -#include "freertos/FreeRTOS.h" -#include "freertos/task.h" -#include "esp_cpu.h" #include #include #include #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 @@ -23,33 +23,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, @@ -65,33 +55,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 } @@ -103,91 +82,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 && !CONFIG_IDF_TARGET_ESP8684 // 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; @@ -195,14 +138,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); } @@ -212,55 +177,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) @@ -274,7 +218,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; } @@ -295,10 +239,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;