1 // SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
2 /*
3 * Console IO routine for use by libc
4 *
5 * fd is the classic posix 0,1,2 (stdin, stdout, stderr)
6 *
7 * Copyright 2013-2018 IBM Corp.
8 */
9
10 #include <skiboot.h>
11 #include <unistd.h>
12 #include <console.h>
13 #include <opal.h>
14 #include <device.h>
15 #include <processor.h>
16 #include <cpu.h>
17
18 static char *con_buf = (char *)INMEM_CON_START;
19 static size_t con_in;
20 static size_t con_out;
21 static bool con_wrapped;
22
23 /* Internal console driver ops */
24 static struct con_ops *con_driver;
25
26 /* External (OPAL) console driver ops */
27 static struct opal_con_ops *opal_con_driver = &dummy_opal_con;
28
29 static struct lock con_lock = LOCK_UNLOCKED;
30
31 /* This is mapped via TCEs so we keep it alone in a page */
32 struct memcons memcons __section(".data.memcons") = {
33 .magic = CPU_TO_BE64(MEMCONS_MAGIC),
34 .obuf_phys = CPU_TO_BE64(INMEM_CON_START),
35 .ibuf_phys = CPU_TO_BE64(INMEM_CON_START + INMEM_CON_OUT_LEN),
36 .obuf_size = CPU_TO_BE32(INMEM_CON_OUT_LEN),
37 .ibuf_size = CPU_TO_BE32(INMEM_CON_IN_LEN),
38 };
39
dummy_console_enabled(void)40 static bool dummy_console_enabled(void)
41 {
42 #ifdef FORCE_DUMMY_CONSOLE
43 return true;
44 #else
45 return dt_has_node_property(dt_chosen,
46 "sapphire,enable-dummy-console", NULL);
47 #endif
48 }
49
50 /*
51 * Helper function for adding /ibm,opal/consoles/serial@<xyz> nodes
52 */
add_opal_console_node(int index,const char * type,uint32_t write_buffer_size)53 struct dt_node *add_opal_console_node(int index, const char *type,
54 uint32_t write_buffer_size)
55 {
56 struct dt_node *con, *consoles;
57 char buffer[32];
58
59 consoles = dt_find_by_name(opal_node, "consoles");
60 if (!consoles) {
61 consoles = dt_new(opal_node, "consoles");
62 assert(consoles);
63 dt_add_property_cells(consoles, "#address-cells", 1);
64 dt_add_property_cells(consoles, "#size-cells", 0);
65 }
66
67 con = dt_new_addr(consoles, "serial", index);
68 assert(con);
69
70 snprintf(buffer, sizeof(buffer), "ibm,opal-console-%s", type);
71 dt_add_property_string(con, "compatible", buffer);
72
73 dt_add_property_cells(con, "#write-buffer-size", write_buffer_size);
74 dt_add_property_cells(con, "reg", index);
75 dt_add_property_string(con, "device_type", "serial");
76
77 return con;
78 }
79
clear_console(void)80 void clear_console(void)
81 {
82 memset(con_buf, 0, INMEM_CON_LEN);
83 }
84
85 /*
86 * Flush the console buffer into the driver, returns true
87 * if there is more to go.
88 * Optionally can skip flushing to drivers, leaving messages
89 * just in memory console.
90 */
__flush_console(bool flush_to_drivers,bool need_unlock)91 static bool __flush_console(bool flush_to_drivers, bool need_unlock)
92 {
93 struct cpu_thread *cpu = this_cpu();
94 size_t req, len = 0;
95 static bool in_flush, more_flush;
96
97 /* Is there anything to flush ? Bail out early if not */
98 if (con_in == con_out || !con_driver)
99 return false;
100
101 /*
102 * Console flushing is suspended on this CPU, typically because
103 * some critical locks are held that would potentially cause a
104 * flush to deadlock
105 *
106 * Also if it recursed on con_lock (need_unlock is false). This
107 * can happen due to debug code firing (e.g., list or stack
108 * debugging).
109 */
110 if (cpu->con_suspend || !need_unlock) {
111 cpu->con_need_flush = true;
112 return false;
113 }
114 cpu->con_need_flush = false;
115
116 /*
117 * We must call the underlying driver with the console lock
118 * dropped otherwise we get some deadlocks if anything down
119 * that path tries to printf() something.
120 *
121 * So instead what we do is we keep a static in_flush flag
122 * set/released with the lock held, which is used to prevent
123 * concurrent attempts at flushing the same chunk of buffer
124 * by other processors.
125 */
126 if (in_flush) {
127 more_flush = true;
128 return false;
129 }
130 in_flush = true;
131
132 /*
133 * NB: this must appear after the in_flush check since it modifies
134 * con_out.
135 */
136 if (!flush_to_drivers) {
137 con_out = con_in;
138 in_flush = false;
139 return false;
140 }
141
142 do {
143 more_flush = false;
144
145 if (con_out > con_in) {
146 req = INMEM_CON_OUT_LEN - con_out;
147 more_flush = true;
148 } else
149 req = con_in - con_out;
150
151 unlock(&con_lock);
152 len = con_driver->write(con_buf + con_out, req);
153 lock(&con_lock);
154
155 con_out = (con_out + len) % INMEM_CON_OUT_LEN;
156
157 /* write error? */
158 if (len < req)
159 break;
160 } while(more_flush);
161
162 in_flush = false;
163 return con_out != con_in;
164 }
165
flush_console(void)166 bool flush_console(void)
167 {
168 bool ret;
169
170 lock(&con_lock);
171 ret = __flush_console(true, true);
172 unlock(&con_lock);
173
174 return ret;
175 }
176
inmem_write(char c)177 static void inmem_write(char c)
178 {
179 uint32_t opos;
180
181 if (!c)
182 return;
183 con_buf[con_in++] = c;
184 if (con_in >= INMEM_CON_OUT_LEN) {
185 con_in = 0;
186 con_wrapped = true;
187 }
188
189 /*
190 * We must always re-generate memcons.out_pos because
191 * under some circumstances, the console script will
192 * use a broken putmemproc that does RMW on the full
193 * 8 bytes containing out_pos and in_prod, thus corrupting
194 * out_pos
195 */
196 opos = con_in;
197 if (con_wrapped)
198 opos |= MEMCONS_OUT_POS_WRAP;
199 lwsync();
200 memcons.out_pos = cpu_to_be32(opos);
201
202 /* If head reaches tail, push tail around & drop chars */
203 if (con_in == con_out)
204 con_out = (con_in + 1) % INMEM_CON_OUT_LEN;
205 }
206
inmem_read(char * buf,size_t req)207 static size_t inmem_read(char *buf, size_t req)
208 {
209 size_t read = 0;
210 char *ibuf = (char *)be64_to_cpu(memcons.ibuf_phys);
211
212 while (req && be32_to_cpu(memcons.in_prod) != be32_to_cpu(memcons.in_cons)) {
213 *(buf++) = ibuf[be32_to_cpu(memcons.in_cons)];
214 lwsync();
215 memcons.in_cons = cpu_to_be32((be32_to_cpu(memcons.in_cons) + 1) % INMEM_CON_IN_LEN);
216 req--;
217 read++;
218 }
219 return read;
220 }
221
write_char(char c)222 static void write_char(char c)
223 {
224 #ifdef MAMBO_DEBUG_CONSOLE
225 mambo_console_write(&c, 1);
226 #endif
227 inmem_write(c);
228 }
229
console_write(bool flush_to_drivers,const void * buf,size_t count)230 ssize_t console_write(bool flush_to_drivers, const void *buf, size_t count)
231 {
232 /* We use recursive locking here as we can get called
233 * from fairly deep debug path
234 */
235 bool need_unlock = lock_recursive(&con_lock);
236 const char *cbuf = buf;
237
238 while(count--) {
239 char c = *(cbuf++);
240 if (c == '\n')
241 write_char('\r');
242 write_char(c);
243 }
244
245 __flush_console(flush_to_drivers, need_unlock);
246
247 if (need_unlock)
248 unlock(&con_lock);
249
250 return count;
251 }
252
write(int fd __unused,const void * buf,size_t count)253 ssize_t write(int fd __unused, const void *buf, size_t count)
254 {
255 return console_write(true, buf, count);
256 }
257
read(int fd __unused,void * buf,size_t req_count)258 ssize_t read(int fd __unused, void *buf, size_t req_count)
259 {
260 bool need_unlock = lock_recursive(&con_lock);
261 size_t count = 0;
262
263 if (con_driver && con_driver->read)
264 count = con_driver->read(buf, req_count);
265 if (!count)
266 count = inmem_read(buf, req_count);
267 if (need_unlock)
268 unlock(&con_lock);
269 return count;
270 }
271
272 /* Helper function to perform a full synchronous flush */
console_complete_flush(void)273 void console_complete_flush(void)
274 {
275 /*
276 * Using term 0 here is a dumb hack that works because the UART
277 * only has term 0 and the FSP doesn't have an explicit flush method.
278 */
279 int64_t ret = opal_con_driver->flush(0);
280
281 if (ret == OPAL_UNSUPPORTED || ret == OPAL_PARAMETER)
282 return;
283
284 while (ret != OPAL_SUCCESS) {
285 ret = opal_con_driver->flush(0);
286 }
287 }
288
289 /*
290 * set_console()
291 *
292 * This sets the driver used internally by Skiboot. This is different to the
293 * OPAL console driver.
294 */
set_console(struct con_ops * driver)295 void set_console(struct con_ops *driver)
296 {
297 con_driver = driver;
298 if (driver)
299 flush_console();
300 }
301
302 /*
303 * set_opal_console()
304 *
305 * Configure the console driver to handle the console provided by the OPAL API.
306 * They are different to the above in that they are typically buffered, and used
307 * by the host OS rather than skiboot.
308 */
309 static bool opal_cons_init = false;
310
set_opal_console(struct opal_con_ops * driver)311 void set_opal_console(struct opal_con_ops *driver)
312 {
313 assert(!opal_cons_init);
314 opal_con_driver = driver;
315 }
316
init_opal_console(void)317 void init_opal_console(void)
318 {
319 assert(!opal_cons_init);
320 opal_cons_init = true;
321
322 if (dummy_console_enabled() && opal_con_driver != &dummy_opal_con) {
323 prlog(PR_WARNING, "OPAL: Dummy console forced, %s ignored\n",
324 opal_con_driver->name);
325
326 opal_con_driver = &dummy_opal_con;
327 }
328
329 prlog(PR_INFO, "OPAL: Using %s\n", opal_con_driver->name);
330
331 if (opal_con_driver->init)
332 opal_con_driver->init();
333
334 opal_register(OPAL_CONSOLE_READ, opal_con_driver->read, 3);
335 opal_register(OPAL_CONSOLE_WRITE, opal_con_driver->write, 3);
336 opal_register(OPAL_CONSOLE_FLUSH, opal_con_driver->flush, 1);
337 opal_register(OPAL_CONSOLE_WRITE_BUFFER_SPACE,
338 opal_con_driver->space, 2);
339 }
340
memcons_add_properties(void)341 void memcons_add_properties(void)
342 {
343 dt_add_property_u64(opal_node, "ibm,opal-memcons", (u64) &memcons);
344 }
345
346 /*
347 * The default OPAL console.
348 *
349 * In the absence of a "real" OPAL console driver we handle the OPAL_CONSOLE_*
350 * calls by writing into the skiboot log buffer. Reads are a little more
351 * complicated since they can come from the in-memory console (BML) or from the
352 * internal skiboot console driver.
353 */
dummy_console_write(int64_t term_number,__be64 * length,const uint8_t * buffer)354 static int64_t dummy_console_write(int64_t term_number, __be64 *length,
355 const uint8_t *buffer)
356 {
357 uint64_t l;
358
359 if (term_number != 0)
360 return OPAL_PARAMETER;
361
362 if (!opal_addr_valid(length) || !opal_addr_valid(buffer))
363 return OPAL_PARAMETER;
364
365 l = be64_to_cpu(*length);
366 write(0, buffer, l);
367
368 return OPAL_SUCCESS;
369 }
370
dummy_console_write_buffer_space(int64_t term_number,__be64 * length)371 static int64_t dummy_console_write_buffer_space(int64_t term_number,
372 __be64 *length)
373 {
374 if (term_number != 0)
375 return OPAL_PARAMETER;
376
377 if (!opal_addr_valid(length))
378 return OPAL_PARAMETER;
379
380 if (length)
381 *length = cpu_to_be64(INMEM_CON_OUT_LEN);
382
383 return OPAL_SUCCESS;
384 }
385
dummy_console_read(int64_t term_number,__be64 * length,uint8_t * buffer)386 static int64_t dummy_console_read(int64_t term_number, __be64 *length,
387 uint8_t *buffer)
388 {
389 uint64_t l;
390
391 if (term_number != 0)
392 return OPAL_PARAMETER;
393
394 if (!opal_addr_valid(length) || !opal_addr_valid(buffer))
395 return OPAL_PARAMETER;
396
397 l = be64_to_cpu(*length);
398 l = read(0, buffer, l);
399 *length = cpu_to_be64(l);
400 opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0);
401
402 return OPAL_SUCCESS;
403 }
404
dummy_console_flush(int64_t term_number __unused)405 static int64_t dummy_console_flush(int64_t term_number __unused)
406 {
407 return OPAL_UNSUPPORTED;
408 }
409
dummy_console_poll(void * data __unused)410 static void dummy_console_poll(void *data __unused)
411 {
412 bool has_data = false;
413
414 lock(&con_lock);
415 if (con_driver && con_driver->poll_read)
416 has_data = con_driver->poll_read();
417 if (memcons.in_prod != memcons.in_cons)
418 has_data = true;
419 if (has_data)
420 opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT,
421 OPAL_EVENT_CONSOLE_INPUT);
422 else
423 opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0);
424 unlock(&con_lock);
425 }
426
dummy_console_add_nodes(void)427 void dummy_console_add_nodes(void)
428 {
429 struct dt_property *p;
430
431 add_opal_console_node(0, "raw", be32_to_cpu(memcons.obuf_size));
432
433 /* Mambo might have left a crap one, clear it */
434 p = __dt_find_property(dt_chosen, "linux,stdout-path");
435 if (p)
436 dt_del_property(dt_chosen, p);
437
438 dt_add_property_string(dt_chosen, "linux,stdout-path",
439 "/ibm,opal/consoles/serial@0");
440
441 opal_add_poller(dummy_console_poll, NULL);
442 }
443
444 struct opal_con_ops dummy_opal_con = {
445 .name = "Dummy Console",
446 .init = dummy_console_add_nodes,
447 .read = dummy_console_read,
448 .write = dummy_console_write,
449 .space = dummy_console_write_buffer_space,
450 .flush = dummy_console_flush,
451 };
452