From 191634d2854dfed448fc323195f9b65c305e2d77 Mon Sep 17 00:00:00 2001 From: Anup Patel Date: Tue, 14 Dec 2021 19:18:32 +0530 Subject: Add ns16550 serial device emulation The ns16550 is a widely use serial device so we add a simplified ns16550 device emulation which is good enough for Linux, OpenSBI, and hypervisors to use as console. Signed-off-by: Anup Patel --- riscv/devices.h | 31 ++++++ riscv/dts.cc | 58 +++++++++- riscv/dts.h | 3 + riscv/ns16550.cc | 315 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ riscv/platform.h | 5 + riscv/riscv.mk.in | 1 + riscv/sim.cc | 16 +++ riscv/sim.h | 1 + 8 files changed, 426 insertions(+), 4 deletions(-) create mode 100644 riscv/ns16550.cc diff --git a/riscv/devices.h b/riscv/devices.h index c9e4f66..df7b289 100644 --- a/riscv/devices.h +++ b/riscv/devices.h @@ -7,6 +7,7 @@ #include "abstract_interrupt_controller.h" #include "platform.h" #include +#include #include #include @@ -117,6 +118,36 @@ class plic_t : public abstract_device_t, public abstract_interrupt_controller_t reg_t offset, uint32_t val); }; +class ns16550_t : public abstract_device_t { + public: + ns16550_t(class bus_t *bus, abstract_interrupt_controller_t *intctrl, + uint32_t interrupt_id, uint32_t reg_shift, uint32_t reg_io_width); + bool load(reg_t addr, size_t len, uint8_t* bytes); + bool store(reg_t addr, size_t len, const uint8_t* bytes); + void tick(void); + size_t size() { return NS16550_SIZE; } + private: + class bus_t *bus; + abstract_interrupt_controller_t *intctrl; + uint32_t interrupt_id; + uint32_t reg_shift; + uint32_t reg_io_width; + std::queue rx_queue; + uint8_t dll; + uint8_t dlm; + uint8_t iir; + uint8_t ier; + uint8_t fcr; + uint8_t lcr; + uint8_t mcr; + uint8_t lsr; + uint8_t msr; + uint8_t scr; + void update_interrupt(void); + uint8_t rx_byte(void); + void tx_byte(uint8_t val); +}; + class mmio_plugin_device_t : public abstract_device_t { public: mmio_plugin_device_t(const std::string& name, const std::string& args); diff --git a/riscv/dts.cc b/riscv/dts.cc index 970e0ef..e6b810d 100644 --- a/riscv/dts.cc +++ b/riscv/dts.cc @@ -26,15 +26,16 @@ std::string make_dts(size_t insns_per_rtc_tick, size_t cpu_hz, " #size-cells = <2>;\n" " compatible = \"ucbbar,spike-bare-dev\";\n" " model = \"ucbbar,spike-bare\";\n" - " chosen {\n"; + " chosen {\n" + " stdout-path = &SERIAL0;\n"; if (initrd_start < initrd_end) { s << " linux,initrd-start = <" << (size_t)initrd_start << ">;\n" " linux,initrd-end = <" << (size_t)initrd_end << ">;\n"; if (!bootargs) - bootargs = "root=/dev/ram console=hvc0 earlycon=sbi"; + bootargs = "root=/dev/ram console=ttyS0 earlycon"; } else { if (!bootargs) - bootargs = "console=hvc0 earlycon=sbi"; + bootargs = "console=ttyS0 earlycon"; } s << " bootargs = \""; for (size_t i = 0; i < strlen(bootargs); i++) { @@ -93,7 +94,7 @@ std::string make_dts(size_t insns_per_rtc_tick, size_t cpu_hz, " reg = <0x" << (clintbs >> 32) << " 0x" << (clintbs & (uint32_t)-1) << " 0x" << (clintsz >> 32) << " 0x" << (clintsz & (uint32_t)-1) << ">;\n" " };\n" - " plic@" << PLIC_BASE << " {\n" + " PLIC: plic@" << PLIC_BASE << " {\n" " compatible = \"riscv,plic0\";\n" " interrupts-extended = <" << std::dec; for (size_t i = 0; i < procs.size(); i++) @@ -108,6 +109,19 @@ std::string make_dts(size_t insns_per_rtc_tick, size_t cpu_hz, " #interrupt-cells = <1>;\n" " interrupt-controller;\n" " };\n" + " SERIAL0: ns16550@" << NS16550_BASE << " {\n" + " compatible = \"ns16550a\";\n" + " clock-frequency = <" << std::dec << (cpu_hz/insns_per_rtc_tick) << ">;\n" + " interrupt-parent = <&PLIC>;\n" + " interrupts = <" << std::dec << NS16550_INTERRUPT_ID; + reg_t ns16550bs = NS16550_BASE; + reg_t ns16550sz = NS16550_SIZE; + s << std::hex << ">;\n" + " reg = <0x" << (ns16550bs >> 32) << " 0x" << (ns16550bs & (uint32_t)-1) << + " 0x" << (ns16550sz >> 32) << " 0x" << (ns16550sz & (uint32_t)-1) << ">;\n" + " reg-shift = <0x" << NS16550_REG_SHIFT << ">;\n" + " reg-io-width = <0x" << NS16550_REG_IO_WIDTH << ">;\n" + " };\n" " };\n" " htif {\n" " compatible = \"ucb,htif0\";\n" @@ -314,6 +328,42 @@ int fdt_parse_plic(void *fdt, reg_t *plic_addr, uint32_t *ndev, return 0; } +int fdt_parse_ns16550(void *fdt, reg_t *ns16550_addr, + uint32_t *reg_shift, uint32_t *reg_io_width, + const char *compatible) +{ + int nodeoffset, len, rc; + const fdt32_t *reg_p; + + nodeoffset = fdt_node_offset_by_compatible(fdt, -1, compatible); + if (nodeoffset < 0) + return nodeoffset; + + rc = fdt_get_node_addr_size(fdt, nodeoffset, ns16550_addr, NULL, "reg"); + if (rc < 0 || !ns16550_addr) + return -ENODEV; + + reg_p = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "reg-shift", &len); + if (reg_shift) { + if (reg_p) { + *reg_shift = fdt32_to_cpu(*reg_p); + } else { + *reg_shift = NS16550_REG_SHIFT; + } + } + + reg_p = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "reg-io-width", &len); + if (reg_io_width) { + if (reg_p) { + *reg_io_width = fdt32_to_cpu(*reg_p); + } else { + *reg_io_width = NS16550_REG_IO_WIDTH; + } + } + + return 0; +} + int fdt_parse_pmp_num(void *fdt, int cpu_offset, reg_t *pmp_num) { int rc; diff --git a/riscv/dts.h b/riscv/dts.h index 66add51..a02f594 100644 --- a/riscv/dts.h +++ b/riscv/dts.h @@ -23,6 +23,9 @@ int fdt_parse_clint(void *fdt, reg_t *clint_addr, const char *compatible); int fdt_parse_plic(void *fdt, reg_t *plic_addr, uint32_t *ndev, const char *compatible); +int fdt_parse_ns16550(void *fdt, reg_t *ns16550_addr, + uint32_t *reg_shift, uint32_t *reg_io_width, + const char *compatible); int fdt_parse_pmp_num(void *fdt, int cpu_offset, reg_t *pmp_num); int fdt_parse_pmp_alignment(void *fdt, int cpu_offset, reg_t *pmp_align); int fdt_parse_mmu_type(void *fdt, int cpu_offset, const char **mmu_type); diff --git a/riscv/ns16550.cc b/riscv/ns16550.cc new file mode 100644 index 0000000..37af568 --- /dev/null +++ b/riscv/ns16550.cc @@ -0,0 +1,315 @@ +#include +#include "devices.h" +#include "processor.h" +#include "term.h" + +#define UART_QUEUE_SIZE 64 + +#define UART_RX 0 /* In: Receive buffer */ +#define UART_TX 0 /* Out: Transmit buffer */ + +#define UART_IER 1 /* Out: Interrupt Enable Register */ +#define UART_IER_MSI 0x08 /* Enable Modem status interrupt */ +#define UART_IER_RLSI 0x04 /* Enable receiver line status interrupt */ +#define UART_IER_THRI 0x02 /* Enable Transmitter holding register int. */ +#define UART_IER_RDI 0x01 /* Enable receiver data interrupt */ + +#define UART_IIR 2 /* In: Interrupt ID Register */ +#define UART_IIR_NO_INT 0x01 /* No interrupts pending */ +#define UART_IIR_ID 0x0e /* Mask for the interrupt ID */ +#define UART_IIR_MSI 0x00 /* Modem status interrupt */ +#define UART_IIR_THRI 0x02 /* Transmitter holding register empty */ +#define UART_IIR_RDI 0x04 /* Receiver data interrupt */ +#define UART_IIR_RLSI 0x06 /* Receiver line status interrupt */ + +#define UART_IIR_TYPE_BITS 0xc0 + +#define UART_FCR 2 /* Out: FIFO Control Register */ +#define UART_FCR_ENABLE_FIFO 0x01 /* Enable the FIFO */ +#define UART_FCR_CLEAR_RCVR 0x02 /* Clear the RCVR FIFO */ +#define UART_FCR_CLEAR_XMIT 0x04 /* Clear the XMIT FIFO */ +#define UART_FCR_DMA_SELECT 0x08 /* For DMA applications */ + +#define UART_LCR 3 /* Out: Line Control Register */ +#define UART_LCR_DLAB 0x80 /* Divisor latch access bit */ +#define UART_LCR_SBC 0x40 /* Set break control */ +#define UART_LCR_SPAR 0x20 /* Stick parity (?) */ +#define UART_LCR_EPAR 0x10 /* Even parity select */ +#define UART_LCR_PARITY 0x08 /* Parity Enable */ +#define UART_LCR_STOP 0x04 /* Stop bits: 0=1 bit, 1=2 bits */ + +#define UART_MCR 4 /* Out: Modem Control Register */ +#define UART_MCR_LOOP 0x10 /* Enable loopback test mode */ +#define UART_MCR_OUT2 0x08 /* Out2 complement */ +#define UART_MCR_OUT1 0x04 /* Out1 complement */ +#define UART_MCR_RTS 0x02 /* RTS complement */ +#define UART_MCR_DTR 0x01 /* DTR complement */ + +#define UART_LSR 5 /* In: Line Status Register */ +#define UART_LSR_FIFOE 0x80 /* Fifo error */ +#define UART_LSR_TEMT 0x40 /* Transmitter empty */ +#define UART_LSR_THRE 0x20 /* Transmit-hold-register empty */ +#define UART_LSR_BI 0x10 /* Break interrupt indicator */ +#define UART_LSR_FE 0x08 /* Frame error indicator */ +#define UART_LSR_PE 0x04 /* Parity error indicator */ +#define UART_LSR_OE 0x02 /* Overrun error indicator */ +#define UART_LSR_DR 0x01 /* Receiver data ready */ +#define UART_LSR_BRK_ERROR_BITS 0x1E /* BI, FE, PE, OE bits */ + +#define UART_MSR 6 /* In: Modem Status Register */ +#define UART_MSR_DCD 0x80 /* Data Carrier Detect */ +#define UART_MSR_RI 0x40 /* Ring Indicator */ +#define UART_MSR_DSR 0x20 /* Data Set Ready */ +#define UART_MSR_CTS 0x10 /* Clear to Send */ +#define UART_MSR_DDCD 0x08 /* Delta DCD */ +#define UART_MSR_TERI 0x04 /* Trailing edge ring indicator */ +#define UART_MSR_DDSR 0x02 /* Delta DSR */ +#define UART_MSR_DCTS 0x01 /* Delta CTS */ +#define UART_MSR_ANY_DELTA 0x0F /* Any of the delta bits! */ + +#define UART_SCR 7 /* I/O: Scratch Register */ + +ns16550_t::ns16550_t(class bus_t *bus, abstract_interrupt_controller_t *intctrl, + uint32_t interrupt_id, uint32_t reg_shift, uint32_t reg_io_width) + : bus(bus), intctrl(intctrl), interrupt_id(interrupt_id), reg_shift(reg_shift), reg_io_width(reg_io_width) +{ + ier = 0; + iir = UART_IIR_NO_INT; + fcr = 0; + lcr = 0; + lsr = UART_LSR_TEMT | UART_LSR_THRE; + msr = UART_MSR_DCD | UART_MSR_DSR | UART_MSR_CTS; + dll = 0x0C; + mcr = UART_MCR_OUT2; + scr = 0; +} + +void ns16550_t::update_interrupt(void) +{ + uint8_t interrupts = 0; + + /* Handle clear rx */ + if (lcr & UART_FCR_CLEAR_RCVR) { + lcr &= ~UART_FCR_CLEAR_RCVR; + while (!rx_queue.empty()) { + rx_queue.pop(); + } + lsr &= ~UART_LSR_DR; + } + + /* Handle clear tx */ + if (lcr & UART_FCR_CLEAR_XMIT) { + lcr &= ~UART_FCR_CLEAR_XMIT; + lsr |= UART_LSR_TEMT | UART_LSR_THRE; + } + + /* Data ready and rcv interrupt enabled ? */ + if ((ier & UART_IER_RDI) && (lsr & UART_LSR_DR)) { + interrupts |= UART_IIR_RDI; + } + + /* Transmitter empty and interrupt enabled ? */ + if ((ier & UART_IER_THRI) && (lsr & UART_LSR_TEMT)) { + interrupts |= UART_IIR_THRI; + } + + /* Now update the interrup line, if necessary */ + if (!interrupts) { + iir = UART_IIR_NO_INT; + intctrl->set_interrupt_level(interrupt_id, 0); + } else { + iir = interrupts; + intctrl->set_interrupt_level(interrupt_id, 1); + } + + /* + * If the OS disabled the tx interrupt, we know that there is nothing + * more to transmit. + */ + if (!(ier & UART_IER_THRI)) { + lsr |= UART_LSR_TEMT | UART_LSR_THRE; + } +} + +uint8_t ns16550_t::rx_byte(void) +{ + uint8_t ret = 0; + + if (rx_queue.empty()) { + lsr &= ~UART_LSR_DR; + return 0; + } + + /* Break issued ? */ + if (lsr & UART_LSR_BI) { + lsr &= ~UART_LSR_BI; + return 0; + } + + ret = rx_queue.front(); + rx_queue.pop(); + if (rx_queue.empty()) { + lsr &= ~UART_LSR_DR; + } + + return ret; +} + +void ns16550_t::tx_byte(uint8_t val) +{ + lsr |= UART_LSR_TEMT | UART_LSR_THRE; + canonical_terminal_t::write(val); +} + +bool ns16550_t::load(reg_t addr, size_t len, uint8_t* bytes) +{ + uint8_t val; + bool ret = true, update = false; + + if (reg_io_width != len) { + return false; + } + addr >>= reg_shift; + addr &= 7; + + switch (addr) { + case UART_RX: + if (lcr & UART_LCR_DLAB) { + val = dll; + } else { + val = rx_byte(); + } + update = true; + break; + case UART_IER: + if (lcr & UART_LCR_DLAB) { + val = dlm; + } else { + val = ier; + } + break; + case UART_IIR: + val = iir | UART_IIR_TYPE_BITS; + break; + case UART_LCR: + val = lcr; + break; + case UART_MCR: + val = mcr; + break; + case UART_LSR: + val = lsr; + break; + case UART_MSR: + val = msr; + break; + case UART_SCR: + val = scr; + break; + default: + ret = false; + break; + }; + + if (ret) { + bytes[0] = val; + } + if (update) { + update_interrupt(); + } + + return ret; +} + +bool ns16550_t::store(reg_t addr, size_t len, const uint8_t* bytes) +{ + uint8_t val; + bool ret = true, update = false; + + if (reg_io_width != len) { + return false; + } + addr >>= reg_shift; + addr &= 7; + val = bytes[0]; + + switch (addr) { + case UART_TX: + update = true; + + if (lcr & UART_LCR_DLAB) { + dll = val; + break; + } + + /* Loopback mode */ + if (mcr & UART_MCR_LOOP) { + if (rx_queue.size() < UART_QUEUE_SIZE) { + rx_queue.push(val); + lsr |= UART_LSR_DR; + } + break; + } + + tx_byte(val); + break; + case UART_IER: + if (!(lcr & UART_LCR_DLAB)) { + ier = val & 0x0f; + } else { + dlm = val; + } + update = true; + break; + case UART_FCR: + fcr = val; + update = true; + break; + case UART_LCR: + lcr = val; + update = true; + break; + case UART_MCR: + mcr = val; + update = true; + break; + case UART_LSR: + /* Factory test */ + break; + case UART_MSR: + /* Not used */ + break; + case UART_SCR: + scr = val; + break; + default: + ret = false; + break; + }; + + if (update) { + update_interrupt(); + } + + return ret; +} + +void ns16550_t::tick(void) +{ + int rc; + + if (!(fcr & UART_FCR_ENABLE_FIFO) || + (mcr & UART_MCR_LOOP) || + (UART_QUEUE_SIZE <= rx_queue.size())) { + return; + } + + rc = canonical_terminal_t::read(); + if (rc < 0) { + return; + } + + rx_queue.push((uint8_t)rc); + lsr |= UART_LSR_DR; + update_interrupt(); +} diff --git a/riscv/platform.h b/riscv/platform.h index 4321e84..2bafa68 100644 --- a/riscv/platform.h +++ b/riscv/platform.h @@ -9,6 +9,11 @@ #define PLIC_SIZE 0x01000000 #define PLIC_NDEV 31 #define PLIC_PRIO_BITS 4 +#define NS16550_BASE 0x10000000 +#define NS16550_SIZE 0x100 +#define NS16550_REG_SHIFT 0 +#define NS16550_REG_IO_WIDTH 1 +#define NS16550_INTERRUPT_ID 1 #define EXT_IO_BASE 0x40000000 #define DRAM_BASE 0x80000000 diff --git a/riscv/riscv.mk.in b/riscv/riscv.mk.in index 7f1fc9a..708cead 100644 --- a/riscv/riscv.mk.in +++ b/riscv/riscv.mk.in @@ -55,6 +55,7 @@ riscv_srcs = \ rom.cc \ clint.cc \ plic.cc \ + ns16550.cc \ debug_module.cc \ remote_bitbang.cc \ jtag_dtm.cc \ diff --git a/riscv/sim.cc b/riscv/sim.cc index 81a0cbc..e909009 100644 --- a/riscv/sim.cc +++ b/riscv/sim.cc @@ -98,12 +98,27 @@ sim_t::sim_t(const cfg_t *cfg, bool halted, bus.add_device(clint_base, clint.get()); } + // pointer to wired interrupt controller + abstract_interrupt_controller_t *intctrl = NULL; + // create plic reg_t plic_base; uint32_t plic_ndev; if (fdt_parse_plic(fdt, &plic_base, &plic_ndev, "riscv,plic0") == 0) { plic.reset(new plic_t(procs, true, plic_ndev)); bus.add_device(plic_base, plic.get()); + intctrl = plic.get(); + } + + // create ns16550 + reg_t ns16550_base; + uint32_t ns16550_shift, ns16550_io_width; + if (fdt_parse_ns16550(fdt, &ns16550_base, + &ns16550_shift, &ns16550_io_width, "ns16550a") == 0) { + assert(intctrl); + ns16550.reset(new ns16550_t(&bus, intctrl, NS16550_INTERRUPT_ID, + ns16550_shift, ns16550_io_width)); + bus.add_device(ns16550_base, ns16550.get()); } //per core attribute @@ -228,6 +243,7 @@ void sim_t::step(size_t n) if (++current_proc == procs.size()) { current_proc = 0; if (clint) clint->increment(INTERLEAVE / INSNS_PER_RTC_TICK); + if (ns16550) ns16550->tick(); } host->switch_to(); diff --git a/riscv/sim.h b/riscv/sim.h index 91b25c2..a09c6e5 100644 --- a/riscv/sim.h +++ b/riscv/sim.h @@ -83,6 +83,7 @@ private: std::unique_ptr boot_rom; std::unique_ptr clint; std::unique_ptr plic; + std::unique_ptr ns16550; bus_t bus; log_file_t log_file; -- cgit v1.1