From 658e2b1558d446a4dd9effc24308cfbe8d16618f Mon Sep 17 00:00:00 2001 From: Benjamin Herrenschmidt Date: Thu, 9 Oct 2014 17:23:10 +1100 Subject: uart: Give UART it's own OPAL console callbacks instead of dummy console This means the Linux output no longer gets into our internal log, which makes dumping of it from Linux a lot nicer. It will also allow us to improve the way we do the bufferring for Linux and to exploit eventually the TX interrupts. It will also allow us to implement some form of timeouts for the OPAL console variant of it so we don't get stuck of the BMC doesn't consume from the virtual UART. Signed-off-by: Benjamin Herrenschmidt --- core/console.c | 7 +- hw/lpc-uart.c | 304 +++++++++++++++++++++++++++++++--------------- include/console.h | 2 - include/skiboot.h | 4 +- platforms/astbmc/common.c | 7 +- platforms/rhesus/rhesus.c | 3 + 6 files changed, 215 insertions(+), 112 deletions(-) diff --git a/core/console.c b/core/console.c index 9b41962..111d64f 100644 --- a/core/console.c +++ b/core/console.c @@ -313,18 +313,13 @@ opal_call(OPAL_CONSOLE_READ, dummy_console_read, 3); static void dummy_console_poll(void *data __unused) { - bool uart_has_data; - lock(&con_lock); - uart_has_data = uart_console_poll(); - - if (uart_has_data || memcons.in_prod != memcons.in_cons) + if (memcons.in_prod != memcons.in_cons) 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) diff --git a/hw/lpc-uart.c b/hw/lpc-uart.c index 8391866..3d29d4c 100644 --- a/hw/lpc-uart.c +++ b/hw/lpc-uart.c @@ -23,6 +23,7 @@ #include #include #include +#include #include DEFINE_LOG_ENTRY(OPAL_RC_UART_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_UART, @@ -43,20 +44,26 @@ DEFINE_LOG_ENTRY(OPAL_RC_UART_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_UART, #define REG_MSR 6 #define REG_SCR 7 -#define LSR_DR 0x01 /* Data ready */ -#define LSR_OE 0x02 /* Overrun */ -#define LSR_PE 0x04 /* Parity error */ -#define LSR_FE 0x08 /* Framing error */ -#define LSR_BI 0x10 /* Break */ -#define LSR_THRE 0x20 /* Xmit holding register empty */ -#define LSR_TEMT 0x40 /* Xmitter empty */ -#define LSR_ERR 0x80 /* Error */ +#define LSR_DR 0x01 /* Data ready */ +#define LSR_OE 0x02 /* Overrun */ +#define LSR_PE 0x04 /* Parity error */ +#define LSR_FE 0x08 /* Framing error */ +#define LSR_BI 0x10 /* Break */ +#define LSR_THRE 0x20 /* Xmit holding register empty */ +#define LSR_TEMT 0x40 /* Xmitter empty */ +#define LSR_ERR 0x80 /* Error */ -#define LCR_DLAB 0x80 /* DLL access */ +#define LCR_DLAB 0x80 /* DLL access */ +#define IER_RX 0x01 +#define IER_THRE 0x02 + +static struct lock uart_lock = LOCK_UNLOCKED; +static struct dt_node *uart_node; static uint32_t uart_base; -static bool has_irq, irq_disabled; +static bool has_irq, rx_full, tx_full; static uint8_t tx_room; +static uint8_t cached_ier; /* * We implement a simple buffer to buffer input data as some bugs in @@ -94,32 +101,109 @@ static inline void uart_write(unsigned int reg, uint8_t val) lpc_outb(val, uart_base + reg); } +static void uart_check_tx_room(void) +{ + if (uart_read(REG_LSR) & LSR_THRE) + /* FIFO is 16 entries */ + tx_room = 16; +} + static void uart_wait_tx_room(void) { - while ((uart_read(REG_LSR) & LSR_THRE) == 0) - cpu_relax(); - /* FIFO is 16 entries */ - tx_room = 16; + while(!tx_room) { + uart_check_tx_room(); + if (!tx_room) + cpu_relax(); + } +} + +static void uart_update_ier(void) +{ + uint8_t ier = 0; + + if (!has_irq) + return; + if (!rx_full) + ier |= IER_RX; + if (tx_full) + ier |= IER_THRE; + if (ier != cached_ier) { + uart_write(REG_IER, ier); + cached_ier = ier; + } } +/* + * Internal console driver (output only) + */ static size_t uart_con_write(const char *buf, size_t len) { size_t written = 0; + lock(&uart_lock); while(written < len) { if (tx_room == 0) { uart_wait_tx_room(); if (tx_room == 0) - return written; + goto bail; } else { uart_write(REG_THR, buf[written++]); tx_room--; } } + bail: + unlock(&uart_lock); return written; } -/* Must be called with console lock held */ +static struct con_ops uart_con_driver = { + .write = uart_con_write +}; + +/* + * OPAL console driver + */ + +static int64_t uart_opal_write(int64_t term_number, int64_t *length, + const uint8_t *buffer) +{ + size_t req = *length, written = 0; + + if (term_number != 0) + return OPAL_PARAMETER; + + lock(&uart_lock); + while(written < req) { + if (tx_room == 0) { + uart_check_tx_room(); + if (tx_room == 0) + goto bail; + } else { + uart_write(REG_THR, buffer[written++]); + tx_room--; + } + } + bail: + unlock(&uart_lock); + *length = written; + + return written ? OPAL_SUCCESS : OPAL_BUSY_EVENT; +} + +static int64_t uart_opal_write_buffer_space(int64_t term_number, + int64_t *length) +{ + if (term_number != 0) + return OPAL_PARAMETER; + + lock(&uart_lock); + uart_check_tx_room(); + *length = tx_room; + unlock(&uart_lock); + return OPAL_SUCCESS; +} + +/* Must be called with UART lock held */ static void uart_read_to_buffer(void) { /* As long as there is room in the buffer */ @@ -135,38 +219,41 @@ static void uart_read_to_buffer(void) in_buf[in_count++] = uart_read(REG_RBR); } - if (!has_irq) - return; - /* If the buffer is full disable the interrupt */ - if (in_count == IN_BUF_SIZE) { - if (!irq_disabled) - uart_write(REG_IER, 0x00); - irq_disabled = true; - } else { - /* Otherwise, enable it */ - if (irq_disabled) - uart_write(REG_IER, 0x01); - irq_disabled = false; - } + rx_full = (in_count == IN_BUF_SIZE); + uart_update_ier(); +} + +static void uart_adjust_opal_event(void) +{ + if (in_count) + opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, + OPAL_EVENT_CONSOLE_INPUT); + else + opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0); } /* This is called with the console lock held */ -static size_t uart_con_read(char *buf, size_t len) +static int64_t uart_opal_read(int64_t term_number, int64_t *length, + uint8_t *buffer) { - size_t read_cnt = 0; + size_t req_count = *length, read_cnt = 0; uint8_t lsr = 0; + if (term_number != 0) + return OPAL_PARAMETER; if (!in_buf) - return 0; + return OPAL_INTERNAL_ERROR; + + lock(&uart_lock); /* Read from buffer first */ if (in_count) { read_cnt = in_count; - if (len < read_cnt) - read_cnt = len; - memcpy(buf, in_buf, read_cnt); - len -= read_cnt; + if (req_count < read_cnt) + read_cnt = req_count; + memcpy(buffer, in_buf, read_cnt); + req_count -= read_cnt; if (in_count != read_cnt) memmove(in_buf, in_buf + read_cnt, in_count - read_cnt); in_count -= read_cnt; @@ -176,65 +263,106 @@ static size_t uart_con_read(char *buf, size_t len) * If there's still room in the user buffer, read from the UART * directly */ - while(len) { + while(req_count) { lsr = uart_read(REG_LSR); if ((lsr & LSR_DR) == 0) break; - buf[read_cnt++] = uart_read(REG_RBR); - len--; + buffer[read_cnt++] = uart_read(REG_RBR); + req_count--; } /* Finally, flush whatever's left in the UART into our buffer */ uart_read_to_buffer(); + + uart_trace(TRACE_UART_CTX_READ, read_cnt, tx_full, in_count); + + unlock(&uart_lock); /* Adjust the OPAL event */ - if (in_count) - opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, - OPAL_EVENT_CONSOLE_INPUT); - else - opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, 0); - - uart_trace(TRACE_UART_CTX_READ, read_cnt, irq_disabled, in_count); + uart_adjust_opal_event(); - return read_cnt; + *length = read_cnt; + return OPAL_SUCCESS; } -static struct con_ops uart_con_driver = { - .read = uart_con_read, - .write = uart_con_write -}; +static void uart_console_poll(void *data __unused) +{ + lock(&uart_lock); + uart_read_to_buffer(); + unlock(&uart_lock); + + uart_adjust_opal_event(); +} -bool uart_console_poll(void) +void uart_irq(void) { if (!in_buf) - return false; + return; /* Grab what's in the UART and stash it into our buffer */ + lock(&uart_lock); uart_read_to_buffer(); + uart_trace(TRACE_UART_CTX_IRQ, 0, tx_full, in_count); + unlock(&uart_lock); - uart_trace(TRACE_UART_CTX_POLL, 0, irq_disabled, in_count); + uart_adjust_opal_event(); +} + +void uart_setup_linux_passthrough(void) +{ + char *path; - return !!in_count; + dt_add_property_strings(uart_node, "status", "ok"); + path = dt_get_path(uart_node); + dt_add_property_string(dt_chosen, "linux,stdout-path", path); + free(path); + printf("UART: Enabled as OS pass-through\n"); } -void uart_irq(void) +void uart_setup_opal_console(void) { - if (!in_buf) - return; + struct dt_node *con, *consoles; - /* This needs locking vs read() */ - lock(&con_lock); + /* Create OPAL console node */ + consoles = dt_new(opal_node, "consoles"); + assert(consoles); + dt_add_property_cells(consoles, "#address-cells", 1); + dt_add_property_cells(consoles, "#size-cells", 0); - /* Grab what's in the UART and stash it into our buffer */ - uart_read_to_buffer(); + con = dt_new_addr(consoles, "serial", 0); + assert(con); + dt_add_property_string(con, "compatible", "ibm,opal-console-raw"); + dt_add_property_cells(con, "#write-buffer-size", INMEM_CON_OUT_LEN); + dt_add_property_cells(con, "reg", 0); + dt_add_property_string(con, "device_type", "serial"); - /* Set the event if the buffer has anything in it */ - if (in_count) - opal_update_pending_evt(OPAL_EVENT_CONSOLE_INPUT, - OPAL_EVENT_CONSOLE_INPUT); + dt_add_property_string(dt_chosen, "linux,stdout-path", + "/ibm,opal/consoles/serial@0"); + + /* + * We mark the UART as reserved since we don't want the + * kernel to start using it with its own 8250 driver + */ + dt_add_property_strings(uart_node, "status", "reserved"); - uart_trace(TRACE_UART_CTX_IRQ, 0, irq_disabled, in_count); - unlock(&con_lock); + /* + * If the interrupt is enabled, turn on RX interrupts (and + * only these for now + */ + tx_full = rx_full = false; + uart_update_ier(); + + /* Allocate an input buffer */ + in_buf = zalloc(IN_BUF_SIZE); + printf("UART: Enabled as OS console\n"); + + /* Register OPAL APIs */ + opal_register(OPAL_CONSOLE_READ, uart_opal_read, 3); + opal_register(OPAL_CONSOLE_WRITE_BUFFER_SPACE, + uart_opal_write_buffer_space, 2); + opal_register(OPAL_CONSOLE_WRITE, uart_opal_write, 3); + + opal_add_poller(uart_console_poll, NULL); } static bool uart_init_hw(unsigned int speed, unsigned int clock) @@ -275,8 +403,13 @@ void uart_init(bool enable_interrupt) if (!lpc_present()) return; + /* UART lock is in the console path and thus must block + * printf re-entrancy + */ + uart_lock.in_con_path = true; + /* We support only one */ - n = dt_find_compatible_node(dt_root, NULL, "ns16550"); + uart_node = n = dt_find_compatible_node(dt_root, NULL, "ns16550"); if (!n) return; @@ -314,38 +447,9 @@ void uart_init(bool enable_interrupt) irqchip = dt_prop_get_u32(n, "ibm,irq-chip-id"); irq = get_psi_interrupt(irqchip) + P8_IRQ_PSI_HOST_ERR; printf("UART: IRQ connected to chip %d, irq# is 0x%x\n", irqchip, irq); - if (enable_interrupt) { + has_irq = enable_interrupt; + if (has_irq) { dt_add_property_cells(n, "interrupts", irq); dt_add_property_cells(n, "interrupt-parent", get_ics_phandle()); } - - if (dummy_console_enabled()) { - /* - * If the dummy console is enabled, we mark the UART as - * reserved since we don't want the kernel to start using it - * with its own 8250 driver - */ - dt_add_property_strings(n, "status", "reserved"); - - /* - * If the interrupt is enabled, turn on RX interrupts (and - * only these for now - */ - if (enable_interrupt) { - uart_write(REG_IER, 0x01); - has_irq = true; - irq_disabled = false; - } - - /* Allocate an input buffer */ - in_buf = zalloc(IN_BUF_SIZE); - printf("UART: Enabled as OS console\n"); - } else { - /* Else, we expose it as our chosen console */ - dt_add_property_strings(n, "status", "ok"); - path = dt_get_path(n); - dt_add_property_string(dt_chosen, "linux,stdout-path", path); - free(path); - printf("UART: Enabled as OS pass-through\n"); - } } diff --git a/include/console.h b/include/console.h index 8a47bad..bbe2444 100644 --- a/include/console.h +++ b/include/console.h @@ -66,6 +66,4 @@ extern void clear_console(void); extern void memcons_add_properties(void); extern void dummy_console_add_nodes(void); -extern bool uart_console_poll(void); - #endif /* __CONSOLE_H */ diff --git a/include/skiboot.h b/include/skiboot.h index 7ded831..41180ae 100644 --- a/include/skiboot.h +++ b/include/skiboot.h @@ -205,8 +205,10 @@ struct flash_chip; extern int flash_nvram_init(struct flash_chip *chip, uint32_t start, uint32_t size); -/* UART interrupt */ +/* UART stuff */ extern void uart_irq(void); +extern void uart_setup_linux_passthrough(void); +extern void uart_setup_opal_console(void); /* Flatten device-tree */ extern void *create_dtb(const struct dt_node *root); diff --git a/platforms/astbmc/common.c b/platforms/astbmc/common.c index 359188b..17dd66f 100644 --- a/platforms/astbmc/common.c +++ b/platforms/astbmc/common.c @@ -54,6 +54,10 @@ void astbmc_init(void) /* As soon as IPMI is up, inform BMC we are in "S0" */ ipmi_set_power_state(IPMI_PWR_SYS_S0_WORKING, IPMI_PWR_NOCHANGE); + + /* Setup UART console for use by Linux via OPAL API */ + if (!dummy_console_enabled()) + uart_setup_opal_console(); } int64_t astbmc_ipmi_power_down(uint64_t request) @@ -163,9 +167,6 @@ static void astbmc_fixup_dt(void) astbmc_fixup_dt_uart(primary_lpc); astbmc_fixup_dt_bt(primary_lpc); - - /* Force the dummy console for now */ - force_dummy_console(); } static void astbmc_fixup_psi_bar(void) diff --git a/platforms/rhesus/rhesus.c b/platforms/rhesus/rhesus.c index b749eef..bcf67ee 100644 --- a/platforms/rhesus/rhesus.c +++ b/platforms/rhesus/rhesus.c @@ -179,6 +179,9 @@ static void rhesus_init(void) { /* Initialize PNOR/NVRAM */ rhesus_pnor_init(); + + /* Setup UART for direct use by Linux */ + uart_setup_linux_passthrough(); } static void rhesus_dt_fixup_uart(struct dt_node *lpc) -- cgit v1.1