diff options
author | Palmer Dabbelt <palmer@dabbelt.com> | 2017-12-12 19:04:56 -0800 |
---|---|---|
committer | Palmer Dabbelt <palmer@dabbelt.com> | 2017-12-12 20:08:17 -0800 |
commit | 132c6292bb3fc18e52a1e3a3a5cdd3b8ca838b28 (patch) | |
tree | e8bb8c83b10f637b83566058fb65d4ee80dafa40 /machine | |
parent | 3d921d3c76db3af7b9ae0b5df0f0790f26222246 (diff) | |
download | riscv-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')
-rw-r--r-- | machine/machine.mk.in | 2 | ||||
-rw-r--r-- | machine/minit.c | 2 | ||||
-rw-r--r-- | machine/mtrap.c | 5 | ||||
-rw-r--r-- | machine/uart16550.c | 74 | ||||
-rw-r--r-- | machine/uart16550.h | 12 |
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 |