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