From 784b3d6ea0563f6458f9626c2f0ef108d4aa2e8c Mon Sep 17 00:00:00 2001 From: Tahsin Mutlugun Date: Mon, 16 Jun 2025 17:32:02 +0300 Subject: [PATCH] arch: xtensa: Add semihosting support Add semihosting support for Xtensa architecture. Existing semihosting instructions are based on ARM, so they are converted to Xtensa codes before the semihosting call is invoked. Return codes of read, write and seek calls had to be converted to match semihosting API definitions. Signed-off-by: Tahsin Mutlugun --- arch/common/Kconfig | 8 +- arch/xtensa/core/CMakeLists.txt | 5 + arch/xtensa/core/semihost.c | 249 ++++++++++++++++++++++++++++++++ 3 files changed, 258 insertions(+), 4 deletions(-) create mode 100644 arch/xtensa/core/semihost.c diff --git a/arch/common/Kconfig b/arch/common/Kconfig index 1cc91d7c25b..8b860917977 100644 --- a/arch/common/Kconfig +++ b/arch/common/Kconfig @@ -4,11 +4,11 @@ # SPDX-License-Identifier: Apache-2.0 config SEMIHOST - bool "Semihosting support for ARM and RISC-V targets" - depends on ARM || ARM64 || RISCV + bool "Semihosting support for ARM, RISC-V and Xtensa targets" + depends on ARM || ARM64 || RISCV || (XTENSA && !SIMULATOR_XTENSA) help - Semihosting is a mechanism that enables code running on an ARM or - RISC-V target to communicate and use the Input/Output facilities on + Semihosting is a mechanism that enables code running on an ARM, RISC-V + or Xtensa target to communicate and use the Input/Output facilities on a host computer that is running a debugger. Additional information can be found in: https://developer.arm.com/documentation/dui0471/m/what-is-semihosting- diff --git a/arch/xtensa/core/CMakeLists.txt b/arch/xtensa/core/CMakeLists.txt index e3d6025a91f..9ac52b16317 100644 --- a/arch/xtensa/core/CMakeLists.txt +++ b/arch/xtensa/core/CMakeLists.txt @@ -23,6 +23,7 @@ zephyr_library_sources_ifdef(CONFIG_XTENSA_ENABLE_BACKTRACE debug_helpers_asm.S) zephyr_library_sources_ifdef(CONFIG_DEBUG_COREDUMP coredump.c) zephyr_library_sources_ifdef(CONFIG_TIMING_FUNCTIONS timing.c) zephyr_library_sources_ifdef(CONFIG_GDBSTUB gdbstub.c) +zephyr_library_sources_ifdef(CONFIG_SEMIHOST semihost.c) zephyr_library_sources_ifdef(CONFIG_XTENSA_MMU ptables.c mmu.c) zephyr_library_sources_ifdef(CONFIG_XTENSA_MPU mpu.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace.S syscall_helper.c) @@ -90,6 +91,10 @@ else() set(NEED_FLUSH_SCRATCH_REG false) endif() +if(CONFIG_SEMIHOST) + zephyr_library_include_directories(${ZEPHYR_BASE}/arch/common/include) +endif() + # Generates a list of device-specific scratch register choices set(ZSR_H ${CMAKE_BINARY_DIR}/zephyr/include/generated/zephyr/zsr.h) add_custom_command(OUTPUT ${ZSR_H} DEPENDS ${CORE_ISA_DM} diff --git a/arch/xtensa/core/semihost.c b/arch/xtensa/core/semihost.c new file mode 100644 index 00000000000..762ae9f3f58 --- /dev/null +++ b/arch/xtensa/core/semihost.c @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2022 Intel Corporation. + * Copyright (c) 2025 Analog Devices, Inc. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include "semihost_types.h" + +#define XTENSA_SEMIHOST_OPEN (-2) +#define XTENSA_SEMIHOST_CLOSE (-3) +#define XTENSA_SEMIHOST_READ (-4) +#define XTENSA_SEMIHOST_WRITE (-5) +#define XTENSA_SEMIHOST_LSEEK (-6) +#define XTENSA_SEMIHOST_RENAME (-7) +#define XTENSA_SEMIHOST_FSTAT (-10) + +enum semihost_open_flag { + SEMIHOST_RDONLY = 0x0, + SEMIHOST_WRONLY = 0x1, + SEMIHOST_RDWR = 0x2, + SEMIHOST_APPEND = 0x8, + SEMIHOST_CREAT = 0x200, + SEMIHOST_TRUNC = 0x400, + SEMIHOST_EXCL = 0x800, +}; + +uint32_t semihost_flags(enum semihost_open_mode mode) +{ + uint32_t flags = 0; + + switch (mode) { + case SEMIHOST_OPEN_R: + case SEMIHOST_OPEN_RB: + flags = SEMIHOST_RDONLY; + break; + case SEMIHOST_OPEN_R_PLUS: + case SEMIHOST_OPEN_RB_PLUS: + flags = SEMIHOST_RDWR; + break; + case SEMIHOST_OPEN_W: + case SEMIHOST_OPEN_WB: + flags = SEMIHOST_WRONLY | SEMIHOST_CREAT | SEMIHOST_TRUNC; + break; + case SEMIHOST_OPEN_W_PLUS: + case SEMIHOST_OPEN_WB_PLUS: + flags = SEMIHOST_RDWR | SEMIHOST_CREAT | SEMIHOST_TRUNC; + break; + case SEMIHOST_OPEN_A: + case SEMIHOST_OPEN_AB: + flags = SEMIHOST_WRONLY | SEMIHOST_CREAT | SEMIHOST_APPEND; + break; + case SEMIHOST_OPEN_A_PLUS: + case SEMIHOST_OPEN_AB_PLUS: + flags = SEMIHOST_RDWR | SEMIHOST_CREAT | SEMIHOST_APPEND; + break; + default: + return -1; + } + + return flags; +} + +uint32_t semihost_mode(enum semihost_open_mode mode) +{ + switch (mode) { + case SEMIHOST_OPEN_W: + case SEMIHOST_OPEN_WB: + case SEMIHOST_OPEN_W_PLUS: + case SEMIHOST_OPEN_WB_PLUS: + case SEMIHOST_OPEN_A: + case SEMIHOST_OPEN_AB: + case SEMIHOST_OPEN_A_PLUS: + case SEMIHOST_OPEN_AB_PLUS: + /* Octal 0600, S_IRUSR | S_IWUSR */ + return 0x180; + default: + return 0; + } +} + +static inline uintptr_t xtensa_semihost_call_4(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t arg4, uintptr_t call_id) +{ + register uintptr_t a2 __asm__("%a2") = call_id; + register uintptr_t a6 __asm__("%a6") = arg1; + register uintptr_t a3 __asm__("%a3") = arg2; + register uintptr_t a4 __asm__("%a4") = arg3; + register uintptr_t a5 __asm__("%a5") = arg4; + + __asm__ volatile("break 1, 14\n\t" + : "=r"(a2) + : "r"(a2), "r"(a6), "r"(a3), "r"(a4), "r"(a5) + : "memory"); + + return a2; +} + +static inline uintptr_t xtensa_semihost_call_3(uintptr_t arg1, uintptr_t arg2, uintptr_t arg3, + uintptr_t call_id) +{ + register uintptr_t a2 __asm__("%a2") = call_id; + register uintptr_t a6 __asm__("%a6") = arg1; + register uintptr_t a3 __asm__("%a3") = arg2; + register uintptr_t a4 __asm__("%a4") = arg3; + + __asm__ volatile("break 1, 14\n\t" + : "=r"(a2) + : "r"(a2), "r"(a6), "r"(a3), "r"(a4) + : "memory"); + + return a2; +} + +static inline uintptr_t xtensa_semihost_call_2(uintptr_t arg1, uintptr_t arg2, uintptr_t call_id) +{ + register uintptr_t a2 __asm__("%a2") = call_id; + register uintptr_t a6 __asm__("%a6") = arg1; + register uintptr_t a3 __asm__("%a3") = arg2; + + __asm__ volatile("break 1, 14\n\t" : "=r"(a2) : "r"(a2), "r"(a6), "r"(a3) : "memory"); + + return a2; +} + +static inline uintptr_t xtensa_semihost_call_1(uintptr_t arg1, uintptr_t call_id) +{ + register uintptr_t a2 __asm__("%a2") = call_id; + register uintptr_t a6 __asm__("%a6") = arg1; + + __asm__ volatile("break 1, 14\n\t" : "=r"(a2) : "r"(a2), "r"(a6) : "memory"); + + return a2; +} + +long xtensa_semihost_open(struct semihost_open_args *args) +{ + return xtensa_semihost_call_4((uintptr_t)args->path, semihost_flags(args->mode), + semihost_mode(args->mode), args->path_len, + XTENSA_SEMIHOST_OPEN); +} + +long xtensa_semihost_close(long fd) +{ + return xtensa_semihost_call_1(fd, XTENSA_SEMIHOST_CLOSE); +} + +long xtensa_semihost_write(long fd, const char *buf, long len) +{ + long ret; + + ret = (long)xtensa_semihost_call_3(fd, (uintptr_t)buf, len, XTENSA_SEMIHOST_WRITE); + + /* semihost_write assumes that data was written successfully if ret == 0. */ + if (ret == len) { + return 0; + } + + return -1; +} + +long xtensa_semihost_read(long fd, void *buf, long len) +{ + long ret; + + ret = (long)xtensa_semihost_call_3(fd, (uintptr_t)buf, len, XTENSA_SEMIHOST_READ); + + /* semihost_read assumes that all bytes were read if ret == 0. + * If ret == len, it means EOF was reached. + */ + if (ret == len) { + return 0; + } else if (ret <= 0) { + return len; + } else { + return ret; + } +} + +long xtensa_semihost_read_char(long fd) +{ + char c = 0; + + xtensa_semihost_call_3(fd, (uintptr_t)&c, 1, XTENSA_SEMIHOST_READ); + + return (long)c; +} + +long xtensa_semihost_seek(struct semihost_seek_args *args) +{ + long ret; + + ret = (long)xtensa_semihost_call_3(args->fd, args->offset, 0, XTENSA_SEMIHOST_LSEEK); + + if (ret == args->offset) { + return 0; + } + + return ret; +} + +long xtensa_semihost_flen(long fd) +{ + uint8_t buf[64] = {0}; + long ret; + + ret = (long)xtensa_semihost_call_2(fd, (uintptr_t)buf, XTENSA_SEMIHOST_FSTAT); + if (ret < 0) { + return -1; + } + + /* Struct stat is 64 bytes, bytes 28-35 correspond to st_size + * field. 8-bytes cannot fit into long data type so return + * only the lower 4 bytes. + */ + ret = *((long *)&buf[32]); + + return sys_be32_to_cpu(ret); +} + +long semihost_exec(enum semihost_instr instr, void *args) +{ + switch (instr) { + case SEMIHOST_OPEN: + return xtensa_semihost_open((struct semihost_open_args *)args); + case SEMIHOST_CLOSE: + return xtensa_semihost_close(((struct semihost_close_args *)args)->fd); + case SEMIHOST_WRITEC: + return xtensa_semihost_write(1, (char *)args, 1); + case SEMIHOST_WRITE: + return xtensa_semihost_write(((struct semihost_write_args *)args)->fd, + ((struct semihost_write_args *)args)->buf, + ((struct semihost_write_args *)args)->len); + case SEMIHOST_READ: + return xtensa_semihost_read(((struct semihost_read_args *)args)->fd, + ((struct semihost_read_args *)args)->buf, + ((struct semihost_read_args *)args)->len); + case SEMIHOST_READC: + return xtensa_semihost_read_char(((struct semihost_poll_in_args *)args)->zero); + case SEMIHOST_SEEK: + return xtensa_semihost_seek((struct semihost_seek_args *)args); + case SEMIHOST_FLEN: + return xtensa_semihost_flen(((struct semihost_flen_args *)args)->fd); + default: + return -1; + } +}