Browse Source

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 <anup@brainfault.org>
pull/889/head
Anup Patel 5 years ago
parent
commit
191634d285
  1. 31
      riscv/devices.h
  2. 58
      riscv/dts.cc
  3. 3
      riscv/dts.h
  4. 315
      riscv/ns16550.cc
  5. 5
      riscv/platform.h
  6. 1
      riscv/riscv.mk.in
  7. 16
      riscv/sim.cc
  8. 1
      riscv/sim.h

31
riscv/devices.h

@ -7,6 +7,7 @@
#include "abstract_interrupt_controller.h"
#include "platform.h"
#include <map>
#include <queue>
#include <vector>
#include <utility>
@ -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<uint8_t> 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);

58
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;

3
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);

315
riscv/ns16550.cc

@ -0,0 +1,315 @@
#include <sys/time.h>
#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();
}

5
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

1
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 \

16
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();

1
riscv/sim.h

@ -83,6 +83,7 @@ private:
std::unique_ptr<rom_device_t> boot_rom;
std::unique_ptr<clint_t> clint;
std::unique_ptr<plic_t> plic;
std::unique_ptr<ns16550_t> ns16550;
bus_t bus;
log_file_t log_file;

Loading…
Cancel
Save