diff options
Diffstat (limited to 'drivers/serial/serial_xen.c')
| -rw-r--r-- | drivers/serial/serial_xen.c | 182 | 
1 files changed, 182 insertions, 0 deletions
| diff --git a/drivers/serial/serial_xen.c b/drivers/serial/serial_xen.c new file mode 100644 index 00000000000..ba6504b9479 --- /dev/null +++ b/drivers/serial/serial_xen.c @@ -0,0 +1,182 @@ +// 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 <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_alloc_size	= 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 +}; + | 
