1// SPDX-License-Identifier: GPL-2.0
2/*
3 * (C) 2018 NXP
4 * (C) 2020 EPAM Systems Inc.
5 */
6#include <common.h>
7#include <cpu_func.h>
8#include <dm.h>
9#include <serial.h>
10#include <watchdog.h>
11#include <asm/global_data.h>
12
13#include <linux/bug.h>
14
15#include <xen/hvm.h>
16#include <xen/events.h>
17
18#include <xen/interface/sched.h>
19#include <xen/interface/hvm/hvm_op.h>
20#include <xen/interface/hvm/params.h>
21#include <xen/interface/io/console.h>
22#include <xen/interface/io/ring.h>
23
24DECLARE_GLOBAL_DATA_PTR;
25
26u32 console_evtchn;
27
28/*
29 * struct xen_uart_priv - Structure representing a Xen UART info
30 * @intf:    Console I/O interface for Xen guest OSes
31 * @evtchn:  Console event channel
32 */
33struct xen_uart_priv {
34	struct xencons_interface *intf;
35	u32 evtchn;
36};
37
38int xen_serial_setbrg(struct udevice *dev, int baudrate)
39{
40	return 0;
41}
42
43static int xen_serial_probe(struct udevice *dev)
44{
45	struct xen_uart_priv *priv = dev_get_priv(dev);
46	u64 val = 0;
47	unsigned long gfn;
48	int ret;
49
50	ret = hvm_get_parameter(HVM_PARAM_CONSOLE_EVTCHN, &val);
51	if (ret < 0 || val == 0)
52		return ret;
53
54	priv->evtchn = val;
55	console_evtchn = val;
56
57	ret = hvm_get_parameter(HVM_PARAM_CONSOLE_PFN, &val);
58	if (ret < 0)
59		return ret;
60
61	if (!val)
62		return -EINVAL;
63
64	gfn = val;
65	priv->intf = (struct xencons_interface *)(gfn << XEN_PAGE_SHIFT);
66
67	return 0;
68}
69
70static int xen_serial_pending(struct udevice *dev, bool input)
71{
72	struct xen_uart_priv *priv = dev_get_priv(dev);
73	struct xencons_interface *intf = priv->intf;
74
75	if (!input || intf->in_cons == intf->in_prod)
76		return 0;
77
78	return 1;
79}
80
81static int xen_serial_getc(struct udevice *dev)
82{
83	struct xen_uart_priv *priv = dev_get_priv(dev);
84	struct xencons_interface *intf = priv->intf;
85	XENCONS_RING_IDX cons;
86	char c;
87
88	while (intf->in_cons == intf->in_prod)
89		mb(); /* wait */
90
91	cons = intf->in_cons;
92	mb();			/* get pointers before reading ring */
93
94	c = intf->in[MASK_XENCONS_IDX(cons++, intf->in)];
95
96	mb();			/* read ring before consuming */
97	intf->in_cons = cons;
98
99	notify_remote_via_evtchn(priv->evtchn);
100
101	return c;
102}
103
104static int __write_console(struct udevice *dev, const char *data, int len)
105{
106	struct xen_uart_priv *priv = dev_get_priv(dev);
107	struct xencons_interface *intf = priv->intf;
108	XENCONS_RING_IDX cons, prod;
109	int sent = 0;
110
111	cons = intf->out_cons;
112	prod = intf->out_prod;
113	mb(); /* Update pointer */
114
115	WARN_ON((prod - cons) > sizeof(intf->out));
116
117	while ((sent < len) && ((prod - cons) < sizeof(intf->out)))
118		intf->out[MASK_XENCONS_IDX(prod++, intf->out)] = data[sent++];
119
120	mb(); /* Update data before pointer */
121	intf->out_prod = prod;
122
123	if (sent)
124		notify_remote_via_evtchn(priv->evtchn);
125
126	return sent;
127}
128
129static int write_console(struct udevice *dev, const char *data, int len)
130{
131	/*
132	 * Make sure the whole buffer is emitted, polling if
133	 * necessary.  We don't ever want to rely on the hvc daemon
134	 * because the most interesting console output is when the
135	 * kernel is crippled.
136	 */
137	while (len) {
138		int sent = __write_console(dev, data, len);
139
140		data += sent;
141		len -= sent;
142
143		if (unlikely(len))
144			HYPERVISOR_sched_op(SCHEDOP_yield, NULL);
145	}
146
147	return 0;
148}
149
150static int xen_serial_putc(struct udevice *dev, const char ch)
151{
152	write_console(dev, &ch, 1);
153
154	return 0;
155}
156
157static const struct dm_serial_ops xen_serial_ops = {
158	.putc = xen_serial_putc,
159	.getc = xen_serial_getc,
160	.pending = xen_serial_pending,
161};
162
163#if CONFIG_IS_ENABLED(OF_CONTROL)
164static const struct udevice_id xen_serial_ids[] = {
165	{ .compatible = "xen,xen" },
166	{ }
167};
168#endif
169
170U_BOOT_DRIVER(serial_xen) = {
171	.name			= "serial_xen",
172	.id			= UCLASS_SERIAL,
173#if CONFIG_IS_ENABLED(OF_CONTROL)
174	.of_match		= xen_serial_ids,
175#endif
176	.priv_auto	= sizeof(struct xen_uart_priv),
177	.probe			= xen_serial_probe,
178	.ops			= &xen_serial_ops,
179#if !CONFIG_IS_ENABLED(OF_CONTROL)
180	.flags			= DM_FLAG_PRE_RELOC,
181#endif
182};
183