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