]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
serial: Add semihosting driver
authorSean Anderson <sean.anderson@seco.com>
Tue, 22 Mar 2022 20:59:24 +0000 (16:59 -0400)
committerTom Rini <trini@konsulko.com>
Fri, 1 Apr 2022 19:03:13 +0000 (15:03 -0400)
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 <sean.anderson@seco.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
drivers/serial/Kconfig
drivers/serial/Makefile
drivers/serial/serial.c
drivers/serial/serial_semihosting.c [new file with mode: 0644]
include/serial.h

index 610f8062c76bc964beba4ffaf228469076fdbfab..a07fab225fd457f44b216d024807818e72b0f268 100644 (file)
@@ -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
index 52e70aa1917b012e7c80567215c58b88d6070c23..b68b5e7b2bfd59bfa4f33cea2e2d2b537df45dc0 100644 (file)
@@ -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
index ebbd21916d77836bb56a74bb087f7eeafbdfee20..6cdbb89841c1ba238cf88afd31b482420b13f633 100644 (file)
@@ -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 (file)
index 0000000..7c7c5d9
--- /dev/null
@@ -0,0 +1,147 @@
+// SPDX-License-Identifier: GPL-2.0+
+/*
+ * Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <serial.h>
+#include <semihosting.h>
+
+/**
+ * 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 <debug_uart.h>
+
+static inline void _debug_uart_init(void)
+{
+}
+
+static inline void _debug_uart_putc(int c)
+{
+       smh_putc(c);
+}
+
+DEBUG_UART_FUNCS
+#endif
index 19a8c0c67d2006f8b14c88e6d7ff750fef972fe4..2681d26c829daafec59a0118ad7f310eba7d8ad7 100644 (file)
@@ -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);