diff options
Diffstat (limited to 'roms/skiboot/hw/lpc-uart.c')
-rw-r--r-- | roms/skiboot/hw/lpc-uart.c | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/roms/skiboot/hw/lpc-uart.c b/roms/skiboot/hw/lpc-uart.c new file mode 100644 index 000000000..834011b37 --- /dev/null +++ b/roms/skiboot/hw/lpc-uart.c @@ -0,0 +1,738 @@ +// SPDX-License-Identifier: Apache-2.0 OR GPL-2.0-or-later +/* + * Serial port hanging off LPC + * + * Copyright 2013-2019 IBM Corp. + */ + +#include <skiboot.h> +#include <lpc.h> +#include <console.h> +#include <opal.h> +#include <device.h> +#include <interrupts.h> +#include <processor.h> +#include <errorlog.h> +#include <trace.h> +#include <timebase.h> +#include <cpu.h> +#include <chip.h> +#include <io.h> +#include <nvram.h> + +DEFINE_LOG_ENTRY(OPAL_RC_UART_INIT, OPAL_PLATFORM_ERR_EVT, OPAL_UART, + OPAL_CEC_HARDWARE, OPAL_PREDICTIVE_ERR_GENERAL, + OPAL_NA); + +/* UART reg defs */ +#define REG_RBR 0 +#define REG_THR 0 +#define REG_DLL 0 +#define REG_IER 1 +#define REG_DLM 1 +#define REG_FCR 2 +#define REG_IIR 2 +#define REG_LCR 3 +#define REG_MCR 4 +#define REG_LSR 5 +#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 LCR_DLAB 0x80 /* DLL access */ + +#define IER_RX 0x01 +#define IER_THRE 0x02 +#define IER_ALL 0x0f + +static struct lock uart_lock = LOCK_UNLOCKED; +static struct dt_node *uart_node; +static uint32_t uart_base; +static uint64_t uart_tx_full_time; +static bool has_irq = false, irq_ok, rx_full, tx_full; +static uint8_t tx_room; +static uint8_t cached_ier; +static void *mmio_uart_base; +static int uart_console_policy = UART_CONSOLE_OPAL; +static int lpc_irq = -1; + +void uart_set_console_policy(int policy) +{ + uart_console_policy = policy; +} + +static void uart_trace(u8 ctx, u8 cnt, u8 irq_state, u8 in_count) +{ + union trace t; + + t.uart.ctx = ctx; + t.uart.cnt = cnt; + t.uart.irq_state = irq_state; + t.uart.in_count = cpu_to_be16(in_count); + trace_add(&t, TRACE_UART, sizeof(struct trace_uart)); +} + +static inline uint8_t uart_read(unsigned int reg) +{ + if (mmio_uart_base) + return in_8(mmio_uart_base + reg); + else + return lpc_inb(uart_base + reg); +} + +static inline void uart_write(unsigned int reg, uint8_t val) +{ + if (mmio_uart_base) + out_8(mmio_uart_base + reg, val); + else + lpc_outb(val, uart_base + reg); +} + +static bool uart_check_tx_room(void) +{ + if (tx_room) + return true; + + if (uart_read(REG_LSR) & LSR_THRE) { + /* FIFO is 16 entries */ + tx_room = 16; + tx_full = false; + return true; + } + + return false; +} + +/* Must be called with UART lock held */ +static void uart_write_thr(uint8_t val) +{ + uart_write(REG_THR, val); + + tx_room--; + if (tx_room == 0) { + if (!uart_check_tx_room()) + uart_tx_full_time = mftb(); + } +} + +static bool uart_timed_out(unsigned long msecs) +{ + if (uart_check_tx_room()) + return false; + + if (chip_quirk(QUIRK_SLOW_SIM)) + msecs *= 5; + + if (tb_compare(mftb(), uart_tx_full_time + msecs_to_tb(msecs)) == TB_AAFTERB) + return true; + + return false; +} + +static bool uart_wait_tx_room(void) +{ + if (uart_check_tx_room()) + return true; + + smt_lowest(); + while (!uart_check_tx_room()) { + if (uart_timed_out(100)) { + smt_medium(); + return false; + } + } + smt_medium(); + + return true; +} + +static void uart_update_ier(void) +{ + uint8_t ier = 0; + + if (!has_irq) + return; + + /* If we have never got an interrupt, enable them all, + * the first interrupt received will tell us if interrupts + * are functional (some boards are missing an EC or FPGA + * programming causing LPC interrupts not to work). + */ + if (!irq_ok) + ier = IER_ALL; + if (!rx_full) + ier |= IER_RX; + if (tx_full) + ier |= IER_THRE; + if (ier != cached_ier) { + uart_write(REG_IER, ier); + cached_ier = ier; + } +} + +bool uart_enabled(void) +{ + return mmio_uart_base || uart_base; +} + +/* + * Internal console driver (output only) + */ +static size_t uart_con_write(const char *buf, size_t len) +{ + size_t written = 0; + + /* If LPC bus is bad, we just swallow data */ + if (!lpc_ok() && !mmio_uart_base) + return len; + + lock(&uart_lock); + while (written < len) { + if (!uart_wait_tx_room()) + break; + + uart_write_thr(buf[written++]); + } + + if (!written && uart_timed_out(1000)) { + unlock(&uart_lock); + return len; /* swallow data */ + } + + unlock(&uart_lock); + + return written; +} + +static struct con_ops uart_con_driver = { + .write = uart_con_write, +}; + +/* + * OPAL console driver + */ + +/* + * We implement a simple buffer to buffer input data as some bugs in + * Linux make it fail to read fast enough after we get an interrupt. + * + * We use it on non-interrupt operations as well while at it because + * it doesn't cost us much and might help in a few cases where Linux + * is calling opal_poll_events() but not actually reading. + * + * Most of the time I expect we'll flush it completely to Linux into + * it's tty flip buffers so I don't bother with a ring buffer. + */ +#define IN_BUF_SIZE 0x1000 +static uint8_t *in_buf; +static uint32_t in_count; + +/* + * We implement a ring buffer for output data as well to speed things + * up a bit. This allows us to have interrupt driven sends. This is only + * for the output data coming from the OPAL API, not the internal one + * which is already bufferred. + */ +#define OUT_BUF_SIZE 0x1000 +static uint8_t *out_buf; +static uint32_t out_buf_prod; +static uint32_t out_buf_cons; + +/* Asynchronous flush, uart_lock must be held */ +static int64_t uart_con_flush(void) +{ + bool tx_was_full = tx_full; + uint32_t out_buf_cons_initial = out_buf_cons; + + while(out_buf_prod != out_buf_cons) { + if (tx_room == 0) { + /* + * If the interrupt is not functional, + * we force a full synchronous flush, + * otherwise the Linux console isn't + * usable (too slow). + */ + if (irq_ok) + uart_check_tx_room(); + else + uart_wait_tx_room(); + } + if (tx_room == 0) { + tx_full = true; + break; + } + + uart_write_thr(out_buf[out_buf_cons++]); + out_buf_cons %= OUT_BUF_SIZE; + } + if (tx_full != tx_was_full) + uart_update_ier(); + if (out_buf_prod != out_buf_cons) { + /* Return busy if nothing was flushed this call */ + if (out_buf_cons == out_buf_cons_initial) { + if (uart_timed_out(1000)) + return OPAL_TIMEOUT; + return OPAL_BUSY; + } + /* Return partial if there's more to flush */ + return OPAL_PARTIAL; + } + + return OPAL_SUCCESS; +} + +static uint32_t uart_tx_buf_space(void) +{ + return OUT_BUF_SIZE - 1 - + (out_buf_prod + OUT_BUF_SIZE - out_buf_cons) % OUT_BUF_SIZE; +} + +static int64_t uart_opal_write(int64_t term_number, __be64 *__length, + const uint8_t *buffer) +{ + size_t written = 0, len = be64_to_cpu(*__length); + int64_t ret = OPAL_SUCCESS; + + if (term_number != 0) + return OPAL_PARAMETER; + + lock(&uart_lock); + + /* Copy data to out buffer */ + while (uart_tx_buf_space() && len--) { + out_buf[out_buf_prod++] = *(buffer++); + out_buf_prod %= OUT_BUF_SIZE; + written++; + } + + /* Flush out buffer again */ + uart_con_flush(); + + if (!written && uart_timed_out(1000)) + ret = OPAL_TIMEOUT; + unlock(&uart_lock); + + *__length = cpu_to_be64(written); + + return ret; +} + +static int64_t uart_opal_write_buffer_space(int64_t term_number, + __be64 *__length) +{ + int64_t ret = OPAL_SUCCESS; + int64_t tx_buf_len; + + if (term_number != 0) + return OPAL_PARAMETER; + + lock(&uart_lock); + tx_buf_len = uart_tx_buf_space(); + + if ((tx_buf_len < be64_to_cpu(*__length)) && uart_timed_out(1000)) + ret = OPAL_TIMEOUT; + + *__length = cpu_to_be64(tx_buf_len); + unlock(&uart_lock); + + return ret; +} + +/* Must be called with UART lock held */ +static void uart_read_to_buffer(void) +{ + /* As long as there is room in the buffer */ + while(in_count < IN_BUF_SIZE) { + /* Read status register */ + uint8_t lsr = uart_read(REG_LSR); + + /* Nothing to read ... */ + if ((lsr & LSR_DR) == 0) + break; + + /* Read and add to buffer */ + in_buf[in_count++] = uart_read(REG_RBR); + } + + /* If the buffer is full disable the interrupt */ + 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 int64_t uart_opal_read(int64_t term_number, __be64 *__length, + uint8_t *buffer) +{ + size_t req_count = be64_to_cpu(*__length), read_cnt = 0; + uint8_t lsr = 0; + + if (term_number != 0) + return OPAL_PARAMETER; + if (!in_buf) + return OPAL_INTERNAL_ERROR; + + lock(&uart_lock); + + /* Read from buffer first */ + if (in_count) { + read_cnt = in_count; + 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; + } + + /* + * If there's still room in the user buffer, read from the UART + * directly + */ + while(req_count) { + lsr = uart_read(REG_LSR); + if ((lsr & LSR_DR) == 0) + break; + 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 */ + uart_adjust_opal_event(); + + *__length = cpu_to_be64(read_cnt); + return OPAL_SUCCESS; +} + +static int64_t uart_opal_flush(int64_t term_number) +{ + int64_t rc; + + if (term_number != 0) + return OPAL_PARAMETER; + + lock(&uart_lock); + rc = uart_con_flush(); + unlock(&uart_lock); + + return rc; +} + +static void __uart_do_poll(u8 trace_ctx) +{ + if (!in_buf) + return; + + lock(&uart_lock); + uart_read_to_buffer(); + uart_con_flush(); + uart_trace(trace_ctx, 0, tx_full, in_count); + unlock(&uart_lock); + + uart_adjust_opal_event(); +} + +static void uart_console_poll(void *data __unused) +{ + __uart_do_poll(TRACE_UART_CTX_POLL); +} + +static void uart_irq(uint32_t chip_id __unused, uint32_t irq_mask __unused) +{ + if (!irq_ok) { + prlog(PR_DEBUG, "UART: IRQ functional !\n"); + irq_ok = true; + } + __uart_do_poll(TRACE_UART_CTX_IRQ); +} + +/* + * Common setup/inits + */ + +static void uart_setup_os_passthrough(void) +{ + char *path; + + static struct lpc_client uart_lpc_os_client = { + .reset = NULL, + .interrupt = NULL, + .interrupts = 0 + }; + + 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); + + /* Setup LPC client for OS interrupts */ + if (lpc_irq >= 0) { + uint32_t chip_id = dt_get_chip_id(uart_node); + uart_lpc_os_client.interrupts = LPC_IRQ(lpc_irq); + lpc_register_client(chip_id, &uart_lpc_os_client, + IRQ_ATTR_TARGET_LINUX); + } + prlog(PR_DEBUG, "UART: Enabled as OS pass-through\n"); +} + +static void uart_setup_opal_console(void) +{ + static struct lpc_client uart_lpc_opal_client = { + .interrupt = uart_irq, + }; + + /* Add the opal console node */ + add_opal_console_node(0, "raw", OUT_BUF_SIZE); + + 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"); + + /* Allocate an input buffer */ + in_buf = zalloc(IN_BUF_SIZE); + out_buf = zalloc(OUT_BUF_SIZE); + + /* Setup LPC client for OPAL interrupts */ + if (lpc_irq >= 0) { + uint32_t chip_id = dt_get_chip_id(uart_node); + uart_lpc_opal_client.interrupts = LPC_IRQ(lpc_irq); + lpc_register_client(chip_id, &uart_lpc_opal_client, + IRQ_ATTR_TARGET_OPAL); + has_irq = true; + } + + /* + * If the interrupt is enabled, turn on RX interrupts (and + * only these for now + */ + tx_full = rx_full = false; + uart_update_ier(); + + /* Start console poller */ + opal_add_poller(uart_console_poll, NULL); +} + +static void uart_init_opal_console(void) +{ + const char *nv_policy; + + /* Update the policy if the corresponding nvram variable + * is present + */ + nv_policy = nvram_query_dangerous("uart-con-policy"); + if (nv_policy) { + if (!strcmp(nv_policy, "opal")) + uart_console_policy = UART_CONSOLE_OPAL; + else if (!strcmp(nv_policy, "os")) + uart_console_policy = UART_CONSOLE_OS; + else + prlog(PR_WARNING, + "UART: Unknown console policy in NVRAM: %s\n", + nv_policy); + } + if (uart_console_policy == UART_CONSOLE_OPAL) + uart_setup_opal_console(); + else + uart_setup_os_passthrough(); +} + +struct opal_con_ops uart_opal_con = { + .name = "OPAL UART console", + .init = uart_init_opal_console, + .read = uart_opal_read, + .write = uart_opal_write, + .space = uart_opal_write_buffer_space, + .flush = uart_opal_flush, +}; + +static bool uart_init_hw(unsigned int speed, unsigned int clock) +{ + unsigned int dll = (clock / 16) / speed; + + /* Clear line control */ + uart_write(REG_LCR, 0x00); + + /* Check if the UART responds */ + uart_write(REG_IER, 0x01); + if (uart_read(REG_IER) != 0x01) + goto detect_fail; + uart_write(REG_IER, 0x00); + if (uart_read(REG_IER) != 0x00) + goto detect_fail; + + uart_write(REG_LCR, LCR_DLAB); + uart_write(REG_DLL, dll & 0xff); + uart_write(REG_DLM, dll >> 8); + uart_write(REG_LCR, 0x03); /* 8N1 */ + uart_write(REG_MCR, 0x03); /* RTS/DTR */ + uart_write(REG_FCR, 0x07); /* clear & en. fifos */ + + /* + * On some UART implementations[1], we have observed that characters + * written to the UART during early boot (where no RX path is used, + * so we don't read from RBR) can cause a character timeout interrupt + * once we eventually enable interrupts through the IER. This + * interrupt can only be cleared by reading from RBR (even though we've + * cleared the RX FIFO!). + * + * Unfortunately though, the LCR[DR] bit does *not* indicate that there + * are characters to be read from RBR, so we may never read it, so the + * interrupt continuously fires. + * + * So, manually clear the timeout interrupt by reading the RBR here. + * We discard the read data, but that shouldn't matter as we've just + * reset the FIFO anyway. + * + * 1: seen on the AST2500 SUART. I assume this applies to 2400 too. + */ + uart_read(REG_RBR); + + return true; + + detect_fail: + prerror("UART: Presence detect failed !\n"); + return false; +} + +/* + * early_uart_init() is similar to uart_init() in that it configures skiboot + * console log to output via a UART. The main differences are that the early + * version only works with MMIO UARTs and will not setup interrupts or locks. + */ +void early_uart_init(void) +{ + struct dt_node *uart_node; + u32 clk, baud; + + uart_node = dt_find_compatible_node(dt_root, NULL, "ns16550"); + if (!uart_node) + return; + + /* Try translate the address, if this fails then it's not a MMIO UART */ + mmio_uart_base = (void *) dt_translate_address(uart_node, 0, NULL); + if (!mmio_uart_base) + return; + + clk = dt_prop_get_u32(uart_node, "clock-frequency"); + baud = dt_prop_get_u32(uart_node, "current-speed"); + + if (uart_init_hw(baud, clk)) { + set_console(&uart_con_driver); + prlog(PR_DEBUG, "UART: Using UART at %p\n", mmio_uart_base); + } else { + prerror("UART: Early init failed!"); + mmio_uart_base = NULL; + } +} + +void uart_init(void) +{ + const struct dt_property *prop; + struct dt_node *n; + char *path __unused; + const be32 *irqp; + + /* Clean up after early_uart_init() */ + mmio_uart_base = NULL; + + /* UART lock is in the console path and thus must block + * printf re-entrancy + */ + uart_lock.in_con_path = true; + + /* We support only one */ + uart_node = n = dt_find_compatible_node(dt_root, NULL, "ns16550"); + if (!n) + return; + + /* Read the interrupts property if any */ + irqp = dt_prop_get_def(n, "interrupts", NULL); + + /* Now check if the UART is on the root bus. This is the case of + * directly mapped UARTs in simulation environments + */ + if (n->parent == dt_root) { + printf("UART: Found at root !\n"); + mmio_uart_base = (void *)dt_translate_address(n, 0, NULL); + if (!mmio_uart_base) { + printf("UART: Failed to translate address !\n"); + return; + } + + /* If it has an interrupt properly, we consider this to be + * a direct XICS/XIVE interrupt + */ + if (irqp) + has_irq = true; + + } else { + if (!lpc_present()) + return; + + /* Get IO base */ + prop = dt_find_property(n, "reg"); + if (!prop) { + log_simple_error(&e_info(OPAL_RC_UART_INIT), + "UART: Can't find reg property\n"); + return; + } + if (dt_property_get_cell(prop, 0) != OPAL_LPC_IO) { + log_simple_error(&e_info(OPAL_RC_UART_INIT), + "UART: Only supports IO addresses\n"); + return; + } + uart_base = dt_property_get_cell(prop, 1); + + if (irqp) { + lpc_irq = be32_to_cpu(*irqp); + prlog(PR_DEBUG, "UART: Using LPC IRQ %d\n", lpc_irq); + } + } + + + if (!uart_init_hw(dt_prop_get_u32(n, "current-speed"), + dt_prop_get_u32(n, "clock-frequency"))) { + prerror("UART: Initialization failed\n"); + dt_add_property_strings(n, "status", "bad"); + return; + } + + /* + * Mark LPC used by the console (will mark the relevant + * locks to avoid deadlocks when flushing the console) + */ + lpc_used_by_console(); + + /* Install console backend for printf() */ + set_console(&uart_con_driver); +} + |