Преглед изворни кода

vfs: add support for semihosting on ESP32-C3

Ivan Grokhotkov пре 5 година
родитељ
комит
876f4d6a1c

+ 3 - 2
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.

+ 5 - 5
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);
 }

+ 1 - 29
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;

+ 1 - 29
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;

+ 1 - 6
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
 
 /**

+ 68 - 0
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

+ 150 - 0
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 <string.h>
+#include <sys/errno.h>
+
+#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

+ 93 - 143
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 <stdarg.h>
 #include <stdbool.h>
 #include <string.h>
 #include <sys/errno.h>
 #include <sys/stat.h>
 #include <fcntl.h>
+#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
-    };
-
-    int host_err = 0;
-    size_t ret = -1;
-
-    ESP_LOGV(TAG, "%s: s_ver: %x, flags:  %x, par3:  %x, par4:  %x", __func__, (int)&drv_info, sizeof(drv_info), 0, 0);
+#define FAIL_IF_NO_DEBUGGER() \
+    do { \
+        if (!cpu_hal_is_debugger_attached()) { \
+            errno = EIO; \
+            return -1; \
+        } \
+    } while(0)
 
-    ret = generic_syscall(SYS_DRVINFO, (int)&drv_info, sizeof(drv_info), 0, 0, &host_err);
+#if __XTENSA__
+static esp_err_t vfs_semihost_drvinfo(vfs_semihost_ctx_t *ctx)
+{
+    FAIL_IF_NO_DEBUGGER();
 
-    /* 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;
-    ESP_LOGV(TAG, "%s: %p '%s 0x%x 0x%x'", __func__, semi_ctx, path, flags, mode);
+    FAIL_IF_NO_DEBUGGER();
 
-    /* flags processing */
-    if (ctx_uses_abspath(semi_ctx)) {
-        flags |= ESP_O_SEMIHOST_ABSPATH;
-    }
-    o_mode = get_o_mode(flags);
+    ESP_LOGV(TAG, "%s: %p '%s 0x%x 0x%x'", __func__, semi_ctx, path, flags, mode);
 
+    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));

+ 60 - 0
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

+ 81 - 45
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 monitor
-```
+   ```
+   idf.py -p PORT flash
+   ```
 
-(To exit the serial monitor, type ``Ctrl-]``.)
+   See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
 
-See the Getting Started Guide for full steps to configure and use ESP-IDF to build projects.
 
-## Example output
+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:
 
-There are two types of outputs produced by example: 
-1. File `esp32_stdout.txt` in the host directory mounted to the target:
+   ```
+   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:
 
 ```
-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
+openocd -c "set ESP_SEMIHOST_BASEDIR $IDF_PATH/examples/storage/semihost_vfs/data" -f board/esp32-wrover-kit-3.3v.cfg
 ```
 
-2. On the boards console:
+or on Windows:
 
 ```
-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
+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 outputs produced by example:
+
+1. The example creates and writes `esp32_stdout.txt` file in the `data` directory of the project:
+
+   ```
+   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
+   ```
+
+2. The example reads [data/host_file.txt](data/host_file.txt) and prints its contents to the serial console:
+
+   ```
+   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
+   ```
+

+ 1 - 1
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;