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