Greg Kroah-Hartman | e3b3d0f | 2017-11-06 18:11:51 +0100 | [diff] [blame] | 1 | // SPDX-License-Identifier: GPL-2.0 |
Greg Kroah-Hartman | a9f96f0 | 2017-11-06 18:11:53 +0100 | [diff] [blame] | 2 | /* Copyright (c) 2010, 2014 The Linux Foundation. All rights reserved. */ |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 3 | |
Michal Simek | d1a1af2 | 2019-10-08 16:12:06 +0200 | [diff] [blame] | 4 | #include <linux/console.h> |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 5 | #include <linux/init.h> |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 6 | #include <linux/kfifo.h> |
Elliot Berman | 602b160 | 2020-10-06 17:38:05 -0700 | [diff] [blame] | 7 | #include <linux/moduleparam.h> |
Michal Simek | d1a1af2 | 2019-10-08 16:12:06 +0200 | [diff] [blame] | 8 | #include <linux/serial.h> |
| 9 | #include <linux/serial_core.h> |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 10 | #include <linux/spinlock.h> |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 11 | |
Christopher Covington | 4061f49 | 2014-05-22 18:07:18 -0400 | [diff] [blame] | 12 | #include <asm/dcc.h> |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 13 | #include <asm/processor.h> |
| 14 | |
| 15 | #include "hvc_console.h" |
| 16 | |
Elliot Berman | 602b160 | 2020-10-06 17:38:05 -0700 | [diff] [blame] | 17 | /* |
| 18 | * Disable DCC driver at runtime. Want driver enabled for GKI, but some devices |
| 19 | * do not support the registers and crash when driver pokes the registers |
| 20 | */ |
| 21 | static bool enable; |
| 22 | module_param(enable, bool, 0444); |
| 23 | |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 24 | /* DCC Status Bits */ |
| 25 | #define DCC_STATUS_RX (1 << 30) |
| 26 | #define DCC_STATUS_TX (1 << 29) |
| 27 | |
Michal Simek | d1a1af2 | 2019-10-08 16:12:06 +0200 | [diff] [blame] | 28 | static void dcc_uart_console_putchar(struct uart_port *port, int ch) |
| 29 | { |
| 30 | while (__dcc_getstatus() & DCC_STATUS_TX) |
| 31 | cpu_relax(); |
| 32 | |
| 33 | __dcc_putchar(ch); |
| 34 | } |
| 35 | |
| 36 | static void dcc_early_write(struct console *con, const char *s, unsigned n) |
| 37 | { |
| 38 | struct earlycon_device *dev = con->data; |
| 39 | |
| 40 | uart_console_write(&dev->port, s, n, dcc_uart_console_putchar); |
| 41 | } |
| 42 | |
| 43 | static int __init dcc_early_console_setup(struct earlycon_device *device, |
| 44 | const char *opt) |
| 45 | { |
| 46 | device->con->write = dcc_early_write; |
| 47 | |
| 48 | return 0; |
| 49 | } |
| 50 | |
| 51 | EARLYCON_DECLARE(dcc, dcc_early_console_setup); |
| 52 | |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 53 | static int hvc_dcc_put_chars(uint32_t vt, const char *buf, int count) |
| 54 | { |
| 55 | int i; |
| 56 | |
| 57 | for (i = 0; i < count; i++) { |
| 58 | while (__dcc_getstatus() & DCC_STATUS_TX) |
| 59 | cpu_relax(); |
| 60 | |
Stephen Boyd | bf73bd3 | 2011-02-03 15:48:35 -0800 | [diff] [blame] | 61 | __dcc_putchar(buf[i]); |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 62 | } |
| 63 | |
| 64 | return count; |
| 65 | } |
| 66 | |
| 67 | static int hvc_dcc_get_chars(uint32_t vt, char *buf, int count) |
| 68 | { |
| 69 | int i; |
| 70 | |
Stephen Boyd | bf73bd3 | 2011-02-03 15:48:35 -0800 | [diff] [blame] | 71 | for (i = 0; i < count; ++i) |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 72 | if (__dcc_getstatus() & DCC_STATUS_RX) |
Stephen Boyd | bf73bd3 | 2011-02-03 15:48:35 -0800 | [diff] [blame] | 73 | buf[i] = __dcc_getchar(); |
| 74 | else |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 75 | break; |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 76 | |
| 77 | return i; |
| 78 | } |
| 79 | |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 80 | /* |
| 81 | * Check if the DCC is enabled. If CONFIG_HVC_DCC_SERIALIZE_SMP is enabled, |
| 82 | * then we assume then this function will be called first on core 0. That |
| 83 | * way, dcc_core0_available will be true only if it's available on core 0. |
| 84 | */ |
Rob Herring | f377775 | 2013-09-24 21:05:58 -0500 | [diff] [blame] | 85 | static bool hvc_dcc_check(void) |
| 86 | { |
| 87 | unsigned long time = jiffies + (HZ / 10); |
| 88 | |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 89 | #ifdef CONFIG_HVC_DCC_SERIALIZE_SMP |
| 90 | static bool dcc_core0_available; |
| 91 | |
| 92 | /* |
| 93 | * If we're not on core 0, but we previously confirmed that DCC is |
| 94 | * active, then just return true. |
| 95 | */ |
| 96 | if (smp_processor_id() && dcc_core0_available) |
| 97 | return true; |
| 98 | #endif |
| 99 | |
Rob Herring | f377775 | 2013-09-24 21:05:58 -0500 | [diff] [blame] | 100 | /* Write a test character to check if it is handled */ |
| 101 | __dcc_putchar('\n'); |
| 102 | |
| 103 | while (time_is_after_jiffies(time)) { |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 104 | if (!(__dcc_getstatus() & DCC_STATUS_TX)) { |
| 105 | #ifdef CONFIG_HVC_DCC_SERIALIZE_SMP |
| 106 | dcc_core0_available = true; |
| 107 | #endif |
Rob Herring | f377775 | 2013-09-24 21:05:58 -0500 | [diff] [blame] | 108 | return true; |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 109 | } |
Rob Herring | f377775 | 2013-09-24 21:05:58 -0500 | [diff] [blame] | 110 | } |
| 111 | |
| 112 | return false; |
| 113 | } |
| 114 | |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 115 | #ifdef CONFIG_HVC_DCC_SERIALIZE_SMP |
| 116 | |
| 117 | static void dcc_put_work_fn(struct work_struct *work); |
| 118 | static void dcc_get_work_fn(struct work_struct *work); |
| 119 | static DECLARE_WORK(dcc_pwork, dcc_put_work_fn); |
| 120 | static DECLARE_WORK(dcc_gwork, dcc_get_work_fn); |
| 121 | static DEFINE_SPINLOCK(dcc_lock); |
| 122 | static DEFINE_KFIFO(inbuf, unsigned char, 128); |
| 123 | static DEFINE_KFIFO(outbuf, unsigned char, 1024); |
| 124 | |
| 125 | /* |
| 126 | * Workqueue function that writes the output FIFO to the DCC on core 0. |
| 127 | */ |
| 128 | static void dcc_put_work_fn(struct work_struct *work) |
| 129 | { |
| 130 | unsigned char ch; |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 131 | unsigned long irqflags; |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 132 | |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 133 | spin_lock_irqsave(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 134 | |
| 135 | /* While there's data in the output FIFO, write it to the DCC */ |
| 136 | while (kfifo_get(&outbuf, &ch)) |
| 137 | hvc_dcc_put_chars(0, &ch, 1); |
| 138 | |
| 139 | /* While we're at it, check for any input characters */ |
| 140 | while (!kfifo_is_full(&inbuf)) { |
| 141 | if (!hvc_dcc_get_chars(0, &ch, 1)) |
| 142 | break; |
| 143 | kfifo_put(&inbuf, ch); |
| 144 | } |
| 145 | |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 146 | spin_unlock_irqrestore(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 147 | } |
| 148 | |
| 149 | /* |
| 150 | * Workqueue function that reads characters from DCC and puts them into the |
| 151 | * input FIFO. |
| 152 | */ |
| 153 | static void dcc_get_work_fn(struct work_struct *work) |
| 154 | { |
| 155 | unsigned char ch; |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 156 | unsigned long irqflags; |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 157 | |
| 158 | /* |
| 159 | * Read characters from DCC and put them into the input FIFO, as |
| 160 | * long as there is room and we have characters to read. |
| 161 | */ |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 162 | spin_lock_irqsave(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 163 | |
| 164 | while (!kfifo_is_full(&inbuf)) { |
| 165 | if (!hvc_dcc_get_chars(0, &ch, 1)) |
| 166 | break; |
| 167 | kfifo_put(&inbuf, ch); |
| 168 | } |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 169 | spin_unlock_irqrestore(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 170 | } |
| 171 | |
| 172 | /* |
| 173 | * Write characters directly to the DCC if we're on core 0 and the FIFO |
| 174 | * is empty, or write them to the FIFO if we're not. |
| 175 | */ |
| 176 | static int hvc_dcc0_put_chars(uint32_t vt, const char *buf, |
| 177 | int count) |
| 178 | { |
| 179 | int len; |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 180 | unsigned long irqflags; |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 181 | |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 182 | spin_lock_irqsave(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 183 | if (smp_processor_id() || (!kfifo_is_empty(&outbuf))) { |
| 184 | len = kfifo_in(&outbuf, buf, count); |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 185 | spin_unlock_irqrestore(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 186 | /* |
| 187 | * We just push data to the output FIFO, so schedule the |
| 188 | * workqueue that will actually write that data to DCC. |
| 189 | */ |
| 190 | schedule_work_on(0, &dcc_pwork); |
| 191 | return len; |
| 192 | } |
| 193 | |
| 194 | /* |
| 195 | * If we're already on core 0, and the FIFO is empty, then just |
| 196 | * write the data to DCC. |
| 197 | */ |
| 198 | len = hvc_dcc_put_chars(vt, buf, count); |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 199 | spin_unlock_irqrestore(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 200 | |
| 201 | return len; |
| 202 | } |
| 203 | |
| 204 | /* |
| 205 | * Read characters directly from the DCC if we're on core 0 and the FIFO |
| 206 | * is empty, or read them from the FIFO if we're not. |
| 207 | */ |
| 208 | static int hvc_dcc0_get_chars(uint32_t vt, char *buf, int count) |
| 209 | { |
| 210 | int len; |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 211 | unsigned long irqflags; |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 212 | |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 213 | spin_lock_irqsave(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 214 | |
| 215 | if (smp_processor_id() || (!kfifo_is_empty(&inbuf))) { |
| 216 | len = kfifo_out(&inbuf, buf, count); |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 217 | spin_unlock_irqrestore(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 218 | |
| 219 | /* |
| 220 | * If the FIFO was empty, there may be characters in the DCC |
| 221 | * that we haven't read yet. Schedule a workqueue to fill |
| 222 | * the input FIFO, so that the next time this function is |
| 223 | * called, we'll have data. |
| 224 | */ |
| 225 | if (!len) |
| 226 | schedule_work_on(0, &dcc_gwork); |
| 227 | |
| 228 | return len; |
| 229 | } |
| 230 | |
| 231 | /* |
| 232 | * If we're already on core 0, and the FIFO is empty, then just |
| 233 | * read the data from DCC. |
| 234 | */ |
| 235 | len = hvc_dcc_get_chars(vt, buf, count); |
Elliot Berman | 37f6718 | 2020-11-05 10:39:15 -0800 | [diff] [blame] | 236 | spin_unlock_irqrestore(&dcc_lock, irqflags); |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 237 | |
| 238 | return len; |
| 239 | } |
| 240 | |
| 241 | static const struct hv_ops hvc_dcc_get_put_ops = { |
| 242 | .get_chars = hvc_dcc0_get_chars, |
| 243 | .put_chars = hvc_dcc0_put_chars, |
| 244 | }; |
| 245 | |
| 246 | #else |
| 247 | |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 248 | static const struct hv_ops hvc_dcc_get_put_ops = { |
| 249 | .get_chars = hvc_dcc_get_chars, |
| 250 | .put_chars = hvc_dcc_put_chars, |
| 251 | }; |
| 252 | |
Timur Tabi | 61d87ac | 2015-06-26 13:52:34 -0500 | [diff] [blame] | 253 | #endif |
| 254 | |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 255 | static int __init hvc_dcc_console_init(void) |
| 256 | { |
Timur Tabi | 3d27070 | 2015-09-12 12:44:38 -0500 | [diff] [blame] | 257 | int ret; |
| 258 | |
Elliot Berman | 602b160 | 2020-10-06 17:38:05 -0700 | [diff] [blame] | 259 | if (!enable || !hvc_dcc_check()) |
Rob Herring | f377775 | 2013-09-24 21:05:58 -0500 | [diff] [blame] | 260 | return -ENODEV; |
| 261 | |
Timur Tabi | 3d27070 | 2015-09-12 12:44:38 -0500 | [diff] [blame] | 262 | /* Returns -1 if error */ |
| 263 | ret = hvc_instantiate(0, 0, &hvc_dcc_get_put_ops); |
| 264 | |
| 265 | return ret < 0 ? -ENODEV : 0; |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 266 | } |
| 267 | console_initcall(hvc_dcc_console_init); |
| 268 | |
| 269 | static int __init hvc_dcc_init(void) |
| 270 | { |
Timur Tabi | 3d27070 | 2015-09-12 12:44:38 -0500 | [diff] [blame] | 271 | struct hvc_struct *p; |
| 272 | |
Elliot Berman | 602b160 | 2020-10-06 17:38:05 -0700 | [diff] [blame] | 273 | if (!enable || !hvc_dcc_check()) |
Rob Herring | f377775 | 2013-09-24 21:05:58 -0500 | [diff] [blame] | 274 | return -ENODEV; |
| 275 | |
Timur Tabi | 3d27070 | 2015-09-12 12:44:38 -0500 | [diff] [blame] | 276 | p = hvc_alloc(0, 0, &hvc_dcc_get_put_ops, 128); |
| 277 | |
| 278 | return PTR_ERR_OR_ZERO(p); |
Daniel Walker | 16c63f8 | 2010-11-30 11:25:39 -0800 | [diff] [blame] | 279 | } |
| 280 | device_initcall(hvc_dcc_init); |