blob: e05805f63726a399cfd030546939bc039627dd8c [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0
/*
* (C) 2018 NXP
* (C) 2020 EPAM Systems Inc.
*/
#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
};