diff options
author | 2023-10-10 14:33:42 +0000 | |
---|---|---|
committer | 2023-10-10 14:33:42 +0000 | |
commit | af1a266670d040d2f4083ff309d732d648afba2a (patch) | |
tree | 2fc46203448ddcc6f81546d379abfaeb323575e9 /roms/skiboot/core/console.c | |
parent | e02cda008591317b1625707ff8e115a4841aa889 (diff) |
Change-Id: Iaf8d18082d3991dec7c0ebbea540f092188eb4ec
Diffstat (limited to 'roms/skiboot/core/console.c')
-rw-r--r-- | roms/skiboot/core/console.c | 451 |
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, +}; |