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 <sean.anderson@seco.com>
Reviewed-by: Simon Glass <sjg@chromium.org>
This commit is contained in:
Sean Anderson 2022-03-22 16:59:24 -04:00 committed by Tom Rini
parent 3ea744e873
commit 74d11d37e2
5 changed files with 173 additions and 0 deletions

View 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

View 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

View 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();

View file

@ -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

View 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);