From d8f186aa4a18ab2230a9733c3a133fe1433aa87d Mon Sep 17 00:00:00 2001 From: Jordan Yates Date: Wed, 30 Mar 2022 20:43:41 +1000 Subject: [PATCH] arch: common: semihost: add semihosting operations Add an API that utilizes the ARM semihosting mechanism to interact with the host system when a device is being emulated or run under a debugger. RISCV is implemented in terms of the ARM implementation, and therefore the ARM definitions cross enough architectures to be defined 'common'. Functionality is exposed as a separate API instead of syscall implementations (`_lseek`, `_open`, etc) due to various quirks with the ARM mechanisms that means function arguments are not standard. For more information see: https://developer.arm.com/documentation/dui0471/m/what-is-semihosting- Signed-off-by: Jordan Yates impl --- .../core/aarch32/cortex_a_r/CMakeLists.txt | 1 + arch/arm/core/aarch32/cortex_a_r/semihost.c | 27 +++ arch/arm/core/aarch32/cortex_m/CMakeLists.txt | 1 + arch/arm/core/aarch32/cortex_m/semihost.c | 18 ++ arch/arm64/core/CMakeLists.txt | 1 + arch/arm64/core/semihost.c | 18 ++ arch/common/CMakeLists.txt | 2 + arch/common/semihost.c | 131 ++++++++++++ arch/riscv/core/CMakeLists.txt | 1 + arch/riscv/core/semihost.c | 26 +++ include/zephyr/arch/common/semihost.h | 200 ++++++++++++++++++ 11 files changed, 426 insertions(+) create mode 100644 arch/arm/core/aarch32/cortex_a_r/semihost.c create mode 100644 arch/arm/core/aarch32/cortex_m/semihost.c create mode 100644 arch/arm64/core/semihost.c create mode 100644 arch/common/semihost.c create mode 100644 arch/riscv/core/semihost.c create mode 100644 include/zephyr/arch/common/semihost.h diff --git a/arch/arm/core/aarch32/cortex_a_r/CMakeLists.txt b/arch/arm/core/aarch32/cortex_a_r/CMakeLists.txt index a044a2b20bb..bb95c33782c 100644 --- a/arch/arm/core/aarch32/cortex_a_r/CMakeLists.txt +++ b/arch/arm/core/aarch32/cortex_a_r/CMakeLists.txt @@ -15,3 +15,4 @@ zephyr_library_sources( ) zephyr_library_sources_ifdef(CONFIG_USERSPACE thread.c) +zephyr_library_sources_ifdef(CONFIG_SEMIHOST semihost.c) diff --git a/arch/arm/core/aarch32/cortex_a_r/semihost.c b/arch/arm/core/aarch32/cortex_a_r/semihost.c new file mode 100644 index 00000000000..705160de651 --- /dev/null +++ b/arch/arm/core/aarch32/cortex_a_r/semihost.c @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include + +#if !(defined(CONFIG_ISA_ARM) || defined(CONFIG_ISA_THUMB2)) +#error Unsupported ISA +#endif + +long semihost_exec(enum semihost_instr instr, void *args) +{ + register unsigned long r0 __asm__ ("r0") = instr; + register void *r1 __asm__ ("r1") = args; + register long ret __asm__ ("r0"); + + if (IS_ENABLED(CONFIG_ISA_THUMB2)) { + __asm__ __volatile__ ("svc 0xab" : : "r" (r0), "r" (r1) : "memory"); + } else { + __asm__ __volatile__ ("svc 0x123456" : : "r" (r0), "r" (r1) : "memory"); + } + return ret; +} diff --git a/arch/arm/core/aarch32/cortex_m/CMakeLists.txt b/arch/arm/core/aarch32/cortex_m/CMakeLists.txt index 4e3050abd57..b998cbcf320 100644 --- a/arch/arm/core/aarch32/cortex_m/CMakeLists.txt +++ b/arch/arm/core/aarch32/cortex_m/CMakeLists.txt @@ -17,6 +17,7 @@ zephyr_library_sources( zephyr_library_sources_ifdef(CONFIG_USERSPACE thread.c) zephyr_library_sources_ifdef(CONFIG_DEBUG_COREDUMP coredump.c) zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE __aeabi_read_tp.S) +zephyr_library_sources_ifdef(CONFIG_SEMIHOST semihost.c) if(CONFIG_NULL_POINTER_EXCEPTION_DETECTION_DWT) zephyr_library_sources(debug.c) diff --git a/arch/arm/core/aarch32/cortex_m/semihost.c b/arch/arm/core/aarch32/cortex_m/semihost.c new file mode 100644 index 00000000000..3369af3e14a --- /dev/null +++ b/arch/arm/core/aarch32/cortex_m/semihost.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +long semihost_exec(enum semihost_instr instr, void *args) +{ + register unsigned int r0 __asm__ ("r0") = instr; + register void *r1 __asm__ ("r1") = args; + register int ret __asm__ ("r0"); + + __asm__ __volatile__ ("bkpt 0xab" : : "r" (r0), "r" (r1) : "memory"); + return ret; +} diff --git a/arch/arm64/core/CMakeLists.txt b/arch/arm64/core/CMakeLists.txt index 1a0a5337bf8..da6e3b1f5c2 100644 --- a/arch/arm64/core/CMakeLists.txt +++ b/arch/arm64/core/CMakeLists.txt @@ -25,6 +25,7 @@ zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE tls.c) zephyr_library_sources_ifdef(CONFIG_HAS_ARM_SMCCC smccc-call.S) zephyr_library_sources_ifdef(CONFIG_AARCH64_IMAGE_HEADER header.S) zephyr_library_sources_ifdef(CONFIG_CACHE_MANAGEMENT cache.c) +zephyr_library_sources_ifdef(CONFIG_SEMIHOST semihost.c) if ((CONFIG_MP_NUM_CPUS GREATER 1) OR (CONFIG_SMP)) zephyr_library_sources(smp.c) endif () diff --git a/arch/arm64/core/semihost.c b/arch/arm64/core/semihost.c new file mode 100644 index 00000000000..91657b22fb3 --- /dev/null +++ b/arch/arm64/core/semihost.c @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +long semihost_exec(enum semihost_instr instr, void *args) +{ + register unsigned long w0 __asm__ ("w0") = instr; + register void *x1 __asm__ ("x1") = args; + register long ret __asm__ ("x0"); + + __asm__ volatile ("hlt 0xf000" : : "r" (w0), "r" (x1) : "memory"); + return ret; +} diff --git a/arch/common/CMakeLists.txt b/arch/common/CMakeLists.txt index aa9ddec353d..8a8a9e6d20e 100644 --- a/arch/common/CMakeLists.txt +++ b/arch/common/CMakeLists.txt @@ -67,3 +67,5 @@ if(CONFIG_COVERAGE) zephyr_compile_options($) zephyr_link_libraries($) endif() + +zephyr_sources_ifdef(CONFIG_SEMIHOST semihost.c) diff --git a/arch/common/semihost.c b/arch/common/semihost.c new file mode 100644 index 00000000000..f22891c6487 --- /dev/null +++ b/arch/common/semihost.c @@ -0,0 +1,131 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include + +struct semihost_poll_in_args { + long zero; +} __packed; + +struct semihost_open_args { + const char *path; + long mode; + long path_len; +} __packed; + +struct semihost_close_args { + long fd; +} __packed; + +struct semihost_flen_args { + long fd; +} __packed; + +struct semihost_seek_args { + long fd; + long offset; +} __packed; + +struct semihost_read_args { + long fd; + char *buf; + long len; +} __packed; + +struct semihost_write_args { + long fd; + const char *buf; + long len; +} __packed; + +char semihost_poll_in(void) +{ + struct semihost_poll_in_args args = { + .zero = 0 + }; + + return (char)semihost_exec(SEMIHOST_READC, &args); +} + +void semihost_poll_out(char c) +{ + /* WRITEC takes a pointer directly to the character */ + (void)semihost_exec(SEMIHOST_WRITEC, &c); +} + +long semihost_open(const char *path, long mode) +{ + struct semihost_open_args args = { + .path = path, + .mode = mode, + .path_len = strlen(path) + }; + + return semihost_exec(SEMIHOST_OPEN, &args); +} + +long semihost_close(long fd) +{ + struct semihost_close_args args = { + .fd = fd + }; + + return semihost_exec(SEMIHOST_CLOSE, &args); +} + +long semihost_flen(long fd) +{ + struct semihost_flen_args args = { + .fd = fd + }; + + return semihost_exec(SEMIHOST_FLEN, &args); +} + +long semihost_seek(long fd, long offset) +{ + struct semihost_seek_args args = { + .fd = fd, + .offset = offset + }; + + return semihost_exec(SEMIHOST_SEEK, &args); +} + +long semihost_read(long fd, void *buf, long len) +{ + struct semihost_read_args args = { + .fd = fd, + .buf = buf, + .len = len + }; + long ret; + + ret = semihost_exec(SEMIHOST_READ, &args); + /* EOF condition */ + if (ret == len) { + ret = -EIO; + } + /* All bytes read */ + else if (ret == 0) { + ret = len; + } + return ret; +} + +long semihost_write(long fd, const void *buf, long len) +{ + struct semihost_write_args args = { + .fd = fd, + .buf = buf, + .len = len + }; + + return semihost_exec(SEMIHOST_WRITE, &args); +} diff --git a/arch/riscv/core/CMakeLists.txt b/arch/riscv/core/CMakeLists.txt index 2b7c884f59e..9bb2619f8f2 100644 --- a/arch/riscv/core/CMakeLists.txt +++ b/arch/riscv/core/CMakeLists.txt @@ -21,3 +21,4 @@ zephyr_library_sources_ifdef(CONFIG_DEBUG_COREDUMP coredump.c) zephyr_library_sources_ifdef(CONFIG_IRQ_OFFLOAD irq_offload.c) zephyr_library_sources_ifdef(CONFIG_THREAD_LOCAL_STORAGE tls.c) zephyr_library_sources_ifdef(CONFIG_USERSPACE userspace.S) +zephyr_library_sources_ifdef(CONFIG_SEMIHOST semihost.c) diff --git a/arch/riscv/core/semihost.c b/arch/riscv/core/semihost.c new file mode 100644 index 00000000000..6c343bcaa50 --- /dev/null +++ b/arch/riscv/core/semihost.c @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include + +long semihost_exec(enum semihost_instr instr, void *args) +{ + register unsigned long a0 __asm__ ("a0") = instr; + register void *a1 __asm__ ("a1") = args; + register long ret __asm__ ("a0"); + + __asm__ volatile ( + ".option push\n\t" + ".option norvc\n\t" + "slli zero, zero, 0x1f\n\t" + "ebreak\n\t" + "srai zero, zero, 0x7\n\t" + ".option pop" + : : "r" (a0), "r" (a1) : "memory"); + + return ret; +} diff --git a/include/zephyr/arch/common/semihost.h b/include/zephyr/arch/common/semihost.h new file mode 100644 index 00000000000..0180b8ecc42 --- /dev/null +++ b/include/zephyr/arch/common/semihost.h @@ -0,0 +1,200 @@ +/* + * Copyright (c) 2022, Commonwealth Scientific and Industrial Research + * Organisation (CSIRO) ABN 41 687 119 230. + * + * SPDX-License-Identifier: Apache-2.0 + * + * Based on the ARM semihosting API from: + * https://github.com/ARM-software/abi-aa/blob/main/semihosting/semihosting.rst + * + * RISC-V semihosting also follows these conventions: + * https://github.com/riscv/riscv-semihosting-spec/blob/main/riscv-semihosting-spec.adoc + */ + +/** + * @file + * + * @brief public Semihosting APIs based on ARM definitions. + * @defgroup semihost Semihosting APIs + * @{ + */ + +#ifndef ZEPHYR_INCLUDE_ARCH_COMMON_SEMIHOST_H_ +#define ZEPHYR_INCLUDE_ARCH_COMMON_SEMIHOST_H_ + +/** @brief Semihosting instructions */ +enum semihost_instr { + /* + * File I/O operations + */ + + /** Open a file or stream on the host system. */ + SEMIHOST_OPEN = 0x01, + /** Check whether a file is associated with a stream/terminal */ + SEMIHOST_ISTTY = 0x09, + /** Write to a file or stream. */ + SEMIHOST_WRITE = 0x05, + /** Read from a file at the current cursor position. */ + SEMIHOST_READ = 0x06, + /** Closes a file on the host which has been opened by SEMIHOST_OPEN. */ + SEMIHOST_CLOSE = 0x02, + /** Get the length of a file. */ + SEMIHOST_FLEN = 0x0C, + /** Set the file cursor to a given position in a file. */ + SEMIHOST_SEEK = 0x0A, + /** Get a temporary absolute file path to create a temporary file. */ + SEMIHOST_TMPNAM = 0x0D, + /** Remove a file on the host system. Possibly insecure! */ + SEMIHOST_REMOVE = 0x0E, + /** Rename a file on the host system. Possibly insecure! */ + SEMIHOST_RENAME = 0x0F, + + /* + * Terminal I/O operations + */ + + /** Write one character to the debug terminal. */ + SEMIHOST_WRITEC = 0x03, + /** Write a NULL terminated string to the debug terminal. */ + SEMIHOST_WRITE0 = 0x04, + /** Read one character from the debug terminal. */ + SEMIHOST_READC = 0x07, + + /* + * Time operations + */ + SEMIHOST_CLOCK = 0x10, + SEMIHOST_ELAPSED = 0x30, + SEMIHOST_TICKFREQ = 0x31, + SEMIHOST_TIME = 0x11, + + /* + * System/Misc. operations + */ + + /** Retrieve the errno variable from semihosting operations. */ + SEMIHOST_ERRNO = 0x13, + /** Get commandline parameters for the application to run with */ + SEMIHOST_GET_CMDLINE = 0x15, + SEMIHOST_HEAPINFO = 0x16, + SEMIHOST_ISERROR = 0x08, + SEMIHOST_SYSTEM = 0x12 +}; + +/** + * @brief Modes to open a file with + * + * Behaviour corresponds to equivalent fopen strings. + * i.e. SEMIHOST_OPEN_RB_PLUS == "rb+" + */ +enum semihost_open_mode { + SEMIHOST_OPEN_R = 0, + SEMIHOST_OPEN_RB = 1, + SEMIHOST_OPEN_R_PLUS = 2, + SEMIHOST_OPEN_RB_PLUS = 3, + SEMIHOST_OPEN_W = 4, + SEMIHOST_OPEN_WB = 5, + SEMIHOST_OPEN_W_PLUS = 6, + SEMIHOST_OPEN_WB_PLUS = 7, + SEMIHOST_OPEN_A = 8, + SEMIHOST_OPEN_AB = 9, + SEMIHOST_OPEN_A_PLUS = 10, + SEMIHOST_OPEN_AB_PLUS = 11, +}; + +/** + * @brief Manually execute a semihosting instruction + * + * @param instr instruction code to run + * @param args instruction specific arguments + * + * @return integer return code of instruction + */ +long semihost_exec(enum semihost_instr instr, void *args); + +/** + * @brief Read a byte from the console + * + * @return char byte read from the console. + */ +char semihost_poll_in(void); + +/** + * @brief Write a byte to the console + * + * @param c byte to write to console + */ +void semihost_poll_out(char c); + +/** + * @brief Open a file on the host system + * + * @param path file path to open. Can be absolute or relative to current + * directory of the running process. + * @param mode value from @ref semihost_open_mode. + * + * @retval handle positive handle on success. + * @retval -1 on failure. + */ +long semihost_open(const char *path, long mode); + +/** + * @brief Close a file + * + * @param fd handle returned by @ref semihost_open. + * + * @retval 0 on success. + * @retval -1 on failure. + */ +long semihost_close(long fd); + +/** + * @brief Query the size of a file + * + * @param fd handle returned by @ref semihost_open. + * + * @retval positive file size on success. + * @retval -1 on failure. + */ +long semihost_flen(long fd); + +/** + * @brief Seeks to an absolute position in a file. + * + * @param fd handle returned by @ref semihost_open. + * @param offset offset from the start of the file in bytes. + * + * @retval 0 on success. + * @retval -errno negative error code on failure. + */ +long semihost_seek(long fd, long offset); + +/** + * @brief Read the contents of a file into a buffer. + * + * @param fd handle returned by @ref semihost_open. + * @param buf buffer to read data into. + * @param len number of bytes to read. + * + * @retval read number of bytes read on success. + * @retval -errno negative error code on failure. + */ +long semihost_read(long fd, void *buf, long len); + +/** + * @brief Write the contents of a buffer into a file. + * + * @param fd handle returned by @ref semihost_open. + * @param buf buffer to write data from. + * @param len number of bytes to write. + * + * @retval 0 on success. + * @retval -errno negative error code on failure. + */ +long semihost_write(long fd, const void *buf, long len); + +/** + * @} + */ + +#endif /* ZEPHYR_INCLUDE_ARCH_COMMON_SEMIHOST_H_ */