vfs: add support for semihosting on ESP32-C3

This commit is contained in:
Ivan Grokhotkov 2021-01-06 00:26:07 +01:00
parent 991874ae43
commit 876f4d6a1c
11 changed files with 464 additions and 260 deletions

View File

@ -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.

View File

@ -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);
}

View File

@ -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;

View File

@ -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;

View File

@ -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
/**

View File

@ -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

View File

@ -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

View File

@ -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
};
#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));

View File

@ -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

View File

@ -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
```

View File

@ -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;