| // 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, |
| }; |