xref: /openbmc/linux/drivers/tty/hvc/hvc_dcc.c (revision 4f2c0a4acffbec01079c28f839422e64ddeff004)
1e3b3d0f5SGreg Kroah-Hartman // SPDX-License-Identifier: GPL-2.0
2*4419da5dSShanker Donthineni /* Copyright (c) 2010, 2014, 2022 The Linux Foundation. All rights reserved.  */
3728674a7SGreg Kroah-Hartman 
4d1a1af2cSMichal Simek #include <linux/console.h>
5*4419da5dSShanker Donthineni #include <linux/cpu.h>
6*4419da5dSShanker Donthineni #include <linux/cpumask.h>
7728674a7SGreg Kroah-Hartman #include <linux/init.h>
8*4419da5dSShanker Donthineni #include <linux/kfifo.h>
9d1a1af2cSMichal Simek #include <linux/serial.h>
10d1a1af2cSMichal Simek #include <linux/serial_core.h>
11*4419da5dSShanker Donthineni #include <linux/smp.h>
12*4419da5dSShanker Donthineni #include <linux/spinlock.h>
13728674a7SGreg Kroah-Hartman 
144061f498SChristopher Covington #include <asm/dcc.h>
15728674a7SGreg Kroah-Hartman #include <asm/processor.h>
16728674a7SGreg Kroah-Hartman 
17728674a7SGreg Kroah-Hartman #include "hvc_console.h"
18728674a7SGreg Kroah-Hartman 
19728674a7SGreg Kroah-Hartman /* DCC Status Bits */
20728674a7SGreg Kroah-Hartman #define DCC_STATUS_RX		(1 << 30)
21728674a7SGreg Kroah-Hartman #define DCC_STATUS_TX		(1 << 29)
22728674a7SGreg Kroah-Hartman 
23*4419da5dSShanker Donthineni #define DCC_INBUF_SIZE		128
24*4419da5dSShanker Donthineni #define DCC_OUTBUF_SIZE		1024
25*4419da5dSShanker Donthineni 
26*4419da5dSShanker Donthineni /* Lock to serialize access to DCC fifo */
27*4419da5dSShanker Donthineni static DEFINE_SPINLOCK(dcc_lock);
28*4419da5dSShanker Donthineni 
29*4419da5dSShanker Donthineni static DEFINE_KFIFO(inbuf, unsigned char, DCC_INBUF_SIZE);
30*4419da5dSShanker Donthineni static DEFINE_KFIFO(outbuf, unsigned char, DCC_OUTBUF_SIZE);
31*4419da5dSShanker Donthineni 
dcc_uart_console_putchar(struct uart_port * port,unsigned char ch)323f8bab17SJiri Slaby static void dcc_uart_console_putchar(struct uart_port *port, unsigned char ch)
33d1a1af2cSMichal Simek {
34d1a1af2cSMichal Simek 	while (__dcc_getstatus() & DCC_STATUS_TX)
35d1a1af2cSMichal Simek 		cpu_relax();
36d1a1af2cSMichal Simek 
37d1a1af2cSMichal Simek 	__dcc_putchar(ch);
38d1a1af2cSMichal Simek }
39d1a1af2cSMichal Simek 
dcc_early_write(struct console * con,const char * s,unsigned n)40d1a1af2cSMichal Simek static void dcc_early_write(struct console *con, const char *s, unsigned n)
41d1a1af2cSMichal Simek {
42d1a1af2cSMichal Simek 	struct earlycon_device *dev = con->data;
43d1a1af2cSMichal Simek 
44d1a1af2cSMichal Simek 	uart_console_write(&dev->port, s, n, dcc_uart_console_putchar);
45d1a1af2cSMichal Simek }
46d1a1af2cSMichal Simek 
dcc_early_console_setup(struct earlycon_device * device,const char * opt)47d1a1af2cSMichal Simek static int __init dcc_early_console_setup(struct earlycon_device *device,
48d1a1af2cSMichal Simek 					  const char *opt)
49d1a1af2cSMichal Simek {
50d1a1af2cSMichal Simek 	device->con->write = dcc_early_write;
51d1a1af2cSMichal Simek 
52d1a1af2cSMichal Simek 	return 0;
53d1a1af2cSMichal Simek }
54d1a1af2cSMichal Simek 
55d1a1af2cSMichal Simek EARLYCON_DECLARE(dcc, dcc_early_console_setup);
56d1a1af2cSMichal Simek 
hvc_dcc_put_chars(uint32_t vt,const char * buf,int count)57728674a7SGreg Kroah-Hartman static int hvc_dcc_put_chars(uint32_t vt, const char *buf, int count)
58728674a7SGreg Kroah-Hartman {
59728674a7SGreg Kroah-Hartman 	int i;
60728674a7SGreg Kroah-Hartman 
61728674a7SGreg Kroah-Hartman 	for (i = 0; i < count; i++) {
62728674a7SGreg Kroah-Hartman 		while (__dcc_getstatus() & DCC_STATUS_TX)
63728674a7SGreg Kroah-Hartman 			cpu_relax();
64728674a7SGreg Kroah-Hartman 
65bf73bd35SStephen Boyd 		__dcc_putchar(buf[i]);
66728674a7SGreg Kroah-Hartman 	}
67728674a7SGreg Kroah-Hartman 
68728674a7SGreg Kroah-Hartman 	return count;
69728674a7SGreg Kroah-Hartman }
70728674a7SGreg Kroah-Hartman 
hvc_dcc_get_chars(uint32_t vt,char * buf,int count)71728674a7SGreg Kroah-Hartman static int hvc_dcc_get_chars(uint32_t vt, char *buf, int count)
72728674a7SGreg Kroah-Hartman {
73728674a7SGreg Kroah-Hartman 	int i;
74728674a7SGreg Kroah-Hartman 
75bf73bd35SStephen Boyd 	for (i = 0; i < count; ++i)
76728674a7SGreg Kroah-Hartman 		if (__dcc_getstatus() & DCC_STATUS_RX)
77bf73bd35SStephen Boyd 			buf[i] = __dcc_getchar();
78bf73bd35SStephen Boyd 		else
79728674a7SGreg Kroah-Hartman 			break;
80728674a7SGreg Kroah-Hartman 
81728674a7SGreg Kroah-Hartman 	return i;
82728674a7SGreg Kroah-Hartman }
83728674a7SGreg Kroah-Hartman 
84*4419da5dSShanker Donthineni /*
85*4419da5dSShanker Donthineni  * Check if the DCC is enabled. If CONFIG_HVC_DCC_SERIALIZE_SMP is enabled,
86*4419da5dSShanker Donthineni  * then we assume then this function will be called first on core0. That way,
87*4419da5dSShanker Donthineni  * dcc_core0_available will be true only if it's available on core0.
88*4419da5dSShanker Donthineni  */
hvc_dcc_check(void)89f377775dSRob Herring static bool hvc_dcc_check(void)
90f377775dSRob Herring {
91f377775dSRob Herring 	unsigned long time = jiffies + (HZ / 10);
92*4419da5dSShanker Donthineni 	static bool dcc_core0_available;
93*4419da5dSShanker Donthineni 
94*4419da5dSShanker Donthineni 	/*
95*4419da5dSShanker Donthineni 	 * If we're not on core 0, but we previously confirmed that DCC is
96*4419da5dSShanker Donthineni 	 * active, then just return true.
97*4419da5dSShanker Donthineni 	 */
98*4419da5dSShanker Donthineni 	int cpu = get_cpu();
99*4419da5dSShanker Donthineni 
100*4419da5dSShanker Donthineni 	if (IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP) && cpu && dcc_core0_available) {
101*4419da5dSShanker Donthineni 		put_cpu();
102*4419da5dSShanker Donthineni 		return true;
103*4419da5dSShanker Donthineni 	}
104*4419da5dSShanker Donthineni 
105*4419da5dSShanker Donthineni 	put_cpu();
106f377775dSRob Herring 
107f377775dSRob Herring 	/* Write a test character to check if it is handled */
108f377775dSRob Herring 	__dcc_putchar('\n');
109f377775dSRob Herring 
110f377775dSRob Herring 	while (time_is_after_jiffies(time)) {
111*4419da5dSShanker Donthineni 		if (!(__dcc_getstatus() & DCC_STATUS_TX)) {
112*4419da5dSShanker Donthineni 			dcc_core0_available = true;
113f377775dSRob Herring 			return true;
114f377775dSRob Herring 		}
115*4419da5dSShanker Donthineni 	}
116f377775dSRob Herring 
117f377775dSRob Herring 	return false;
118f377775dSRob Herring }
119f377775dSRob Herring 
120*4419da5dSShanker Donthineni /*
121*4419da5dSShanker Donthineni  * Workqueue function that writes the output FIFO to the DCC on core 0.
122*4419da5dSShanker Donthineni  */
dcc_put_work(struct work_struct * work)123*4419da5dSShanker Donthineni static void dcc_put_work(struct work_struct *work)
124*4419da5dSShanker Donthineni {
125*4419da5dSShanker Donthineni 	unsigned char ch;
126*4419da5dSShanker Donthineni 	unsigned long irqflags;
127*4419da5dSShanker Donthineni 
128*4419da5dSShanker Donthineni 	spin_lock_irqsave(&dcc_lock, irqflags);
129*4419da5dSShanker Donthineni 
130*4419da5dSShanker Donthineni 	/* While there's data in the output FIFO, write it to the DCC */
131*4419da5dSShanker Donthineni 	while (kfifo_get(&outbuf, &ch))
132*4419da5dSShanker Donthineni 		hvc_dcc_put_chars(0, &ch, 1);
133*4419da5dSShanker Donthineni 
134*4419da5dSShanker Donthineni 	/* While we're at it, check for any input characters */
135*4419da5dSShanker Donthineni 	while (!kfifo_is_full(&inbuf)) {
136*4419da5dSShanker Donthineni 		if (!hvc_dcc_get_chars(0, &ch, 1))
137*4419da5dSShanker Donthineni 			break;
138*4419da5dSShanker Donthineni 		kfifo_put(&inbuf, ch);
139*4419da5dSShanker Donthineni 	}
140*4419da5dSShanker Donthineni 
141*4419da5dSShanker Donthineni 	spin_unlock_irqrestore(&dcc_lock, irqflags);
142*4419da5dSShanker Donthineni }
143*4419da5dSShanker Donthineni 
144*4419da5dSShanker Donthineni static DECLARE_WORK(dcc_pwork, dcc_put_work);
145*4419da5dSShanker Donthineni 
146*4419da5dSShanker Donthineni /*
147*4419da5dSShanker Donthineni  * Workqueue function that reads characters from DCC and puts them into the
148*4419da5dSShanker Donthineni  * input FIFO.
149*4419da5dSShanker Donthineni  */
dcc_get_work(struct work_struct * work)150*4419da5dSShanker Donthineni static void dcc_get_work(struct work_struct *work)
151*4419da5dSShanker Donthineni {
152*4419da5dSShanker Donthineni 	unsigned char ch;
153*4419da5dSShanker Donthineni 	unsigned long irqflags;
154*4419da5dSShanker Donthineni 
155*4419da5dSShanker Donthineni 	/*
156*4419da5dSShanker Donthineni 	 * Read characters from DCC and put them into the input FIFO, as
157*4419da5dSShanker Donthineni 	 * long as there is room and we have characters to read.
158*4419da5dSShanker Donthineni 	 */
159*4419da5dSShanker Donthineni 	spin_lock_irqsave(&dcc_lock, irqflags);
160*4419da5dSShanker Donthineni 
161*4419da5dSShanker Donthineni 	while (!kfifo_is_full(&inbuf)) {
162*4419da5dSShanker Donthineni 		if (!hvc_dcc_get_chars(0, &ch, 1))
163*4419da5dSShanker Donthineni 			break;
164*4419da5dSShanker Donthineni 		kfifo_put(&inbuf, ch);
165*4419da5dSShanker Donthineni 	}
166*4419da5dSShanker Donthineni 	spin_unlock_irqrestore(&dcc_lock, irqflags);
167*4419da5dSShanker Donthineni }
168*4419da5dSShanker Donthineni 
169*4419da5dSShanker Donthineni static DECLARE_WORK(dcc_gwork, dcc_get_work);
170*4419da5dSShanker Donthineni 
171*4419da5dSShanker Donthineni /*
172*4419da5dSShanker Donthineni  * Write characters directly to the DCC if we're on core 0 and the FIFO
173*4419da5dSShanker Donthineni  * is empty, or write them to the FIFO if we're not.
174*4419da5dSShanker Donthineni  */
hvc_dcc0_put_chars(u32 vt,const char * buf,int count)175*4419da5dSShanker Donthineni static int hvc_dcc0_put_chars(u32 vt, const char *buf, int count)
176*4419da5dSShanker Donthineni {
177*4419da5dSShanker Donthineni 	int len;
178*4419da5dSShanker Donthineni 	unsigned long irqflags;
179*4419da5dSShanker Donthineni 
180*4419da5dSShanker Donthineni 	if (!IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP))
181*4419da5dSShanker Donthineni 		return hvc_dcc_put_chars(vt, buf, count);
182*4419da5dSShanker Donthineni 
183*4419da5dSShanker Donthineni 	spin_lock_irqsave(&dcc_lock, irqflags);
184*4419da5dSShanker Donthineni 	if (smp_processor_id() || (!kfifo_is_empty(&outbuf))) {
185*4419da5dSShanker Donthineni 		len = kfifo_in(&outbuf, buf, count);
186*4419da5dSShanker Donthineni 		spin_unlock_irqrestore(&dcc_lock, irqflags);
187*4419da5dSShanker Donthineni 
188*4419da5dSShanker Donthineni 		/*
189*4419da5dSShanker Donthineni 		 * We just push data to the output FIFO, so schedule the
190*4419da5dSShanker Donthineni 		 * workqueue that will actually write that data to DCC.
191*4419da5dSShanker Donthineni 		 * CPU hotplug is disabled in dcc_init so CPU0 cannot be
192*4419da5dSShanker Donthineni 		 * offlined after the cpu online check.
193*4419da5dSShanker Donthineni 		 */
194*4419da5dSShanker Donthineni 		if (cpu_online(0))
195*4419da5dSShanker Donthineni 			schedule_work_on(0, &dcc_pwork);
196*4419da5dSShanker Donthineni 
197*4419da5dSShanker Donthineni 		return len;
198*4419da5dSShanker Donthineni 	}
199*4419da5dSShanker Donthineni 
200*4419da5dSShanker Donthineni 	/*
201*4419da5dSShanker Donthineni 	 * If we're already on core 0, and the FIFO is empty, then just
202*4419da5dSShanker Donthineni 	 * write the data to DCC.
203*4419da5dSShanker Donthineni 	 */
204*4419da5dSShanker Donthineni 	len = hvc_dcc_put_chars(vt, buf, count);
205*4419da5dSShanker Donthineni 	spin_unlock_irqrestore(&dcc_lock, irqflags);
206*4419da5dSShanker Donthineni 
207*4419da5dSShanker Donthineni 	return len;
208*4419da5dSShanker Donthineni }
209*4419da5dSShanker Donthineni 
210*4419da5dSShanker Donthineni /*
211*4419da5dSShanker Donthineni  * Read characters directly from the DCC if we're on core 0 and the FIFO
212*4419da5dSShanker Donthineni  * is empty, or read them from the FIFO if we're not.
213*4419da5dSShanker Donthineni  */
hvc_dcc0_get_chars(u32 vt,char * buf,int count)214*4419da5dSShanker Donthineni static int hvc_dcc0_get_chars(u32 vt, char *buf, int count)
215*4419da5dSShanker Donthineni {
216*4419da5dSShanker Donthineni 	int len;
217*4419da5dSShanker Donthineni 	unsigned long irqflags;
218*4419da5dSShanker Donthineni 
219*4419da5dSShanker Donthineni 	if (!IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP))
220*4419da5dSShanker Donthineni 		return hvc_dcc_get_chars(vt, buf, count);
221*4419da5dSShanker Donthineni 
222*4419da5dSShanker Donthineni 	spin_lock_irqsave(&dcc_lock, irqflags);
223*4419da5dSShanker Donthineni 
224*4419da5dSShanker Donthineni 	if (smp_processor_id() || (!kfifo_is_empty(&inbuf))) {
225*4419da5dSShanker Donthineni 		len = kfifo_out(&inbuf, buf, count);
226*4419da5dSShanker Donthineni 		spin_unlock_irqrestore(&dcc_lock, irqflags);
227*4419da5dSShanker Donthineni 
228*4419da5dSShanker Donthineni 		/*
229*4419da5dSShanker Donthineni 		 * If the FIFO was empty, there may be characters in the DCC
230*4419da5dSShanker Donthineni 		 * that we haven't read yet.  Schedule a workqueue to fill
231*4419da5dSShanker Donthineni 		 * the input FIFO, so that the next time this function is
232*4419da5dSShanker Donthineni 		 * called, we'll have data. CPU hotplug is disabled in dcc_init
233*4419da5dSShanker Donthineni 		 * so CPU0 cannot be offlined after the cpu online check.
234*4419da5dSShanker Donthineni 		 */
235*4419da5dSShanker Donthineni 		if (!len && cpu_online(0))
236*4419da5dSShanker Donthineni 			schedule_work_on(0, &dcc_gwork);
237*4419da5dSShanker Donthineni 
238*4419da5dSShanker Donthineni 		return len;
239*4419da5dSShanker Donthineni 	}
240*4419da5dSShanker Donthineni 
241*4419da5dSShanker Donthineni 	/*
242*4419da5dSShanker Donthineni 	 * If we're already on core 0, and the FIFO is empty, then just
243*4419da5dSShanker Donthineni 	 * read the data from DCC.
244*4419da5dSShanker Donthineni 	 */
245*4419da5dSShanker Donthineni 	len = hvc_dcc_get_chars(vt, buf, count);
246*4419da5dSShanker Donthineni 	spin_unlock_irqrestore(&dcc_lock, irqflags);
247*4419da5dSShanker Donthineni 
248*4419da5dSShanker Donthineni 	return len;
249*4419da5dSShanker Donthineni }
250*4419da5dSShanker Donthineni 
251728674a7SGreg Kroah-Hartman static const struct hv_ops hvc_dcc_get_put_ops = {
252*4419da5dSShanker Donthineni 	.get_chars = hvc_dcc0_get_chars,
253*4419da5dSShanker Donthineni 	.put_chars = hvc_dcc0_put_chars,
254728674a7SGreg Kroah-Hartman };
255728674a7SGreg Kroah-Hartman 
hvc_dcc_console_init(void)256728674a7SGreg Kroah-Hartman static int __init hvc_dcc_console_init(void)
257728674a7SGreg Kroah-Hartman {
2583d270701STimur Tabi 	int ret;
2593d270701STimur Tabi 
260f377775dSRob Herring 	if (!hvc_dcc_check())
261f377775dSRob Herring 		return -ENODEV;
262f377775dSRob Herring 
2633d270701STimur Tabi 	/* Returns -1 if error */
2643d270701STimur Tabi 	ret = hvc_instantiate(0, 0, &hvc_dcc_get_put_ops);
2653d270701STimur Tabi 
2663d270701STimur Tabi 	return ret < 0 ? -ENODEV : 0;
267728674a7SGreg Kroah-Hartman }
268728674a7SGreg Kroah-Hartman console_initcall(hvc_dcc_console_init);
269728674a7SGreg Kroah-Hartman 
hvc_dcc_init(void)270728674a7SGreg Kroah-Hartman static int __init hvc_dcc_init(void)
271728674a7SGreg Kroah-Hartman {
2723d270701STimur Tabi 	struct hvc_struct *p;
2733d270701STimur Tabi 
274f377775dSRob Herring 	if (!hvc_dcc_check())
275f377775dSRob Herring 		return -ENODEV;
276f377775dSRob Herring 
277*4419da5dSShanker Donthineni 	if (IS_ENABLED(CONFIG_HVC_DCC_SERIALIZE_SMP)) {
278*4419da5dSShanker Donthineni 		pr_warn("\n");
279*4419da5dSShanker Donthineni 		pr_warn("********************************************************************\n");
280*4419da5dSShanker Donthineni 		pr_warn("**     NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE           **\n");
281*4419da5dSShanker Donthineni 		pr_warn("**                                                                **\n");
282*4419da5dSShanker Donthineni 		pr_warn("**  HVC_DCC_SERIALIZE_SMP SUPPORT HAS BEEN ENABLED IN THIS KERNEL **\n");
283*4419da5dSShanker Donthineni 		pr_warn("**                                                                **\n");
284*4419da5dSShanker Donthineni 		pr_warn("** This means that this is a DEBUG kernel and unsafe for          **\n");
285*4419da5dSShanker Donthineni 		pr_warn("** production use and has important feature like CPU hotplug      **\n");
286*4419da5dSShanker Donthineni 		pr_warn("** disabled.                                                      **\n");
287*4419da5dSShanker Donthineni 		pr_warn("**                                                                **\n");
288*4419da5dSShanker Donthineni 		pr_warn("** If you see this message and you are not debugging the          **\n");
289*4419da5dSShanker Donthineni 		pr_warn("** kernel, report this immediately to your vendor!                **\n");
290*4419da5dSShanker Donthineni 		pr_warn("**                                                                **\n");
291*4419da5dSShanker Donthineni 		pr_warn("**     NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE NOTICE           **\n");
292*4419da5dSShanker Donthineni 		pr_warn("********************************************************************\n");
293*4419da5dSShanker Donthineni 
294*4419da5dSShanker Donthineni 		cpu_hotplug_disable();
295*4419da5dSShanker Donthineni 	}
296*4419da5dSShanker Donthineni 
2973d270701STimur Tabi 	p = hvc_alloc(0, 0, &hvc_dcc_get_put_ops, 128);
2983d270701STimur Tabi 
2993d270701STimur Tabi 	return PTR_ERR_OR_ZERO(p);
300728674a7SGreg Kroah-Hartman }
301728674a7SGreg Kroah-Hartman device_initcall(hvc_dcc_init);
302