66356b4c06
Signed-off-by: Wolfgang Denk <wd@denx.de>
183 lines
3.7 KiB
C
183 lines
3.7 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* (C) 2018 NXP
|
|
* (C) 2020 EPAM Systems Inc.
|
|
*/
|
|
#include <common.h>
|
|
#include <cpu_func.h>
|
|
#include <dm.h>
|
|
#include <serial.h>
|
|
#include <watchdog.h>
|
|
#include <asm/global_data.h>
|
|
|
|
#include <linux/bug.h>
|
|
|
|
#include <xen/hvm.h>
|
|
#include <xen/events.h>
|
|
|
|
#include <xen/interface/sched.h>
|
|
#include <xen/interface/hvm/hvm_op.h>
|
|
#include <xen/interface/hvm/params.h>
|
|
#include <xen/interface/io/console.h>
|
|
#include <xen/interface/io/ring.h>
|
|
|
|
DECLARE_GLOBAL_DATA_PTR;
|
|
|
|
u32 console_evtchn;
|
|
|
|
/*
|
|
* struct xen_uart_priv - Structure representing a Xen UART info
|
|
* @intf: Console I/O interface for Xen guest OSes
|
|
* @evtchn: Console event channel
|
|
*/
|
|
struct xen_uart_priv {
|
|
struct xencons_interface *intf;
|
|
u32 evtchn;
|
|
};
|
|
|
|
int xen_serial_setbrg(struct udevice *dev, int baudrate)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static int xen_serial_probe(struct udevice *dev)
|
|
{
|
|
struct xen_uart_priv *priv = dev_get_priv(dev);
|
|
u64 val = 0;
|
|
unsigned long gfn;
|
|
int ret;
|
|
|
|
ret = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &val);
|
|
if (ret < 0 || val == 0)
|
|
return ret;
|
|
|
|
priv->evtchn = val;
|
|
console_evtchn = val;
|
|
|
|
ret = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &val);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!val)
|
|
return -EINVAL;
|
|
|
|
gfn = val;
|
|
priv->intf = (struct xencons_interface *)(gfn << XEN_PAGE_SHIFT);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xen_serial_pending(struct udevice *dev, bool input)
|
|
{
|
|
struct xen_uart_priv *priv = dev_get_priv(dev);
|
|
struct xencons_interface *intf = priv->intf;
|
|
|
|
if (!input || intf->in_cons == intf->in_prod)
|
|
return 0;
|
|
|
|
return 1;
|
|
}
|
|
|
|
static int xen_serial_getc(struct udevice *dev)
|
|
{
|
|
struct xen_uart_priv *priv = dev_get_priv(dev);
|
|
struct xencons_interface *intf = priv->intf;
|
|
XENCONS_RING_IDX cons;
|
|
char c;
|
|
|
|
while (intf->in_cons == intf->in_prod)
|
|
mb(); /* wait */
|
|
|
|
cons = intf->in_cons;
|
|
mb(); /* get pointers before reading ring */
|
|
|
|
c = intf->in[MASK_XENCONS_IDX(cons++, intf->in)];
|
|
|
|
mb(); /* read ring before consuming */
|
|
intf->in_cons = cons;
|
|
|
|
notify_remote_via_evtchn(priv->evtchn);
|
|
|
|
return c;
|
|
}
|
|
|
|
static int __write_console(struct udevice *dev, const char *data, int len)
|
|
{
|
|
struct xen_uart_priv *priv = dev_get_priv(dev);
|
|
struct xencons_interface *intf = priv->intf;
|
|
XENCONS_RING_IDX cons, prod;
|
|
int sent = 0;
|
|
|
|
cons = intf->out_cons;
|
|
prod = intf->out_prod;
|
|
mb(); /* Update pointer */
|
|
|
|
WARN_ON((prod - cons) > sizeof(intf->out));
|
|
|
|
while ((sent < len) && ((prod - cons) < sizeof(intf->out)))
|
|
intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++];
|
|
|
|
mb(); /* Update data before pointer */
|
|
intf->out_prod = prod;
|
|
|
|
if (sent)
|
|
notify_remote_via_evtchn(priv->evtchn);
|
|
|
|
return sent;
|
|
}
|
|
|
|
static int write_console(struct udevice *dev, const char *data, int len)
|
|
{
|
|
/*
|
|
* Make sure the whole buffer is emitted, polling if
|
|
* necessary. We don't ever want to rely on the hvc daemon
|
|
* because the most interesting console output is when the
|
|
* kernel is crippled.
|
|
*/
|
|
while (len) {
|
|
int sent = __write_console(dev, data, len);
|
|
|
|
data += sent;
|
|
len -= sent;
|
|
|
|
if (unlikely(len))
|
|
HYPERVISOR_sched_op(SCHEDOP_yield, NULL);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int xen_serial_putc(struct udevice *dev, const char ch)
|
|
{
|
|
write_console(dev, &ch, 1);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct dm_serial_ops xen_serial_ops = {
|
|
.putc = xen_serial_putc,
|
|
.getc = xen_serial_getc,
|
|
.pending = xen_serial_pending,
|
|
};
|
|
|
|
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
|
static const struct udevice_id xen_serial_ids[] = {
|
|
{ .compatible = "xen,xen" },
|
|
{ }
|
|
};
|
|
#endif
|
|
|
|
U_BOOT_DRIVER(serial_xen) = {
|
|
.name = "serial_xen",
|
|
.id = UCLASS_SERIAL,
|
|
#if CONFIG_IS_ENABLED(OF_CONTROL)
|
|
.of_match = xen_serial_ids,
|
|
#endif
|
|
.priv_auto = sizeof(struct xen_uart_priv),
|
|
.probe = xen_serial_probe,
|
|
.ops = &xen_serial_ops,
|
|
#if !CONFIG_IS_ENABLED(OF_CONTROL)
|
|
.flags = DM_FLAG_PRE_RELOC,
|
|
#endif
|
|
};
|