aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPalmer Dabbelt <palmer@dabbelt.com>2017-12-12 19:04:56 -0800
committerPalmer Dabbelt <palmer@dabbelt.com>2017-12-12 20:08:17 -0800
commit132c6292bb3fc18e52a1e3a3a5cdd3b8ca838b28 (patch)
treee8bb8c83b10f637b83566058fb65d4ee80dafa40
parent3d921d3c76db3af7b9ae0b5df0f0790f26222246 (diff)
downloadriscv-pk-132c6292bb3fc18e52a1e3a3a5cdd3b8ca838b28.zip
riscv-pk-132c6292bb3fc18e52a1e3a3a5cdd3b8ca838b28.tar.gz
riscv-pk-132c6292bb3fc18e52a1e3a3a5cdd3b8ca838b28.tar.bz2
Add a 16550 UART driver to back the SBI console
QEMU currently provides the console via HTIF and the SBI. That's a bit messy because BBL polls for serial input, which means that typing too quickly loses characters. While QEMU has a standard 16550 device model, there's no way to have two consoles share the console in QEMU (as they'd step all over each other) so that means we can't have both the HTIF console and the 16550 console. With this patch, QEMU can be changed to use a 16650 instead of the HTIF for serial output. Linux will use the SBI for early printk support (which is fine, polling for output is stable) and then swap over as soon as it detects the UART. When Linux swaps it prints out the whole history, but there's probably a way to get around that. There's a few lines that are output to both, but it appears the Linux driver is close enough to ours that nothing catastrophic happens -- there's not much to the device, so hopefully that pans out on real hardware too. Once Linux swaps over to natively using the driver we get reliable console input. If you don't have the in-kernel driver then Linux never swaps over and keeps using the SBI console just like before.
-rw-r--r--machine/machine.mk.in2
-rw-r--r--machine/minit.c2
-rw-r--r--machine/mtrap.c5
-rw-r--r--machine/uart16550.c74
-rw-r--r--machine/uart16550.h12
5 files changed, 95 insertions, 0 deletions
diff --git a/machine/machine.mk.in b/machine/machine.mk.in
index dc8492f..3d19bf0 100644
--- a/machine/machine.mk.in
+++ b/machine/machine.mk.in
@@ -12,6 +12,7 @@ machine_hdrs = \
mcall.h \
mtrap.h \
uart.h \
+ uart16550.h \
finisher.h \
unprivileged_memory.h \
vm.h \
@@ -26,6 +27,7 @@ machine_c_srcs = \
fp_emulation.c \
fp_ldst.c \
uart.c \
+ uart16550.c \
finisher.c \
misaligned_ldst.c \
diff --git a/machine/minit.c b/machine/minit.c
index 3623f38..ee83f98 100644
--- a/machine/minit.c
+++ b/machine/minit.c
@@ -4,6 +4,7 @@
#include "fp_emulation.h"
#include "fdt.h"
#include "uart.h"
+#include "uart16550.h"
#include "finisher.h"
#include "disabled_hart_mask.h"
#include "htif.h"
@@ -141,6 +142,7 @@ void init_first_hart(uintptr_t hartid, uintptr_t dtb)
{
// Confirm console as early as possible
query_uart(dtb);
+ query_uart16550(dtb);
query_htif(dtb);
hart_init();
diff --git a/machine/mtrap.c b/machine/mtrap.c
index 0f77def..6c0e99c 100644
--- a/machine/mtrap.c
+++ b/machine/mtrap.c
@@ -5,6 +5,7 @@
#include "bits.h"
#include "vm.h"
#include "uart.h"
+#include "uart16550.h"
#include "finisher.h"
#include "fdt.h"
#include "unprivileged_memory.h"
@@ -22,6 +23,8 @@ static uintptr_t mcall_console_putchar(uint8_t ch)
{
if (uart) {
uart_putchar(ch);
+ } else if (uart16550) {
+ uart16550_putchar(ch);
} else if (htif) {
htif_console_putchar(ch);
}
@@ -73,6 +76,8 @@ static uintptr_t mcall_console_getchar()
{
if (uart) {
return uart_getchar();
+ } else if (uart16550) {
+ return uart16550_getchar();
} else if (htif) {
return htif_console_getchar();
} else {
diff --git a/machine/uart16550.c b/machine/uart16550.c
new file mode 100644
index 0000000..fe1ba99
--- /dev/null
+++ b/machine/uart16550.c
@@ -0,0 +1,74 @@
+#include <string.h>
+#include "uart16550.h"
+#include "fdt.h"
+
+volatile uint8_t* uart16550;
+
+#define UART_REG_QUEUE 0
+#define UART_REG_LINESTAT 5
+#define UART_REG_STATUS_RX 0x01
+#define UART_REG_STATUS_TX 0x20
+
+void uart16550_putchar(uint8_t ch)
+{
+ while ((uart16550[UART_REG_LINESTAT] & UART_REG_STATUS_TX) == 0);
+ uart16550[UART_REG_QUEUE] = ch;
+}
+
+int uart16550_getchar()
+{
+ if (uart16550[UART_REG_LINESTAT] & UART_REG_STATUS_RX)
+ return uart16550[UART_REG_QUEUE];
+ return -1;
+}
+
+struct uart16550_scan
+{
+ int compat;
+ uint64_t reg;
+};
+
+static void uart16550_open(const struct fdt_scan_node *node, void *extra)
+{
+ struct uart16550_scan *scan = (struct uart16550_scan *)extra;
+ memset(scan, 0, sizeof(*scan));
+}
+
+static void uart16550_prop(const struct fdt_scan_prop *prop, void *extra)
+{
+ struct uart16550_scan *scan = (struct uart16550_scan *)extra;
+ if (!strcmp(prop->name, "compatible") && !strcmp((const char*)prop->value, "ns16550a")) {
+ scan->compat = 1;
+ } else if (!strcmp(prop->name, "reg")) {
+ fdt_get_address(prop->node->parent, prop->value, &scan->reg);
+ }
+}
+
+static void uart16550_done(const struct fdt_scan_node *node, void *extra)
+{
+ struct uart16550_scan *scan = (struct uart16550_scan *)extra;
+ if (!scan->compat || !scan->reg || uart16550) return;
+
+ uart16550 = (void*)(uintptr_t)scan->reg;
+ // http://wiki.osdev.org/Serial_Ports
+ uart16550[1] = 0x00; // Disable all interrupts
+ uart16550[3] = 0x80; // Enable DLAB (set baud rate divisor)
+ uart16550[0] = 0x03; // Set divisor to 3 (lo byte) 38400 baud
+ uart16550[1] = 0x00; // (hi byte)
+ uart16550[3] = 0x03; // 8 bits, no parity, one stop bit
+ uart16550[2] = 0xC7; // Enable FIFO, clear them, with 14-byte threshold
+}
+
+void query_uart16550(uintptr_t fdt)
+{
+ struct fdt_cb cb;
+ struct uart16550_scan scan;
+
+ memset(&cb, 0, sizeof(cb));
+ cb.open = uart16550_open;
+ cb.prop = uart16550_prop;
+ cb.done = uart16550_done;
+ cb.extra = &scan;
+
+ fdt_scan(fdt, &cb);
+}
diff --git a/machine/uart16550.h b/machine/uart16550.h
new file mode 100644
index 0000000..d7a0805
--- /dev/null
+++ b/machine/uart16550.h
@@ -0,0 +1,12 @@
+#ifndef _RISCV_16550_H
+#define _RISCV_16550_H
+
+#include <stdint.h>
+
+extern volatile uint8_t* uart16550;
+
+void uart16550_putchar(uint8_t ch);
+int uart16550_getchar();
+void query_uart16550(uintptr_t dtb);
+
+#endif