mirror of
				https://source.denx.de/u-boot/u-boot.git
				synced 2025-10-31 16:31:25 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			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
 | |
| };
 |