2020-02-09 12:52:20 +01:00
|
|
|
/*
|
|
|
|
* 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
|
2020-09-09 11:32:30 +02:00
|
|
|
* @brief STDIO over ARM and RISC-V Semihosting implementation
|
|
|
|
*
|
|
|
|
* RISC-V semihosting closely mimics ARM semihosting. Only the break sequence is
|
|
|
|
* different, but all defined values are also used with RISC-V
|
2020-02-09 12:52:20 +01:00
|
|
|
*
|
|
|
|
* @author Koen Zandberg <koen@bergzand.net>
|
|
|
|
*
|
|
|
|
* @}
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <errno.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "stdio_semihosting.h"
|
2023-06-16 14:36:26 +02:00
|
|
|
#include "ztimer/periodic.h"
|
2020-02-09 12:52:20 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @brief Rate at which the stdin read polls (breaks) the debugger for input
|
2021-11-03 11:32:27 +01:00
|
|
|
* data in milliseconds
|
2020-02-09 12:52:20 +01:00
|
|
|
*/
|
2021-11-03 11:32:27 +01:00
|
|
|
#define STDIO_SEMIHOSTING_POLL_RATE_MS (10)
|
2020-02-09 12:52:20 +01:00
|
|
|
|
|
|
|
/**
|
2020-09-09 11:32:30 +02:00
|
|
|
* @brief ARM Semihosting STDIN file descriptor. Also used with RISC-V
|
2020-02-09 12:52:20 +01:00
|
|
|
*/
|
|
|
|
#define STDIO_SEMIHOSTING_F_STDIN (1)
|
|
|
|
|
|
|
|
/**
|
2020-09-09 11:32:30 +02:00
|
|
|
* @brief ARM Semihosting STDOUT file descriptor. Also used with RISC-V
|
2020-02-09 12:52:20 +01:00
|
|
|
*/
|
|
|
|
#define STDIO_SEMIHOSTING_F_STDOUT (1)
|
|
|
|
|
|
|
|
/**
|
2020-09-09 11:32:30 +02:00
|
|
|
* @name ARM Semihosting commands.
|
|
|
|
*
|
|
|
|
* RISC-V copied over these command names and values
|
2020-02-09 12:52:20 +01:00
|
|
|
*
|
|
|
|
* Extend when required
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
#define STDIO_SEMIHOSTING_SYS_WRITE (0x05) /**< Write command */
|
|
|
|
#define STDIO_SEMIHOSTING_SYS_READ (0x06) /**< Read command */
|
|
|
|
/** @} */
|
|
|
|
|
2023-06-16 14:36:26 +02:00
|
|
|
static ztimer_periodic_t stdin_timer;
|
|
|
|
|
2021-02-17 13:47:50 +01:00
|
|
|
#if defined(MODULE_RISCV_COMMON)
|
2020-09-09 11:32:30 +02:00
|
|
|
|
|
|
|
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__(
|
|
|
|
".option norvc \n"
|
|
|
|
"mv a0, %[cmd] \n"
|
|
|
|
"mv a1, %[args] \n"
|
|
|
|
/* Wrapping the ebreak instruction in two NOP SLLI and SRAI instructions
|
|
|
|
* act as indicator to the GDB session that this is a
|
|
|
|
* semihosting trap */
|
|
|
|
"slli x0, x0, 0x1f \n"
|
|
|
|
"ebreak \n"
|
|
|
|
"srai x0, x0, 7 \n"
|
|
|
|
"mv %[result], a0 \n"
|
|
|
|
: /* Outputs */
|
|
|
|
[result] "=r" (result)
|
|
|
|
: /* Inputs */
|
|
|
|
[cmd] "r" (cmd),
|
|
|
|
[args] "r" (args)
|
|
|
|
: /* Clobbered registers */
|
|
|
|
"a0", "a1", "memory"
|
|
|
|
);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
|
|
|
|
#elif defined(MODULE_CORTEXM_COMMON)
|
|
|
|
|
2020-02-09 12:52:20 +01:00
|
|
|
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;
|
|
|
|
}
|
2020-09-09 11:32:30 +02:00
|
|
|
#endif
|
2020-02-09 12:52:20 +01:00
|
|
|
|
2023-06-16 14:36:26 +02:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2020-02-09 12:52:20 +01:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-06-16 14:36:26 +02:00
|
|
|
static bool _read_cb(void *arg)
|
2020-02-09 12:52:20 +01:00
|
|
|
{
|
2023-06-16 14:36:26 +02:00
|
|
|
uint8_t buffer[STDIO_RX_BUFSIZE];
|
|
|
|
|
|
|
|
if (!_semihosting_connected()) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
int bytes = _semihosting_read(buffer, sizeof(buffer));
|
|
|
|
if (bytes > 0) {
|
|
|
|
isrpipe_write(arg, buffer, bytes);
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
2020-02-09 12:52:20 +01:00
|
|
|
}
|
|
|
|
|
2023-06-16 14:36:26 +02:00
|
|
|
static bool _init_done;
|
|
|
|
static void _init(void) {
|
|
|
|
if (!STDIO_SEMIHOSTING_RX || !IS_USED(MODULE_STDIO_DISPATCH)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!thread_getpid()) {
|
|
|
|
/* we can't use ztimer in early init */
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
ztimer_periodic_init(ZTIMER_MSEC, &stdin_timer, _read_cb, &stdin_isrpipe,
|
|
|
|
STDIO_SEMIHOSTING_POLL_RATE_MS);
|
|
|
|
ztimer_periodic_start(&stdin_timer);
|
|
|
|
_init_done = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void _detach(void)
|
2020-02-09 12:52:20 +01:00
|
|
|
{
|
|
|
|
if (STDIO_SEMIHOSTING_RX) {
|
2023-06-16 14:36:26 +02:00
|
|
|
ztimer_periodic_stop(&stdin_timer);
|
2020-02-09 12:52:20 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-06-16 14:36:26 +02:00
|
|
|
static ssize_t _write(const void* buffer, size_t len)
|
2020-02-09 12:52:20 +01:00
|
|
|
{
|
|
|
|
if (!_semihosting_connected()) {
|
|
|
|
return len;
|
|
|
|
}
|
2023-06-16 14:36:26 +02:00
|
|
|
if (STDIO_SEMIHOSTING_RX && IS_USED(MODULE_STDIO_DISPATCH)
|
|
|
|
&& !_init_done) {
|
|
|
|
_init();
|
|
|
|
}
|
|
|
|
|
2020-02-09 12:52:20 +01:00
|
|
|
size_t remaining = _semihosting_write(buffer, len);
|
|
|
|
return len - remaining;
|
|
|
|
}
|
2023-06-16 14:36:26 +02:00
|
|
|
|
|
|
|
#ifndef MODULE_STDIO_DISPATCH
|
|
|
|
ssize_t stdio_read(void* buffer, size_t count)
|
|
|
|
{
|
|
|
|
if (!STDIO_SEMIHOSTING_RX) {
|
|
|
|
return -ENOTSUP;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t last_wakeup = ztimer_now(ZTIMER_MSEC);
|
|
|
|
ssize_t bytes_read = _semihosting_read(buffer, count);
|
|
|
|
while (bytes_read == 0) {
|
|
|
|
ztimer_periodic_wakeup(ZTIMER_MSEC, &last_wakeup,
|
|
|
|
STDIO_SEMIHOSTING_POLL_RATE_MS);
|
|
|
|
bytes_read = _semihosting_read(buffer, count);
|
|
|
|
}
|
|
|
|
return bytes_read;
|
|
|
|
}
|
|
|
|
|
|
|
|
int stdio_available(void)
|
|
|
|
{
|
|
|
|
return -ENOTSUP;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
STDIO_PROVIDER(STDIO_SEMIHOSTING, _init, _detach, _write)
|