blob: cfa1ec3148c5a1d8d6cc3198b501fe76ee36ec64 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0+
/*
* Copyright (C) 2022 Sean Anderson <sean.anderson@seco.com>
*/
#include <common.h>
#include <dm.h>
#include <malloc.h>
#include <serial.h>
#include <semihosting.h>
/**
* struct smh_serial_priv - Semihosting serial private data
* @infd: stdin file descriptor (or error)
* @outfd: stdout file descriptor (or error)
* @counter: Counter used to fake pending every other call
*/
struct smh_serial_priv {
int infd;
int outfd;
unsigned counter;
};
#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 ssize_t smh_serial_puts(struct udevice *dev, const char *s, size_t len)
{
int ret;
struct smh_serial_priv *priv = dev_get_priv(dev);
unsigned long written;
if (priv->outfd < 0) {
char *buf;
/* Try and avoid a copy if we can */
if (!s[len + 1]) {
smh_puts(s);
return len;
}
buf = strndup(s, len);
if (!buf)
return -ENOMEM;
smh_puts(buf);
free(buf);
return len;
}
ret = smh_write(priv->outfd, s, len, &written);
if (written)
return written;
return ret;
}
static int smh_serial_pending(struct udevice *dev, bool input)
{
struct smh_serial_priv *priv = dev_get_priv(dev);
if (input)
return priv->counter++ & 1;
return false;
}
static const struct dm_serial_ops smh_serial_ops = {
.putc = smh_serial_putc,
.puts = smh_serial_puts,
.getc = smh_serial_getc,
.pending = smh_serial_pending,
};
static int smh_serial_bind(struct udevice *dev)
{
if (semihosting_enabled())
return 0;
return -ENOENT;
}
static int smh_serial_probe(struct udevice *dev)
{
struct smh_serial_priv *priv = dev_get_priv(dev);
priv->infd = smh_open(":tt", MODE_READ);
priv->outfd = smh_open(":tt", MODE_WRITE);
return 0;
}
U_BOOT_DRIVER(smh_serial) = {
.name = "serial_semihosting",
.id = UCLASS_SERIAL,
.bind = smh_serial_bind,
.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 unsigned counter = 1;
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 counter++ & 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)
{
if (semihosting_enabled())
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