From 74d11d37e2d33c616748d5572fd5718944826d17 Mon Sep 17 00:00:00 2001 From: Sean Anderson Date: Tue, 22 Mar 2022 16:59:24 -0400 Subject: [PATCH] serial: Add semihosting driver This adds a serial driver which uses semihosting calls to read and write to the host's console. For convenience, if CONFIG_DM_SERIAL is enabled, we will instantiate a serial driver. This allows users to enable this driver (which has no physical device) without modifying their device trees or board files. We also implement a non-DM driver for SPL, or for much faster output in U-Boot proper. There are three ways to print to the console: Method Baud ================== ===== smh_putc in a loop 170 smh_puts 1600 smh_write with :tt 20000 ================== ===== These speeds were measured using a 175 character message with a J-Link adapter. For reference, U-Boot typically prints around 2700 characters during boot on this board. There are two major factors affecting the speed of these functions. First, each breakpoint incurs a delay. Second, each debugger memory transaction incurs a delay. smh_putc has a breakpoint and memory transaction for every character. smh_puts has one breakpoint, but still has to use a transaction for every character. This is because we don't know the length up front, so OpenOCD has to check if each character is nul. smh_write has only one breakpoint and one memory transfer. DM serial drivers can only implement a putc interface, so we are stuck with the slowest API. Non-DM drivers can implement puts, which is vastly more efficient. When the driver starts up, we try to open :tt. Since this is an extension, this may fail. If it does, we fall back to smh_puts. We don't check :semihosting-features, since there are nonconforming implementations (OpenOCD) which don't implement it (but *do* implement :tt). Some semihosting implementations (QEMU) don't handle READC properly. To work around this, we try to use open/read (much like for stdin) if possible. There is no non-blocking I/O available, so we don't implement pending. This will cause __serial_tstc to always return true. If CONFIG_SERIAL_RX_BUFFER is enabled, _serial_tstc will try and read characters forever. To avoid this, we depend on this config being disabled. Signed-off-by: Sean Anderson Reviewed-by: Simon Glass --- drivers/serial/Kconfig | 22 +++++ drivers/serial/Makefile | 1 + drivers/serial/serial.c | 2 + drivers/serial/serial_semihosting.c | 147 ++++++++++++++++++++++++++++ include/serial.h | 1 + 5 files changed, 173 insertions(+) create mode 100644 drivers/serial/serial_semihosting.c diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig index 610f8062c7..a07fab225f 100644 --- a/drivers/serial/Kconfig +++ b/drivers/serial/Kconfig @@ -399,6 +399,15 @@ config DEBUG_UART_SANDBOX start up driver model. The driver will be available until the real driver model serial is running. +config DEBUG_UART_SEMIHOSTING + bool "semihosting" + depends on SEMIHOSTING_SERIAL + help + Select this to enable the debug UART using the semihosting driver. + This provides basic serial output from the console without needing to + start up driver model. The driver will be available until the real + driver model serial is running. + config DEBUG_UART_SIFIVE bool "SiFive UART" depends on SIFIVE_SERIAL @@ -782,6 +791,19 @@ config SCIF_CONSOLE on systems with RCar or SH SoCs, say Y to this option. If unsure, say N. +config SEMIHOSTING_SERIAL + bool "Semihosting UART support" + depends on SEMIHOSTING && !SERIAL_RX_BUFFER + help + Select this to enable a serial UART using semihosting. Special halt + instructions will be issued which an external debugger (such as a + JTAG emulator) may interpret. The debugger will display U-Boot's + console output on the host system. + + Enable this option only if you are using a debugger which supports + semihosting. If you are not using a debugger, this driver will halt + the boot. + config UNIPHIER_SERIAL bool "Support for UniPhier on-chip UART" depends on ARCH_UNIPHIER diff --git a/drivers/serial/Makefile b/drivers/serial/Makefile index 52e70aa191..b68b5e7b2b 100644 --- a/drivers/serial/Makefile +++ b/drivers/serial/Makefile @@ -52,6 +52,7 @@ endif obj-$(CONFIG_XILINX_UARTLITE) += serial_xuartlite.o obj-$(CONFIG_SANDBOX_SERIAL) += sandbox.o obj-$(CONFIG_SCIF_CONSOLE) += serial_sh.o +obj-$(CONFIG_SEMIHOSTING_SERIAL) += serial_semihosting.o obj-$(CONFIG_ZYNQ_SERIAL) += serial_zynq.o obj-$(CONFIG_FSL_LPUART) += serial_lpuart.o obj-$(CONFIG_FSL_LINFLEXUART) += serial_linflexuart.o diff --git a/drivers/serial/serial.c b/drivers/serial/serial.c index ebbd21916d..6cdbb89841 100644 --- a/drivers/serial/serial.c +++ b/drivers/serial/serial.c @@ -126,6 +126,7 @@ serial_initfunc(mxc_serial_initialize); serial_initfunc(ns16550_serial_initialize); serial_initfunc(pl01x_serial_initialize); serial_initfunc(pxa_serial_initialize); +serial_initfunc(smh_serial_initialize); serial_initfunc(sh_serial_initialize); serial_initfunc(mtk_serial_initialize); @@ -180,6 +181,7 @@ int serial_initialize(void) ns16550_serial_initialize(); pl01x_serial_initialize(); pxa_serial_initialize(); + smh_serial_initialize(); sh_serial_initialize(); mtk_serial_initialize(); diff --git a/drivers/serial/serial_semihosting.c b/drivers/serial/serial_semihosting.c new file mode 100644 index 0000000000..7c7c5d9455 --- /dev/null +++ b/drivers/serial/serial_semihosting.c @@ -0,0 +1,147 @@ +// SPDX-License-Identifier: GPL-2.0+ +/* + * Copyright (C) 2022 Sean Anderson + */ + +#include +#include +#include +#include + +/** + * struct smh_serial_priv - Semihosting serial private data + * @infd: stdin file descriptor (or error) + */ +struct smh_serial_priv { + int infd; + int outfd; +}; + +#if CONFIG_IS_ENABLED(DM_SERIAL) +static int smh_serial_getc(struct udevice *dev) +{ + char ch = 0; + struct smh_serial_priv *priv = dev_get_priv(dev); + + if (priv->infd < 0) + return smh_getc(); + + smh_read(priv->infd, &ch, sizeof(ch)); + return ch; +} + +static int smh_serial_putc(struct udevice *dev, const char ch) +{ + smh_putc(ch); + return 0; +} + +static const struct dm_serial_ops smh_serial_ops = { + .putc = smh_serial_putc, + .getc = smh_serial_getc, +}; + +static int smh_serial_probe(struct udevice *dev) +{ + struct smh_serial_priv *priv = dev_get_priv(dev); + + priv->infd = smh_open(":tt", MODE_READ); + return 0; +} + +U_BOOT_DRIVER(smh_serial) = { + .name = "serial_semihosting", + .id = UCLASS_SERIAL, + .probe = smh_serial_probe, + .priv_auto = sizeof(struct smh_serial_priv), + .ops = &smh_serial_ops, + .flags = DM_FLAG_PRE_RELOC, +}; + +U_BOOT_DRVINFO(smh_serial) = { + .name = "serial_semihosting", +}; +#else /* DM_SERIAL */ +static int infd = -ENODEV; +static int outfd = -ENODEV; + +static int smh_serial_start(void) +{ + infd = smh_open(":tt", MODE_READ); + outfd = smh_open(":tt", MODE_WRITE); + return 0; +} + +static int smh_serial_stop(void) +{ + if (outfd >= 0) + smh_close(outfd); + return 0; +} + +static void smh_serial_setbrg(void) +{ +} + +static int smh_serial_getc(void) +{ + char ch = 0; + + if (infd < 0) + return smh_getc(); + + smh_read(infd, &ch, sizeof(ch)); + return ch; +} + +static int smh_serial_tstc(void) +{ + return 1; +} + +static void smh_serial_puts(const char *s) +{ + ulong unused; + + if (outfd < 0) + smh_puts(s); + else + smh_write(outfd, s, strlen(s), &unused); +} + +struct serial_device serial_smh_device = { + .name = "serial_smh", + .start = smh_serial_start, + .stop = smh_serial_stop, + .setbrg = smh_serial_setbrg, + .getc = smh_serial_getc, + .tstc = smh_serial_tstc, + .putc = smh_putc, + .puts = smh_serial_puts, +}; + +void smh_serial_initialize(void) +{ + serial_register(&serial_smh_device); +} + +__weak struct serial_device *default_serial_console(void) +{ + return &serial_smh_device; +} +#endif + +#ifdef CONFIG_DEBUG_UART_SEMIHOSTING +#include + +static inline void _debug_uart_init(void) +{ +} + +static inline void _debug_uart_putc(int c) +{ + smh_putc(c); +} + +DEBUG_UART_FUNCS +#endif diff --git a/include/serial.h b/include/serial.h index 19a8c0c67d..2681d26c82 100644 --- a/include/serial.h +++ b/include/serial.h @@ -23,6 +23,7 @@ struct serial_device { void default_serial_puts(const char *s); extern struct serial_device serial_smc_device; +extern struct serial_device serial_smh_device; extern struct serial_device serial_scc_device; extern struct serial_device *default_serial_console(void); -- 2.39.5