aboutsummaryrefslogtreecommitdiff
path: root/machine/uart16550.c
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 /machine/uart16550.c
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.
Diffstat (limited to 'machine/uart16550.c')
-rw-r--r--machine/uart16550.c74
1 files changed, 74 insertions, 0 deletions
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);
+}