aboutsummaryrefslogtreecommitdiffstats
path: root/roms/skiboot/core/console.c
diff options
context:
space:
mode:
authorAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
committerAngelos Mouzakitis <a.mouzakitis@virtualopensystems.com>2023-10-10 14:33:42 +0000
commitaf1a266670d040d2f4083ff309d732d648afba2a (patch)
tree2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/core/console.c
parente02cda008591317b1625707ff8e115a4841aa889 (diff)
Add submodule dependency filesHEADmaster
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/core/console.c')
-rw-r--r--roms/skiboot/core/console.c451
1 files changed, 451 insertions, 0 deletions
diff --git a/roms/skiboot/core/console.c b/roms/skiboot/core/console.c
new file mode 100644
index 000000000..2a1509025
--- /dev/null
+++ b/roms/skiboot/core/console.c
@@ -0,0 +1,451 @@
+// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later
+/*
+ * Console IO routine for use by libc
+ *
+ * fd is the classic posix 0,1,2 (stdin, stdout, stderr)
+ *
+ * Copyright 2013-2018 IBM Corp.
+ */
+
+#include <skiboot.h>
+#include <unistd.h>
+#include <console.h>
+#include <opal.h>
+#include <device.h>
+#include <processor.h>
+#include <cpu.h>
+
+static char *con_buf = (char *)INMEM_CON_START;
+static size_t con_in;
+static size_t con_out;
+static bool con_wrapped;
+
+/* Internal console driver ops */
+static struct con_ops *con_driver;
+
+/* External (OPAL) console driver ops */
+static struct opal_con_ops *opal_con_driver = &dummy_opal_con;
+
+static struct lock con_lock = LOCK_UNLOCKED;
+
+/* This is mapped via TCEs so we keep it alone in a page */
+struct memcons memcons __section(".data.memcons") = {
+ .magic = CPU_TO_BE64(MEMCONS_MAGIC),
+ .obuf_phys = CPU_TO_BE64(INMEM_CON_START),
+ .ibuf_phys = CPU_TO_BE64(INMEM_CON_START + INMEM_CON_OUT_LEN),
+ .obuf_size = CPU_TO_BE32(INMEM_CON_OUT_LEN),
+ .ibuf_size = CPU_TO_BE32(INMEM_CON_IN_LEN),
+};
+
+static bool dummy_console_enabled(void)
+{
+#ifdef FORCE_DUMMY_CONSOLE
+ return true;
+#else
+ return dt_has_node_property(dt_chosen,
+ "sapphire,enable-dummy-console", NULL);
+#endif
+}
+
+/*
+ * Helper function for adding /ibm,opal/consoles/serial@<xyz> nodes
+ */
+struct dt_node *add_opal_console_node(int index, const char *type,
+ uint32_t write_buffer_size)
+{
+ struct dt_node *con, *consoles;
+ char buffer[32];
+
+ consoles = dt_find_by_name(opal_node, "consoles");
+ if (!consoles) {
+ consoles = dt_new(opal_node, "consoles");
+ assert(consoles);
+ dt_add_property_cells(consoles, "#address-cells", 1);
+ dt_add_property_cells(consoles, "#size-cells", 0);
+ }
+
+ con = dt_new_addr(consoles, "serial", index);
+ assert(con);
+
+ snprintf(buffer, sizeof(buffer), "ibm,opal-console-%s", type);
+ dt_add_property_string(con, "compatible", buffer);
+
+ dt_add_property_cells(con, "#write-buffer-size", write_buffer_size);
+ dt_add_property_cells(con, "reg", index);
+ dt_add_property_string(con, "device_type", "serial");
+
+ return con;
+}
+
+void clear_console(void)
+{
+ memset(con_buf, 0, INMEM_CON_LEN);
+}
+
+/*
+ * Flush the console buffer into the driver, returns true
+ * if there is more to go.
+ * Optionally can skip flushing to drivers, leaving messages
+ * just in memory console.
+ */
+static bool __flush_console(bool flush_to_drivers, bool need_unlock)
+{
+ struct cpu_thread *cpu = this_cpu();
+ size_t req, len = 0;
+ static bool in_flush, more_flush;
+
+ /* Is there anything to flush ? Bail out early if not */
+ if (con_in == con_out || !con_driver)
+ return false;
+
+ /*
+ * Console flushing is suspended on this CPU, typically because
+ * some critical locks are held that would potentially cause a
+ * flush to deadlock
+ *
+ * Also if it recursed on con_lock (need_unlock is false). This
+ * can happen due to debug code firing (e.g., list or stack
+ * debugging).
+ */
+ if (cpu->con_suspend || !need_unlock) {
+ cpu->con_need_flush = true;
+ return false;
+ }
+ cpu->con_need_flush = false;
+
+ /*
+ * We must call the underlying driver with the console lock
+ * dropped otherwise we get some deadlocks if anything down
+ * that path tries to printf() something.
+ *
+ * So instead what we do is we keep a static in_flush flag
+ * set/released with the lock held, which is used to prevent
+ * concurrent attempts at flushing the same chunk of buffer
+ * by other processors.
+ */
+ if (in_flush) {
+ more_flush = true;
+ return false;
+ }
+ in_flush = true;
+
+ /*
+ * NB: this must appear after the in_flush check since it modifies
+ * con_out.
+ */
+ if (!flush_to_drivers) {
+ con_out = con_in;
+ in_flush = false;
+ return false;
+ }
+
+ do {
+ more_flush = false;
+
+ if (con_out > con_in) {
+ req = INMEM_CON_OUT_LEN - con_out;
+ more_flush = true;
+ } else
+ req = con_in - con_out;
+
+ unlock(&con_lock);
+ len = con_driver->write(con_buf + con_out, req);
+ lock(&con_lock);
+
+ con_out = (con_out + len) % INMEM_CON_OUT_LEN;
+
+ /* write error? */
+ if (len < req)
+ break;
+ } while(more_flush);
+
+ in_flush = false;
+ return con_out != con_in;
+}
+
+bool flush_console(void)
+{
+ bool ret;
+
+ lock(&con_lock);
+ ret = __flush_console(true, true);
+ unlock(&con_lock);
+
+ return ret;
+}
+
+static void inmem_write(char c)
+{
+ uint32_t opos;
+
+ if (!c)
+ return;
+ con_buf[con_in++] = c;
+ if (con_in >= INMEM_CON_OUT_LEN) {
+ con_in = 0;
+ con_wrapped = true;
+ }
+
+ /*
+ * We must always re-generate memcons.out_pos because
+ * under some circumstances, the console script will
+ * use a broken putmemproc that does RMW on the full
+ * 8 bytes containing out_pos and in_prod, thus corrupting
+ * out_pos
+ */
+ opos = con_in;
+ if (con_wrapped)
+ opos |= MEMCONS_OUT_POS_WRAP;
+ lwsync();
+ memcons.out_pos = cpu_to_be32(opos);
+
+ /* If head reaches tail, push tail around & drop chars */
+ if (con_in == con_out)
+ con_out = (con_in + 1) % INMEM_CON_OUT_LEN;
+}
+
+static size_t inmem_read(char *buf, size_t req)
+{
+ size_t read = 0;
+ char *ibuf = (char *)be64_to_cpu(memcons.ibuf_phys);
+
+ while (req && be32_to_cpu(memcons.in_prod) != be32_to_cpu(memcons.in_cons)) {
+ *(buf++) = ibuf[be32_to_cpu(memcons.in_cons)];
+ lwsync();
+ memcons.in_cons = cpu_to_be32((be32_to_cpu(memcons.in_cons) + 1) % INMEM_CON_IN_LEN);
+ req--;
+ read++;
+ }
+ return read;
+}
+
+static void write_char(char c)
+{
+#ifdef MAMBO_DEBUG_CONSOLE
+ mambo_console_write(&c, 1);
+#endif
+ inmem_write(c);
+}
+
+ssize_t console_write(bool flush_to_drivers, const void *buf, size_t count)
+{
+ /* We use recursive locking here as we can get called
+ * from fairly deep debug path
+ */
+ bool need_unlock = lock_recursive(&con_lock);
+ const char *cbuf = buf;
+
+ while(count--) {
+ char c = *(cbuf++);
+ if (c == '\n')
+ write_char('\r');
+ write_char(c);
+ }
+
+ __flush_console(flush_to_drivers, need_unlock);
+
+ if (need_unlock)
+ unlock(&con_lock);
+
+ return count;
+}
+
+ssize_t write(int fd __unused, const void *buf, size_t count)
+{
+ return console_write(true, buf, count);
+}
+
+ssize_t read(int fd __unused, void *buf, size_t req_count)
+{
+ bool need_unlock = lock_recursive(&con_lock);
+ size_t count = 0;
+
+ if (con_driver && con_driver->read)
+ count = con_driver->read(buf, req_count);
+ if (!count)
+ count = inmem_read(buf, req_count);
+ if (need_unlock)
+ unlock(&con_lock);
+ return count;
+}
+
+/* Helper function to perform a full synchronous flush */
+void console_complete_flush(void)
+{
+ /*
+ * Using term 0 here is a dumb hack that works because the UART
+ * only has term 0 and the FSP doesn't have an explicit flush method.
+ */
+ int64_t ret = opal_con_driver->flush(0);
+
+ if (ret == OPAL_UNSUPPORTED || ret == OPAL_PARAMETER)
+ return;
+
+ while (ret != OPAL_SUCCESS) {
+ ret = opal_con_driver->flush(0);
+ }
+}
+
+/*
+ * set_console()
+ *
+ * This sets the driver used internally by Skiboot. This is different to the
+ * OPAL console driver.
+ */
+void set_console(struct con_ops *driver)
+{
+ con_driver = driver;
+ if (driver)
+ flush_console();
+}
+
+/*
+ * set_opal_console()
+ *
+ * Configure the console driver to handle the console provided by the OPAL API.
+ * They are different to the above in that they are typically buffered, and used
+ * by the host OS rather than skiboot.
+ */
+static bool opal_cons_init = false;
+
+void set_opal_console(struct opal_con_ops *driver)
+{
+ assert(!opal_cons_init);
+ opal_con_driver = driver;
+}
+
+void init_opal_console(void)
+{
+ assert(!opal_cons_init);
+ opal_cons_init = true;
+
+ if (dummy_console_enabled() && opal_con_driver != &dummy_opal_con) {
+ prlog(PR_WARNING, "OPAL: Dummy console forced, %s ignored\n",
+ opal_con_driver->name);
+
+ opal_con_driver = &dummy_opal_con;
+ }
+
+ prlog(PR_INFO, "OPAL: Using %s\n", opal_con_driver->name);
+
+ if (opal_con_driver->init)
+ opal_con_driver->init();
+
+ opal_register(OPAL_CONSOLE_READ, opal_con_driver->read, 3);
+ opal_register(OPAL_CONSOLE_WRITE, opal_con_driver->write, 3);
+ opal_register(OPAL_CONSOLE_FLUSH, opal_con_driver->flush, 1);
+ opal_register(OPAL_CONSOLE_WRITE_BUFFER_SPACE,
+ opal_con_driver->space, 2);
+}
+
+void memcons_add_properties(void)
+{
+ dt_add_property_u64(opal_node, "ibm,opal-memcons", (u64) &memcons);
+}
+
+/*
+ * The default OPAL console.
+ *
+ * In the absence of a "real" OPAL console driver we handle the OPAL_CONSOLE_*
+ * calls by writing into the skiboot log buffer. Reads are a little more
+ * complicated since they can come from the in-memory console (BML) or from the
+ * internal skiboot console driver.
+ */
+static int64_t dummy_console_write(int64_t term_number, __be64 *length,
+ const uint8_t *buffer)
+{
+ uint64_t l;
+
+ if (term_number != 0)
+ return OPAL_PARAMETER;
+
+ if (!opal_addr_valid(length) || !opal_addr_valid(buffer))
+ return OPAL_PARAMETER;
+
+ l = be64_to_cpu(*length);
+ write(0, buffer, l);
+
+ return OPAL_SUCCESS;
+}
+
+static int64_t dummy_console_write_buffer_space(int64_t term_number,
+ __be64 *length)
+{
+ if (term_number != 0)
+ return OPAL_PARAMETER;
+
+ if (!opal_addr_valid(length))
+ return OPAL_PARAMETER;
+
+ if (length)
+ *length = cpu_to_be64(INMEM_CON_OUT_LEN);
+
+ return OPAL_SUCCESS;
+}
+
+static int64_t dummy_console_read(int64_t term_number, __be64 *length,
+ uint8_t *buffer)
+{
+ uint64_t l;
+
+ if (term_number != 0)
+ return OPAL_PARAMETER;
+
+ if (!opal_addr_valid(length) || !opal_addr_valid(buffer))
+ return OPAL_PARAMETER;
+
+ l = be64_to_cpu(*length);
+ l = read(0, buffer, l);
+ *length = cpu_to_be64(l);
+ opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0);
+
+ return OPAL_SUCCESS;
+}
+
+static int64_t dummy_console_flush(int64_t term_number __unused)
+{
+ return OPAL_UNSUPPORTED;
+}
+
+static void dummy_console_poll(void *data __unused)
+{
+ bool has_data = false;
+
+ lock(&con_lock);
+ if (con_driver && con_driver->poll_read)
+ has_data = con_driver->poll_read();
+ if (memcons.in_prod != memcons.in_cons)
+ has_data = true;
+ if (has_data)
+ opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT,
+ OPAL_EVENT_CONSOLE_INPUT);
+ else
+ opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0);
+ unlock(&con_lock);
+}
+
+void dummy_console_add_nodes(void)
+{
+ struct dt_property *p;
+
+ add_opal_console_node(0, "raw", be32_to_cpu(memcons.obuf_size));
+
+ /* Mambo might have left a crap one, clear it */
+ p = __dt_find_property(dt_chosen, "linux,stdout-path");
+ if (p)
+ dt_del_property(dt_chosen, p);
+
+ dt_add_property_string(dt_chosen, "linux,stdout-path",
+ "/ibm,opal/consoles/serial@0");
+
+ opal_add_poller(dummy_console_poll, NULL);
+}
+
+struct opal_con_ops dummy_opal_con = {
+ .name = "Dummy Console",
+ .init = dummy_console_add_nodes,
+ .read = dummy_console_read,
+ .write = dummy_console_write,
+ .space = dummy_console_write_buffer_space,
+ .flush = dummy_console_flush,
+};