aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/console.c7
-rw-r--r--hw/lpc-uart.c304
-rw-r--r--include/console.h2
-rw-r--r--include/skiboot.h4
-rw-r--r--platforms/astbmc/common.c7
-rw-r--r--platforms/rhesus/rhesus.c3
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 <processor.h>
#include <fsp-elog.h>
#include <trace.h>
+#include <timebase.h>
#include <cpu.h>
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)