diff mbox series

[v3,16/29] serial: Add semihosting driver

Message ID 20220322205938.1721846-17-sean.anderson@seco.com
State Accepted
Commit 74d11d37e2d33c616748d5572fd5718944826d17
Delegated to: Tom Rini
Headers show
Series arm: semihosting: Cleanups and new features | expand

Commit Message

Sean Anderson March 22, 2022, 8:59 p.m. UTC
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>
---

(no changes since v2)

Changes in v2:
- Fix baud numbers being off by 10
- Fix typos in commit message
- Rename non-DM driver struct to match format of other drivers

 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

Comments

Simon Glass March 28, 2022, 6:35 a.m. UTC | #1
Hi Sean,


On Tue, 22 Mar 2022 at 15:00, Sean Anderson <sean.anderson@seco.com> wrote:
>
> 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>
> ---
>
> (no changes since v2)
>
> Changes in v2:
> - Fix baud numbers being off by 10
> - Fix typos in commit message
> - Rename non-DM driver struct to match format of other drivers
>
>  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 345d1881f5..cc20759505 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
> @@ -778,6 +787,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 <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
> 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.25.1
>
Simon Glass March 28, 2022, 6:35 a.m. UTC | #2
Hi Sean,

On Tue, 22 Mar 2022 at 15:00, Sean Anderson <sean.anderson@seco.com> wrote:
>
> 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>
> ---
>
> (no changes since v2)
>
> Changes in v2:
> - Fix baud numbers being off by 10
> - Fix typos in commit message
> - Rename non-DM driver struct to match format of other drivers
>
>  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
>

Reviewed-by: Simon Glass <sjg@chromium.org>

But please can we drop the non-DM support?
Sean Anderson March 28, 2022, 3:36 p.m. UTC | #3
On 3/28/22 2:35 AM, Simon Glass wrote:
> Hi Sean,
> 
> On Tue, 22 Mar 2022 at 15:00, Sean Anderson <sean.anderson@seco.com> wrote:
>>
>> 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>
>> ---
>>
>> (no changes since v2)
>>
>> Changes in v2:
>> - Fix baud numbers being off by 10
>> - Fix typos in commit message
>> - Rename non-DM driver struct to match format of other drivers
>>
>>  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
>>
> 
> Reviewed-by: Simon Glass <sjg@chromium.org>
> 
> But please can we drop the non-DM support?

Unfortunately, Layerscape does not support DM serial. I tried converting
it, but I ran into some unusual aborts. At the moment, I don't have time
to debug things further. And I thought that non-DM serial was ok for
SPL?

--Sean
Tom Rini March 28, 2022, 4:03 p.m. UTC | #4
On Mon, Mar 28, 2022 at 11:36:46AM -0400, Sean Anderson wrote:
> 
> 
> On 3/28/22 2:35 AM, Simon Glass wrote:
> > Hi Sean,
> > 
> > On Tue, 22 Mar 2022 at 15:00, Sean Anderson <sean.anderson@seco.com> wrote:
> >>
> >> 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>
> >> ---
> >>
> >> (no changes since v2)
> >>
> >> Changes in v2:
> >> - Fix baud numbers being off by 10
> >> - Fix typos in commit message
> >> - Rename non-DM driver struct to match format of other drivers
> >>
> >>  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
> >>
> > 
> > Reviewed-by: Simon Glass <sjg@chromium.org>
> > 
> > But please can we drop the non-DM support?
> 
> Unfortunately, Layerscape does not support DM serial. I tried converting
> it, but I ran into some unusual aborts. At the moment, I don't have time
> to debug things further. And I thought that non-DM serial was ok for
> SPL?

It is OK for SPL, and it needs migration for non-SPL.  Can you make
another thread with your conversion-that-fails for layerscape please?
Tom Rini April 3, 2022, 12:16 a.m. UTC | #5
On Tue, Mar 22, 2022 at 04:59:24PM -0400, Sean Anderson wrote:

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

Applied to u-boot/next, thanks!
diff mbox series

Patch

diff --git a/drivers/serial/Kconfig b/drivers/serial/Kconfig
index 345d1881f5..cc20759505 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
@@ -778,6 +787,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 <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
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);