diff --git a/makefiles/stdio.inc.mk b/makefiles/stdio.inc.mk index 3696907db9..38aaf58d94 100644 --- a/makefiles/stdio.inc.mk +++ b/makefiles/stdio.inc.mk @@ -4,6 +4,7 @@ STDIO_MODULES = \ stdio_ethos \ stdio_null \ stdio_rtt \ + stdio_semihosting \ stdio_uart \ # @@ -49,3 +50,8 @@ ifeq (,$(filter stdio_cdc_acm,$(USEMODULE))) FEATURES_BLACKLIST += bootloader_arduino FEATURES_BLACKLIST += bootloader_nrfutil endif + +ifneq (,$(filter stdio_semihosting,$(USEMODULE))) + USEMODULE += xtimer + FEATURES_REQUIRED += arch_cortexm +endif diff --git a/makefiles/tools/serial.inc.mk b/makefiles/tools/serial.inc.mk index 65aad99797..ef8b2d815f 100644 --- a/makefiles/tools/serial.inc.mk +++ b/makefiles/tools/serial.inc.mk @@ -33,6 +33,5 @@ else ifeq ($(RIOT_TERMINAL),semihosting) TERMPROG = $(DEBUGGER) TERMFLAGS = $(DEBUGGER_FLAGS) OPENOCD_DBG_EXTRA_CMD += -c 'arm semihosting enable' - OPENOCD_DBG_EXTRA_CMD += -c 'set remotetimeout 10000' $(call target-export-variables,term cleanterm,OPENOCD_DBG_EXTRA_CMD) endif diff --git a/sys/include/stdio_semihosting.h b/sys/include/stdio_semihosting.h new file mode 100644 index 0000000000..6d946c2a8a --- /dev/null +++ b/sys/include/stdio_semihosting.h @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2020 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @defgroup sys_stdio_semihosting STDIO over Semihosting + * @ingroup sys + * + * @brief Standard input/output backend using ARM Semihosting + * + * The ARM Semihosting provides an STDIO backend using the ARM Semihosting + * protocol. The main advantage of Semihosting is that is allows STDIO over the + * SWD/JTAG debugging interface already available on ARM microcontrollers. + * + * ARM Semihosting works by using the breakpoint instructing to trigger the + * debugger to read the output or to write the input chars. Please be aware that + * this might skew the timing of your application. + * + * The main disadvantage of Semihosting is that it is relative slow (even when + * compared to serial uart), and that it requires an active debug session to + * handle the breakpoint instructions. Without an active debug session the CPU + * will halt on the first STDIO activity until the breakpoint is handled by the + * debugger. Don't forget to disable the Semihosting module or replace it with + * stdio_null when switching to production builds. + * + * As this is an ARM specific protocol, this module will only work on ARM-based + * microcontrollers. + * + * ## Usage + * + * Enable Semihosting-based stdio by adding the following module to your + * makefile: + * + * ``` + * USEMODULE += stdio_semihosting + * ``` + * + * If semihosting is not the default stdio mechanism of your board, the + * `RIOT_TERMINAL` variable has to be set to `semihosting`: + * + * ``` + * make term RIOT_TERMINAL=semihosting + * ``` + * + * Launching the terminal will start an OpenOCD session with semihosting + * enabled. This can be used for both STDIO interaction and for debugging the + * firmware. + * + * @{ + * @file + * + * @author Koen Zandberg + */ + +#ifndef STDIO_SEMIHOSTING_H +#define STDIO_SEMIHOSTING_H + +#include "stdio_base.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @brief Enable reception for Semihosting + * + * Automatically enabled when including the `stdin` module + */ +#define STDIO_SEMIHOSTING_RX (IS_USED(MODULE_STDIN)) + +#ifdef __cplusplus +} +#endif +/** @} */ +#endif /* STDIO_SEMIHOSTING_H */ diff --git a/sys/stdio_semihosting/Makefile b/sys/stdio_semihosting/Makefile new file mode 100644 index 0000000000..48422e909a --- /dev/null +++ b/sys/stdio_semihosting/Makefile @@ -0,0 +1 @@ +include $(RIOTBASE)/Makefile.base diff --git a/sys/stdio_semihosting/stdio_semihosting.c b/sys/stdio_semihosting/stdio_semihosting.c new file mode 100644 index 0000000000..bf5ca0c861 --- /dev/null +++ b/sys/stdio_semihosting/stdio_semihosting.c @@ -0,0 +1,137 @@ +/* + * Copyright (C) 2020 Koen Zandberg + * + * This file is subject to the terms and conditions of the GNU Lesser + * General Public License v2.1. See the file LICENSE in the top level + * directory for more details. + */ + +/** + * @ingroup sys + * @{ + * + * @file + * @brief STDIO over ARM Semihosting implementation + * + * @author Koen Zandberg + * + * @} + */ + +#include +#include + +#include "stdio_semihosting.h" +#include "xtimer.h" + +#if MODULE_VFS +#include "vfs.h" +#endif + +/** + * @brief Rate at which the stdin read polls (breaks) the debugger for input + * data + */ +#define STDIO_SEMIHOSTING_POLL_RATE (10 * US_PER_MS) + +/** + * @brief ARM Semihosting STDIN file descriptor + */ +#define STDIO_SEMIHOSTING_F_STDIN (1) + +/** + * @brief ARM Semihosting STDOUT file descriptor + */ +#define STDIO_SEMIHOSTING_F_STDOUT (1) + +/** + * @name ARM Semihosting commands + * + * Extend when required + * @{ + */ +#define STDIO_SEMIHOSTING_SYS_WRITE (0x05) /**< Write command */ +#define STDIO_SEMIHOSTING_SYS_READ (0x06) /**< Read command */ +/** @} */ + +static bool _semihosting_connected(void) { +#ifdef CoreDebug_DHCSR_C_DEBUGEN_Msk + /* Best effort attempt to detect if a debug session is active */ + return CoreDebug->DHCSR & CoreDebug_DHCSR_C_DEBUGEN_Msk; +#else + return true; +#endif +} + +static uint32_t _semihosting_raw(int cmd, uint32_t *args) +{ + uint32_t result = 0; + /* Moves cmd and args to r0 and r1. Then triggers a breakpoint. + * Finally moves the results stored in r0 to result + */ + __asm__( + "mov r0, %[cmd] \n" + "mov r1, %[args] \n" + "bkpt #0xAB \n" + "mov %[result], r0\n" + : /* Outputs */ + [result] "=r" (result) + : /* Inputs */ + [cmd] "r" (cmd), + [args] "r" (args) + : /* Clobbered registers */ + "r0", "r1", "memory" + ); + return result; +} + +static size_t _semihosting_write(const uint8_t *buffer, size_t len) +{ + uint32_t args[3] = { + STDIO_SEMIHOSTING_F_STDOUT, + (uint32_t)buffer, + (uint32_t)len, + }; + return _semihosting_raw(STDIO_SEMIHOSTING_SYS_WRITE, args); +} + +static ssize_t _semihosting_read(uint8_t *buffer, size_t len) +{ + uint32_t args[3] = { + STDIO_SEMIHOSTING_F_STDIN, + (uint32_t)buffer, + (uint32_t)len, + }; + size_t remaining = _semihosting_raw(STDIO_SEMIHOSTING_SYS_READ, args); + return len - remaining; +} + +void stdio_init(void) +{ +#if MODULE_VFS + vfs_bind_stdio(); +#endif +} + +ssize_t stdio_read(void* buffer, size_t count) +{ + if (STDIO_SEMIHOSTING_RX) { + xtimer_ticks32_t last_wakeup = xtimer_now(); + ssize_t bytes_read = _semihosting_read(buffer, count); + while (bytes_read == 0) { + xtimer_periodic_wakeup(&last_wakeup, STDIO_SEMIHOSTING_POLL_RATE); + bytes_read = _semihosting_read(buffer, count); + } + return bytes_read; + } + return -ENOTSUP; +} + +ssize_t stdio_write(const void* buffer, size_t len) +{ + if (!_semihosting_connected()) { + return len; + } + size_t remaining = _semihosting_write(buffer, len); + return len - remaining; +}