aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--riscv/abstract_interrupt_controller.h14
-rw-r--r--riscv/devices.h77
-rw-r--r--riscv/dts.cc93
-rw-r--r--riscv/dts.h5
-rw-r--r--riscv/ns16550.cc315
-rw-r--r--riscv/platform.h9
-rw-r--r--riscv/plic.cc393
-rw-r--r--riscv/processor.h1
-rw-r--r--riscv/riscv.mk.in2
-rw-r--r--riscv/sim.cc24
-rw-r--r--riscv/sim.h2
11 files changed, 932 insertions, 3 deletions
diff --git a/riscv/abstract_interrupt_controller.h b/riscv/abstract_interrupt_controller.h
new file mode 100644
index 0000000..946b079
--- /dev/null
+++ b/riscv/abstract_interrupt_controller.h
@@ -0,0 +1,14 @@
+#ifndef _RISCV_ABSTRACT_INTERRUPT_CONTROLLER_H
+#define _RISCV_ABSTRACT_INTERRUPT_CONTROLLER_H
+
+#include "decode.h"
+#include <cstdint>
+#include <cstddef>
+
+class abstract_interrupt_controller_t {
+ public:
+ virtual void set_interrupt_level(uint32_t interrupt_id, int level) = 0;
+ virtual ~abstract_interrupt_controller_t() {}
+};
+
+#endif
diff --git a/riscv/devices.h b/riscv/devices.h
index 08e9c45..bccda34 100644
--- a/riscv/devices.h
+++ b/riscv/devices.h
@@ -4,8 +4,10 @@
#include "decode.h"
#include "mmio_plugin.h"
#include "abstract_device.h"
+#include "abstract_interrupt_controller.h"
#include "platform.h"
#include <map>
+#include <queue>
#include <vector>
#include <utility>
@@ -74,6 +76,81 @@ class clint_t : public abstract_device_t {
std::vector<mtimecmp_t> mtimecmp;
};
+#define PLIC_MAX_DEVICES 1024
+
+struct plic_context {
+ uint32_t num;
+ processor_t *proc;
+ bool mmode;
+
+ uint8_t priority_threshold;
+ uint32_t enable[PLIC_MAX_DEVICES/32];
+ uint32_t pending[PLIC_MAX_DEVICES/32];
+ uint8_t pending_priority[PLIC_MAX_DEVICES];
+ uint32_t claimed[PLIC_MAX_DEVICES/32];
+};
+typedef struct plic_context plic_context_t;
+
+class plic_t : public abstract_device_t, public abstract_interrupt_controller_t {
+ public:
+ plic_t(std::vector<processor_t*>&, bool smode, uint32_t ndev);
+ bool load(reg_t addr, size_t len, uint8_t* bytes);
+ bool store(reg_t addr, size_t len, const uint8_t* bytes);
+ void set_interrupt_level(uint32_t id, int lvl);
+ size_t size() { return PLIC_SIZE; }
+ private:
+ std::vector<processor_t*>& procs;
+ std::vector<plic_context_t> contexts;
+ uint32_t num_ids;
+ uint32_t num_ids_word;
+ uint32_t max_prio;
+ uint8_t priority[PLIC_MAX_DEVICES];
+ uint32_t level[PLIC_MAX_DEVICES/32];
+ uint32_t context_best_pending(plic_context_t *c);
+ void context_update(plic_context_t *context);
+ uint32_t context_claim(plic_context_t *c);
+ bool priority_read(reg_t offset, uint32_t *val);
+ bool priority_write(reg_t offset, uint32_t val);
+ bool context_enable_read(plic_context_t *context,
+ reg_t offset, uint32_t *val);
+ bool context_enable_write(plic_context_t *context,
+ reg_t offset, uint32_t val);
+ bool context_read(plic_context_t *context,
+ reg_t offset, uint32_t *val);
+ bool context_write(plic_context_t *context,
+ 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);
diff --git a/riscv/dts.cc b/riscv/dts.cc
index 9937d57..6a40565 100644
--- a/riscv/dts.cc
+++ b/riscv/dts.cc
@@ -27,15 +27,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++) {
@@ -94,6 +95,34 @@ 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@" << PLIC_BASE << " {\n"
+ " compatible = \"riscv,plic0\";\n"
+ " interrupts-extended = <" << std::dec;
+ for (size_t i = 0; i < procs.size(); i++)
+ s << "&CPU" << i << "_intc 11 &CPU" << i << "_intc 9 ";
+ reg_t plicbs = PLIC_BASE;
+ reg_t plicsz = PLIC_SIZE;
+ s << std::hex << ">;\n"
+ " reg = <0x" << (plicbs >> 32) << " 0x" << (plicbs & (uint32_t)-1) <<
+ " 0x" << (plicsz >> 32) << " 0x" << (plicsz & (uint32_t)-1) << ">;\n"
+ " riscv,ndev = <0x" << PLIC_NDEV << ">;\n"
+ " riscv,max-priority = <0x" << ((1U << PLIC_PRIO_BITS) - 1) << ">;\n"
+ " #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"
@@ -276,6 +305,64 @@ int fdt_parse_clint(void *fdt, reg_t *clint_addr,
return 0;
}
+int fdt_parse_plic(void *fdt, reg_t *plic_addr, uint32_t *ndev,
+ const char *compatible)
+{
+ int nodeoffset, len, rc;
+ const fdt32_t *ndev_p;
+
+ nodeoffset = fdt_node_offset_by_compatible(fdt, -1, compatible);
+ if (nodeoffset < 0)
+ return nodeoffset;
+
+ rc = fdt_get_node_addr_size(fdt, nodeoffset, plic_addr, NULL, "reg");
+ if (rc < 0 || !plic_addr)
+ return -ENODEV;
+
+ ndev_p = (fdt32_t *)fdt_getprop(fdt, nodeoffset, "riscv,ndev", &len);
+ if (!ndev || !ndev_p)
+ return -ENODEV;
+ *ndev = fdt32_to_cpu(*ndev_p);
+
+ 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 1878ca1..7a64d7b 100644
--- a/riscv/dts.h
+++ b/riscv/dts.h
@@ -22,6 +22,11 @@ int fdt_get_next_subnode(void *fdt, int node);
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 <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();
+}
diff --git a/riscv/platform.h b/riscv/platform.h
index 6618d44..2bafa68 100644
--- a/riscv/platform.h
+++ b/riscv/platform.h
@@ -5,6 +5,15 @@
#define DEFAULT_RSTVEC 0x00001000
#define CLINT_BASE 0x02000000
#define CLINT_SIZE 0x000c0000
+#define PLIC_BASE 0x0c000000
+#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/plic.cc b/riscv/plic.cc
new file mode 100644
index 0000000..2d08c8e
--- /dev/null
+++ b/riscv/plic.cc
@@ -0,0 +1,393 @@
+#include <sys/time.h>
+#include "devices.h"
+#include "processor.h"
+
+#define PLIC_MAX_CONTEXTS 15872
+
+/*
+ * The PLIC consists of memory-mapped control registers, with a memory map
+ * as follows:
+ *
+ * base + 0x000000: Reserved (interrupt source 0 does not exist)
+ * base + 0x000004: Interrupt source 1 priority
+ * base + 0x000008: Interrupt source 2 priority
+ * ...
+ * base + 0x000FFC: Interrupt source 1023 priority
+ * base + 0x001000: Pending 0
+ * base + 0x001FFF: Pending
+ * base + 0x002000: Enable bits for sources 0-31 on context 0
+ * base + 0x002004: Enable bits for sources 32-63 on context 0
+ * ...
+ * base + 0x0020FC: Enable bits for sources 992-1023 on context 0
+ * base + 0x002080: Enable bits for sources 0-31 on context 1
+ * ...
+ * base + 0x002100: Enable bits for sources 0-31 on context 2
+ * ...
+ * base + 0x1F1F80: Enable bits for sources 992-1023 on context 15871
+ * base + 0x1F1F84: Reserved
+ * ... (higher context IDs would fit here, but wouldn't fit
+ * inside the per-context priority vector)
+ * base + 0x1FFFFC: Reserved
+ * base + 0x200000: Priority threshold for context 0
+ * base + 0x200004: Claim/complete for context 0
+ * base + 0x200008: Reserved
+ * ...
+ * base + 0x200FFC: Reserved
+ * base + 0x201000: Priority threshold for context 1
+ * base + 0x201004: Claim/complete for context 1
+ * ...
+ * base + 0xFFE000: Priority threshold for context 15871
+ * base + 0xFFE004: Claim/complete for context 15871
+ * base + 0xFFE008: Reserved
+ * ...
+ * base + 0xFFFFFC: Reserved
+ */
+
+/* Each interrupt source has a priority register associated with it. */
+#define PRIORITY_BASE 0
+#define PRIORITY_PER_ID 4
+
+/*
+ * Each hart context has a vector of interupt enable bits associated with it.
+ * There's one bit for each interrupt source.
+ */
+#define ENABLE_BASE 0x2000
+#define ENABLE_PER_HART 0x80
+
+/*
+ * Each hart context has a set of control registers associated with it. Right
+ * now there's only two: a source priority threshold over which the hart will
+ * take an interrupt, and a register to claim interrupts.
+ */
+#define CONTEXT_BASE 0x200000
+#define CONTEXT_PER_HART 0x1000
+#define CONTEXT_THRESHOLD 0
+#define CONTEXT_CLAIM 4
+
+#define REG_SIZE 0x1000000
+
+plic_t::plic_t(std::vector<processor_t*>& procs, bool smode, uint32_t ndev)
+ : procs(procs), contexts(procs.size() * (smode ? 2 : 1))
+{
+ size_t i;
+ size_t contexts_per_hart = smode ? 2 : 1;
+ plic_context_t *c;
+
+ num_ids = ndev + 1;
+ num_ids_word = num_ids / 32;
+ if ((num_ids_word * 32) < num_ids)
+ num_ids_word++;
+ max_prio = (1UL << PLIC_PRIO_BITS) - 1;
+ memset(priority, 0, sizeof(priority));
+ memset(level, 0, sizeof(level));
+
+ for (i = 0; i < contexts.size(); i++) {
+ c = &contexts[i];
+ c->num = i;
+ c->proc = procs[i / contexts_per_hart];
+ if (smode) {
+ c->mmode = (i % contexts_per_hart == 0);
+ } else {
+ c->mmode = true;
+ }
+ memset(&c->enable, 0, sizeof(c->enable));
+ memset(&c->pending, 0, sizeof(c->pending));
+ memset(&c->pending_priority, 0, sizeof(c->pending_priority));
+ memset(&c->claimed, 0, sizeof(c->claimed));
+ }
+}
+
+uint32_t plic_t::context_best_pending(plic_context_t *c)
+{
+ uint8_t best_id_prio = 0;
+ uint32_t i, j, id, best_id = 0;
+
+ for (i = 0; i < num_ids_word; i++) {
+ if (!c->pending[i]) {
+ continue;
+ }
+
+ for (j = 0; j < 32; j++) {
+ id = i * 32 + j;
+ if ((num_ids <= id) ||
+ !(c->pending[i] & (1 << j)) ||
+ (c->claimed[i] & (1 << j))) {
+ continue;
+ }
+
+ if (!best_id ||
+ (best_id_prio < c->pending_priority[id])) {
+ best_id = id;
+ best_id_prio = c->pending_priority[id];
+ }
+ }
+ }
+
+ return best_id;
+}
+
+void plic_t::context_update(plic_context_t *c)
+{
+ uint32_t best_id = context_best_pending(c);
+ reg_t mask = c->mmode ? MIP_MEIP : MIP_SEIP;
+
+ c->proc->state.mip->backdoor_write_with_mask(mask, best_id ? mask : 0);
+}
+
+uint32_t plic_t::context_claim(plic_context_t *c)
+{
+ uint32_t best_id = context_best_pending(c);
+ uint32_t best_id_word = best_id / 32;
+ uint32_t best_id_mask = (1 << (best_id % 32));
+
+ if (best_id) {
+ c->claimed[best_id_word] |= best_id_mask;
+ }
+
+ context_update(c);
+ return best_id;
+}
+
+bool plic_t::priority_read(reg_t offset, uint32_t *val)
+{
+ uint32_t id = (offset >> 2);
+
+ if (id == 0 || id >= num_ids) {
+ return false;
+ }
+
+ *val = priority[id];
+ return true;
+}
+
+bool plic_t::priority_write(reg_t offset, uint32_t val)
+{
+ uint32_t id = (offset >> 2);
+
+ if (id == 0 || id >= num_ids) {
+ return false;
+ }
+
+ val &= ((1 << PLIC_PRIO_BITS) - 1);
+ priority[id] = val;
+ return true;
+}
+
+bool plic_t::context_enable_read(plic_context_t *c,
+ reg_t offset, uint32_t *val)
+{
+ uint32_t id_word = offset >> 2;
+
+ if (num_ids_word < id_word) {
+ return false;
+ }
+
+ *val = c->enable[id_word];
+ return true;
+}
+
+bool plic_t::context_enable_write(plic_context_t *c,
+ reg_t offset, uint32_t val)
+{
+ uint8_t id_prio;
+ uint32_t i, id, id_mask;
+ uint32_t id_word = offset >> 2;
+ uint32_t old_val, new_val, xor_val;
+
+ if (num_ids_word < id_word) {
+ return false;
+ }
+
+ old_val = c->enable[id_word];
+ new_val = val;
+
+ if (id_word == 0) {
+ new_val &= ~0x1;
+ }
+
+ c->enable[id_word] = new_val;
+
+ xor_val = old_val ^ new_val;
+ for (i = 0; i < 32; i++) {
+ id = id_word * 32 + i;
+ id_mask = 1 << i;
+ id_prio = priority[id];
+ if (!(xor_val & id_mask)) {
+ continue;
+ }
+ if ((new_val & id_mask) &&
+ (level[id_word] & id_mask)) {
+ c->pending[id_word] |= id_mask;
+ c->pending_priority[id] = id_prio;
+ } else if (!(new_val & id_mask)) {
+ c->pending[id_word] &= ~id_mask;
+ c->pending_priority[id] = 0;
+ c->claimed[id_word] &= ~id_mask;
+ }
+ }
+
+ context_update(c);
+ return true;
+}
+
+bool plic_t::context_read(plic_context_t *c,
+ reg_t offset, uint32_t *val)
+{
+ bool ret = true;
+
+ switch (offset) {
+ case CONTEXT_THRESHOLD:
+ *val = c->priority_threshold;
+ break;
+ case CONTEXT_CLAIM:
+ *val = context_claim(c);
+ break;
+ default:
+ ret = false;
+ break;
+ };
+
+ return ret;
+}
+
+bool plic_t::context_write(plic_context_t *c,
+ reg_t offset, uint32_t val)
+{
+ uint32_t id_word, id_mask;
+ bool ret = true, update = false;
+
+ switch (offset) {
+ case CONTEXT_THRESHOLD:
+ val &= ((1 << PLIC_PRIO_BITS) - 1);
+ if (val <= max_prio)
+ c->priority_threshold = val;
+ else
+ update = true;
+ break;
+ case CONTEXT_CLAIM:
+ id_word = val / 32;
+ id_mask = 1 << (val % 32);
+ if ((val < num_ids) &&
+ (c->enable[id_word] & id_mask)) {
+ c->claimed[id_word] &= ~id_mask;
+ update = true;
+ }
+ break;
+ default:
+ ret = false;
+ update = true;
+ break;
+ };
+
+ if (update) {
+ context_update(c);
+ }
+
+ return ret;
+}
+
+void plic_t::set_interrupt_level(uint32_t id, int lvl)
+{
+ uint8_t i, id_prio, id_word;
+ uint32_t id_mask;
+ plic_context_t *c = NULL;
+
+ if (id <= 0 || num_ids <= id) {
+ return;
+ }
+
+ id_prio = priority[id];
+ id_word = id / 32;
+ id_mask = 1 << (id % 32);
+
+ if (lvl) {
+ level[id_word] |= id_mask;
+ } else {
+ level[id_word] &= ~id_mask;
+ }
+
+ /*
+ * Note: PLIC interrupts are level-triggered. As of now,
+ * there is no notion of edge-triggered interrupts. To
+ * handle this we auto-clear edge-triggered interrupts
+ * when PLIC context CLAIM register is read.
+ */
+ for (i = 0; i < contexts.size(); i++) {
+ c = &contexts[i];
+
+ if (c->enable[id_word] & id_mask) {
+ if (lvl) {
+ c->pending[id_word] |= id_mask;
+ c->pending_priority[id] = id_prio;
+ } else {
+ c->pending[id_word] &= ~id_mask;
+ c->pending_priority[id] = 0;
+ c->claimed[id_word] &= ~id_mask;
+ }
+ context_update(c);
+ break;
+ }
+ }
+}
+
+bool plic_t::load(reg_t addr, size_t len, uint8_t* bytes)
+{
+ bool ret = false;
+ uint32_t cntx, val = 0;
+
+ /* Only 32bit loads supported */
+ if (len != 4) {
+ return false;
+ }
+
+ if (PRIORITY_BASE <= addr && addr < ENABLE_BASE) {
+ ret = priority_read(addr, &val);
+ } else if (ENABLE_BASE <= addr && addr < CONTEXT_BASE) {
+ cntx = (addr - ENABLE_BASE) / ENABLE_PER_HART;
+ addr -= cntx * ENABLE_PER_HART + ENABLE_BASE;
+ if (cntx < contexts.size()) {
+ ret = context_enable_read(&contexts[cntx], addr, &val);
+ }
+ } else if (CONTEXT_BASE <= addr && addr < REG_SIZE) {
+ cntx = (addr - CONTEXT_BASE) / CONTEXT_PER_HART;
+ addr -= cntx * CONTEXT_PER_HART + CONTEXT_BASE;
+ if (cntx < contexts.size()) {
+ ret = context_read(&contexts[cntx], addr, &val);
+ }
+ }
+
+ if (ret) {
+ memcpy(bytes, (uint8_t *)&val, len);
+ }
+
+ return ret;
+}
+
+bool plic_t::store(reg_t addr, size_t len, const uint8_t* bytes)
+{
+ bool ret = false;
+ uint32_t cntx, val;
+
+ /* Only 32bit stores supported */
+ if (len != 4) {
+ return false;
+ }
+
+ memcpy((uint8_t *)&val, bytes, len);
+
+ if (PRIORITY_BASE <= addr && addr < ENABLE_BASE) {
+ ret = priority_write(addr, val);
+ } else if (ENABLE_BASE <= addr && addr < CONTEXT_BASE) {
+ cntx = (addr - ENABLE_BASE) / ENABLE_PER_HART;
+ addr -= cntx * ENABLE_PER_HART + ENABLE_BASE;
+ if (cntx < contexts.size())
+ ret = context_enable_write(&contexts[cntx], addr, val);
+ } else if (CONTEXT_BASE <= addr && addr < REG_SIZE) {
+ cntx = (addr - CONTEXT_BASE) / CONTEXT_PER_HART;
+ addr -= cntx * CONTEXT_PER_HART + CONTEXT_BASE;
+ if (cntx < contexts.size())
+ ret = context_write(&contexts[cntx], addr, val);
+ }
+
+ return ret;
+}
diff --git a/riscv/processor.h b/riscv/processor.h
index fc80914..8194046 100644
--- a/riscv/processor.h
+++ b/riscv/processor.h
@@ -365,6 +365,7 @@ private:
friend class mmu_t;
friend class clint_t;
+ friend class plic_t;
friend class extension_t;
void parse_varch_string(const char*);
diff --git a/riscv/riscv.mk.in b/riscv/riscv.mk.in
index bbac794..91f7a5f 100644
--- a/riscv/riscv.mk.in
+++ b/riscv/riscv.mk.in
@@ -87,6 +87,8 @@ riscv_srcs = \
devices.cc \
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 8a15560..5ce7d21 100644
--- a/riscv/sim.cc
+++ b/riscv/sim.cc
@@ -98,6 +98,29 @@ 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
int cpu_offset = 0, rc;
size_t cpu_idx = 0;
@@ -221,6 +244,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 c7dbd47..7816b87 100644
--- a/riscv/sim.h
+++ b/riscv/sim.h
@@ -82,6 +82,8 @@ private:
bool dtb_enabled;
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;