From ed82c5487fbbdd0d2ce17254aa6f8ca135a38a5a Mon Sep 17 00:00:00 2001 From: Stefan Date: Thu, 3 Jun 2021 22:42:59 +0200 Subject: [PATCH] implement UART, CLINT, device tree, opensbi build ...plus some comfort improvements, more testing. This now successfully boots OpenSBI when built like specified in the Makefile! --- .gitignore | 2 + .gitmodules | 3 ++ Makefile | 20 +++++++- dts.dts | 69 ++++++++++++++++++++++++++++ dts.dts.final | 81 ++++++++++++++++++++++++++++++++ elfy/elfy.h | 6 ++- elfy/src/lib.rs | 36 +++++++++++++-- opensbi | 1 + riscv-tests | 2 +- src/cpu.h | 23 ++++++---- src/csr.h | 22 +++++---- src/emu.h | 31 +++++++++---- src/main.c | 100 ++++++++++++++++++++++++++++++---------- src/mem.h | 119 ++++++++++++++++++++++++++++++++++++++++++++++-- src/trap.h | 43 +++++++++++++++-- src/types.h | 24 +++++++++- src/uart.h | 72 +++++++++++++++++++++++++++++ test.sh | 16 +++++-- 18 files changed, 600 insertions(+), 70 deletions(-) create mode 100644 dts.dts create mode 100644 dts.dts.final create mode 160000 opensbi create mode 100644 src/uart.h diff --git a/.gitignore b/.gitignore index 462ce12b..a3d76208 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ rvc *.o elfy/target elfy/Cargo.lock +fw_payload.* +*.dtb diff --git a/.gitmodules b/.gitmodules index b643c44e..490662fb 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,6 @@ [submodule "riscv-rust"] path = riscv-rust url = https://github.com/takahirox/riscv-rust +[submodule "opensbi"] + path = opensbi + url = https://github.com/riscv/opensbi diff --git a/Makefile b/Makefile index 17af4376..67013fcc 100644 --- a/Makefile +++ b/Makefile @@ -11,7 +11,7 @@ INC_DIRS := $(shell find $(SRC_DIRS) -type d) INC_FLAGS := $(addprefix -I,$(INC_DIRS)) $(TARGET): $(OBJS) - $(CC) $(LDFLAGS) -I./elfy/elfy.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/debug/ -Wl,--no-as-needed -ldl -lpthread -lelfy + $(CC) $(LDFLAGS) -I./elfy/elfy.h $(OBJS) -o $@ $(LOADLIBES) $(LDLIBS) -L./elfy/target/release/ -Wl,--no-as-needed -ldl -lpthread -lelfy .PHONY: clean clean: @@ -19,3 +19,21 @@ clean: -include $(DEPS) +# build device tree +dts.dtb: dts.dts + dtc -o $@ $< + +OPENSBI_BUILD="opensbi/build/platform/generic/firmware" + +fw_payload.bin: fw_payload.elf + cp $(OPENSBI_BUILD)/fw_payload.bin . +fw_payload.elf: opensbi + env CROSS_COMPILE=riscv64-elf- \ + PLATFORM=generic \ + PLATFORM_RISCV_XLEN=32 \ + PLATFORM_RISCV_ISA=rv32ima \ + PLATFORM_RISCV_ABI=ilp32 \ + FW_PIC=n \ + ELFFLAGS=-L/usr/lib/gcc/riscv64-elf/10.2.0/rv32im/ilp32 \ + $(MAKE) -C opensbi all + cp $(OPENSBI_BUILD)/fw_payload.elf . diff --git a/dts.dts b/dts.dts new file mode 100644 index 00000000..36e5f2ba --- /dev/null +++ b/dts.dts @@ -0,0 +1,69 @@ +/dts-v1/; + +/ { + #address-cells = <0x2>; + #size-cells = <0x2>; + compatible = "riscv-virtio"; + model = "riscv-virtio,qemu"; + + chosen { + bootargs = "ttyS0"; + stdout-path = "/uart@10000000"; + }; + + uart@10000000 { + interrupts = <0x1>; + interrupt-parent = <0x2>; + clock-frequency = <0x384000>; + reg = <0x0 0x10000000 0x0 0x100>; + compatible = "ns16550a"; + }; + + cpus { + #address-cells = <0x1>; + #size-cells = <0x0>; + timebase-frequency = <0x989680>; + + cpu-map { + cluster0 { + core0 { + cpu = <0x1>; + }; + }; + }; + + cpu@0 { + phandle = <0x1>; + device_type = "cpu"; + reg = <0x0>; + status = "okay"; + compatible = "riscv"; + riscv,isa = "rv64imasu"; + + interrupt-controller { + phandle = <0x2>; + #interrupt-cells = <0x1>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + }; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x8000000>; + }; + + soc { + #address-cells = <0x2>; + #size-cells = <0x2>; + compatible = "simple-bus"; + ranges; + + clint@2000000 { + interrupts-extended = <0x2 0x3 0x2 0x7>; + reg = <0x0 0x2000000 0x0 0x10000>; + compatible = "riscv,clint0"; + }; + }; +}; diff --git a/dts.dts.final b/dts.dts.final new file mode 100644 index 00000000..93fa960f --- /dev/null +++ b/dts.dts.final @@ -0,0 +1,81 @@ +/dts-v1/; + +/ { + #address-cells = <0x2>; + #size-cells = <0x2>; + compatible = "riscv-virtio"; + model = "riscv-virtio,qemu"; + + chosen { + bootargs = "ttyS0"; + stdout-path = "/uart@10000000"; + }; + + uart@10000000 { + interrupts = <0x1>; + interrupt-parent = <0x3>; + clock-frequency = <0x384000>; + reg = <0x0 0x10000000 0x0 0x100>; + compatible = "ns16550a"; + }; + + cpus { + #address-cells = <0x1>; + #size-cells = <0x0>; + timebase-frequency = <0x989680>; + + cpu-map { + cluster0 { + core0 { + cpu = <0x1>; + }; + }; + }; + + cpu@0 { + phandle = <0x1>; + device_type = "cpu"; + reg = <0x0>; + status = "okay"; + compatible = "riscv"; + riscv,isa = "rv64imasu"; + mmu-type = "riscv,sv32"; + + interrupt-controller { + phandle = <0x2>; + #interrupt-cells = <0x1>; + interrupt-controller; + compatible = "riscv,cpu-intc"; + }; + }; + }; + + memory@80000000 { + device_type = "memory"; + reg = <0x0 0x80000000 0x0 0x8000000>; + }; + + soc { + #address-cells = <0x2>; + #size-cells = <0x2>; + compatible = "simple-bus"; + ranges; + + interrupt-controller@c000000 { + phandle = <0x3>; + riscv,ndev = <0x35>; + reg = <0x0 0xc000000 0x0 0x4000000>; + interrupts-extended = <0x2 0xb 0x2 0x9>; + interrupt-controller; + compatible = "riscv,plic0"; + #interrupt-cells = <0x1>; + #address-cells = <0x0>; + }; + + clint@2000000 { + interrupts-extended = <0x2 0x3 0x2 0x7>; + reg = <0x0 0x2000000 0x0 0x10000>; + compatible = "riscv,clint0"; + }; + }; +}; diff --git a/elfy/elfy.h b/elfy/elfy.h index 2ed7fc78..ecfb0175 100644 --- a/elfy/elfy.h +++ b/elfy/elfy.h @@ -3,4 +3,8 @@ #include #include -void load_elf(const char *path, uint64_t path_len, uint8_t *data, uint64_t data_len); +int32_t load_elf(const char *path, + uint64_t path_len, + uint8_t *data, + uint64_t data_len, + bool verbose); diff --git a/elfy/src/lib.rs b/elfy/src/lib.rs index 6fd95df1..81b0399d 100644 --- a/elfy/src/lib.rs +++ b/elfy/src/lib.rs @@ -4,12 +4,24 @@ use std::os::unix::ffi::OsStrExt; use std::os::raw::c_char; #[no_mangle] -pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64) { +pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64, verbose: bool) -> i32 { + let res = std::panic::catch_unwind(|| { + do_load_elf(path, path_len, data, data_len, verbose); + }); + match res { + Ok(()) => 0, + _ => 1, + } +} + +fn do_load_elf(path: *const c_char, path_len: u64, data: *mut u8, data_len: u64, verbose: bool) { let path = unsafe { CStr::from_bytes_with_nul_unchecked(std::slice::from_raw_parts(path as *const u8, path_len as usize)) }; let path = OsStr::from_bytes(path.to_bytes()); let path = PathBuf::from(path); - println!("Loading ELF binary '{}'", path.display()); + if verbose { + println!("Loading ELF binary '{}'", path.display()); + } let elf = elf::File::open_path(path).unwrap(); @@ -22,7 +34,25 @@ pub extern "C" fn load_elf(path: *const c_char, path_len: u64, data: *mut u8, da let addr = section.shdr.addr; let addr_real = addr & 0x7FFFFFFF; let size = section.data.len() as u64; - println!("Loading ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size); + + if name == ".comment" { + if verbose { + let comment = String::from_utf8_lossy(§ion.data); + println!("ELF comment: {}", comment); + } + continue; + } + + if addr == 0 { + if verbose { + println!("Skipping 'zero address' ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size); + } + continue; + } + + if verbose { + println!("Loading ELF section '{}' @{:#x} (@{:#x}) size={}", name, addr, addr_real, size); + } if addr_real + size > data_len { panic!("ELF section too big or offset to great"); diff --git a/opensbi b/opensbi new file mode 160000 index 00000000..f30b1894 --- /dev/null +++ b/opensbi @@ -0,0 +1 @@ +Subproject commit f30b18944e900174bfa9a372ed9d344256891e3a diff --git a/riscv-tests b/riscv-tests index 09cfdaac..1b2c3ea8 160000 --- a/riscv-tests +++ b/riscv-tests @@ -1 +1 @@ -Subproject commit 09cfdaacd9322cf0ac94818d8c852e1f4dc5bc4f +Subproject commit 1b2c3ea84a7f8d8a833fca4d2b9aebb7d1ba4269 diff --git a/src/cpu.h b/src/cpu.h index e0e9be03..34af321f 100644 --- a/src/cpu.h +++ b/src/cpu.h @@ -8,26 +8,31 @@ #include "emu.h" #include "csr.h" -cpu_t cpu_init(uint8_t *mem) { +cpu_t cpu_init(uint8_t *mem, uint8_t *dtb) { cpu_t ret; ret.clock = 0; for (uint i = 0; i < 32; i++) { ret.xreg[i] = 0; } - ret.xreg[0xb] = 0x1020; // linux? + ret.xreg[0xb] = 0x1020; // linux? device tree? ret.pc = 0x80000000; ret.mem = mem; ret.reservation_en = false; init_csrs(&ret); - ret.debug_single_step = -#ifdef SINGLE_STEP - true -#else - false -#endif - ; + ret.dtb = dtb; + + ret.clint.msip = false; + ret.clint.mtimecmp_lo = 0; + ret.clint.mtimecmp_hi = 0; + ret.clint.mtime_lo = 0; + ret.clint.mtime_hi = 0; + + ret.uart.rbr_thr_ier_iir = 0; + ret.uart.lcr_mcr_lsr_scr = 0x00200000; // LSR_THR_EMPTY is set + ret.uart.thre_ip = false; + ret.uart.interrupting = false; return ret; } diff --git a/src/csr.h b/src/csr.h index e72d5cc7..b3f0d7e2 100644 --- a/src/csr.h +++ b/src/csr.h @@ -55,9 +55,9 @@ uint read_csr_raw(cpu_t *cpu, uint address) { case CSR_SSTATUS: return cpu->csr.data[CSR_MSTATUS] & 0x000de162; case CSR_SIE: return cpu->csr.data[CSR_MIE] & 0x222; case CSR_SIP: return cpu->csr.data[CSR_MIP] & 0x222; + case CSR_TIME: return cpu->clint.mtime_lo; case CSR_CYCLE: return cpu->clock; - case CSR_MHARTID: return 0; // this has to be 0, always - /* case CSR_TIME => self.mmu.get_clint().read_mtime(), */ + case CSR_MHARTID: return 0; default: return cpu->csr.data[address & 0xffff]; } } @@ -87,6 +87,9 @@ void write_csr_raw(cpu_t *cpu, uint address, uint value) { /* CSR_TIME => { */ /* self.mmu.get_mut_clint().write_mtime(value); */ /* }, */ + case CSR_TIME: + // ignore writes + break; default: cpu->csr.data[address] = value; break; }; } @@ -95,9 +98,8 @@ void write_csr_raw(cpu_t *cpu, uint address, uint value) { uint get_csr(cpu_t *cpu, uint address, ins_ret *ret) { if (has_csr_access_privilege(cpu, address)) { uint r = read_csr_raw(cpu, address); -#ifdef VERBOSE - printf("CSR read @%03x = %08x\n", address, r); -#endif + if (VERBOSE >= 2) + printf("CSR read @%03x = %08x\n", address, r); return r; } else { ret->trap.en = true; @@ -108,9 +110,8 @@ uint get_csr(cpu_t *cpu, uint address, ins_ret *ret) { } void set_csr(cpu_t *cpu, uint address, uint value, ins_ret *ret) { -#ifdef VERBOSE - printf("CSR write @%03x = %08x\n", address, value); -#endif + if (VERBOSE >= 2) + printf("CSR write @%03x = %08x\n", address, value); if (has_csr_access_privilege(cpu, address)) { bool read_only = ((address >> 10) & 0x3) == 0x3; if (read_only) { @@ -118,10 +119,13 @@ void set_csr(cpu_t *cpu, uint address, uint value, ins_ret *ret) { ret->trap.type = trap_IllegalInstruction; ret->trap.value = cpu->pc; } else { - write_csr_raw(cpu, address, value); if (address == CSR_SATP) { // TODO: update MMU addressing mode + if (VERBOSE >= 1) + printf("WARN: Ignoring write to CSR_SATP\n"); + return; } + write_csr_raw(cpu, address, value); } } else { ret->trap.en = true; diff --git a/src/emu.h b/src/emu.h index 7e1e07ed..a4477d90 100644 --- a/src/emu.h +++ b/src/emu.h @@ -413,11 +413,7 @@ DEF(xori, FormatI, { // rv32i * END INSTRUCTIONS */ -#ifdef VERBOSE -#define pr_ins(name) printf("INS %s (%08x)\n", #name, ins_word); -#else -#define pr_ins(name) {} -#endif +#define pr_ins(name) if (VERBOSE >= 3) printf("INS %s (%08x)\n", #name, ins_word); #define RUN(name, data, insf) case data : { \ pr_ins(name) \ @@ -538,10 +534,11 @@ ins_ret ins_select(cpu_t *cpu, uint ins_word) { RUN(wfi, 0x10500073, ins_FormatEmpty) } - printf("Invalid instruction: %08x\n", ins_word); + if (VERBOSE >= 1) + printf("Invalid instruction: %08x\n", ins_word); ret.trap.en = true; ret.trap.type = trap_IllegalInstruction; - ret.trap.value = cpu->pc; + ret.trap.value = ins_word; return ret; } @@ -567,10 +564,26 @@ void emulate(cpu_t *cpu) { ret.trap.value = cpu->pc; } - if (ret.trap.en) { - handle_trap(cpu, &ret, false); + // handle CLINT IRQs + if (cpu->clint.msip) { + cpu->csr.data[CSR_MIP] |= MIP_MSIP; } + cpu->clint.mtime_lo++; + cpu->clint.mtime_hi += cpu->clint.mtime_lo == 0 ? 1 : 0; + + if (cpu->clint.mtimecmp_lo != 0 && cpu->clint.mtimecmp_hi != 0 && (cpu->clint.mtime_hi > cpu->clint.mtimecmp_hi || (cpu->clint.mtime_hi == cpu->clint.mtimecmp_hi && cpu->clint.mtime_lo >= cpu->clint.mtimecmp_lo))) { + cpu->csr.data[CSR_MIP] |= MIP_MTIP; + } + + uart_tick(cpu); + if (cpu->uart.interrupting) { + uint cur_mip = read_csr_raw(cpu, CSR_MIP); + write_csr_raw(cpu, CSR_MIP, cur_mip | MIP_SEIP); + } + + handle_irq_and_trap(cpu, &ret); + // ret.pc_val should be set to pc+4 by default cpu->pc = ret.pc_val; } diff --git a/src/main.c b/src/main.c index 611306cb..a787d7f1 100644 --- a/src/main.c +++ b/src/main.c @@ -1,6 +1,3 @@ -/* #define VERBOSE */ -/* #define SINGLE_STEP */ - #include #include #include @@ -14,44 +11,97 @@ #include "../elfy/elfy.h" -// lots of inspiration from: -// https://github.com/takahirox/riscv-rust/blob/master/src/cpu.rs - -#define handle_error(msg) \ - do { perror(msg); exit(EXIT_FAILURE); } while (0) - const int MEM_SIZE = 1024 * 1024 * 128; +size_t get_filesize(const char* filename) { + struct stat st; + stat(filename, &st); + return st.st_size; +} + +uint8_t* get_mmap_ptr(const char* filename) { + size_t filesize = get_filesize(filename); + int fd = open(filename, O_RDONLY, 0); + uint8_t* mmapped_data = mmap(NULL, filesize, PROT_READ, MAP_PRIVATE, fd, 0); + return mmapped_data; +} + +void usage() { + printf("Usage: rvc (-e |-b ) [-d ] [-v (0|1|2|3)] [-s]\n"); + exit(EXIT_FAILURE); +} + int main(int argc, char *argv[]) { - if (argc > 2 || (argc == 2 && !strcmp(argv[1], "--help"))) { - printf("Usage: rvc []\n"); - exit(EXIT_FAILURE); + char *elf = NULL; + char *bin = NULL; + char *dtb = NULL; + + int c; + + while ((c = getopt (argc, argv, "e:b:d:v:s")) != -1) { + switch (c) + { + case 'e': + elf = optarg; + break; + case 'b': + bin = optarg; + break; + case 'd': + dtb = optarg; + break; + case 'v': + VERBOSE = atoi(optarg); + break; + case 's': + SINGLE_STEP = 1; + break; + case '?': + default: + usage(); + } + } + + if ((!elf && !bin) || (elf && bin)) { + usage(); } uint8_t *mem = malloc(MEM_SIZE); - - if (argc == 2) { - load_elf(argv[1], strlen(argv[1]) + 1, mem, MEM_SIZE); + if (elf) { + if (load_elf(elf, strlen(elf) + 1, mem, MEM_SIZE, VERBOSE >= 1)) { + exit(EXIT_FAILURE+1); + } + } else if (bin) { + uint8_t *bin_ptr = get_mmap_ptr(bin); + memcpy(mem, bin_ptr, get_filesize(bin)); } - cpu_t cpu = cpu_init(mem); + uint8_t *dtb_ptr = get_mmap_ptr("./dts.dtb"); - printf("CPU initialized!\n"); + cpu_t cpu = cpu_init(mem, dtb_ptr); -#ifdef VERBOSE - cpu_dump(&cpu); -#endif + if (VERBOSE >= 1) + printf("CPU initialized!\n"); + + if (VERBOSE >= 3) + cpu_dump(&cpu); while (1) { cpu_tick(&cpu); -#ifdef VERBOSE - cpu_dump(&cpu); -#endif - if (cpu.debug_single_step) { + if (VERBOSE >= 3) + cpu_dump(&cpu); + + if (SINGLE_STEP) { fflush(stdout); fflush(stdin); - while(getchar()!='\n'); + char ch; + gc: ch = getchar(); + switch (ch) { + case '\n': break; + case 'c': SINGLE_STEP = 0; break; + default: goto gc; + } } } diff --git a/src/mem.h b/src/mem.h index 01517965..686315a0 100644 --- a/src/mem.h +++ b/src/mem.h @@ -1,13 +1,70 @@ #ifndef MEM_H #define MEM_H +#include #include #include "types.h" +#include "uart.h" // little endian, zero extended uint mem_get_byte(cpu_t *cpu, uint addr) { - /* printf("TRACE: mem_get_byte(%d)\n", addr); */ - assert(addr & 0x80000000); + if (VERBOSE >= 3) + printf("mem_get_byte(%08x)\n", addr); + + if (cpu->dtb != NULL && addr >= 0x1020 && addr <= 0x1fff) { + if (VERBOSE >= 2) + printf("DTB read @%04x/%04x\n", addr, addr - 0x1020); + return cpu->dtb[addr - 0x1020]; + } + + switch (addr) { + // CLINT + case 0x02000000: return cpu->clint.msip ? 1 : 0; + case 0x02000001: return 0; + case 0x02000002: return 0; + case 0x02000003: return 0; + + case 0x02004000: return (cpu->clint.mtimecmp_lo >> 0) & 0xFF; + case 0x02004001: return (cpu->clint.mtimecmp_lo >> 8) & 0xFF; + case 0x02004002: return (cpu->clint.mtimecmp_lo >> 16) & 0xFF; + case 0x02004003: return (cpu->clint.mtimecmp_lo >> 24) & 0xFF; + case 0x02004004: return (cpu->clint.mtimecmp_hi >> 0) & 0xFF; + case 0x02004005: return (cpu->clint.mtimecmp_hi >> 8) & 0xFF; + case 0x02004006: return (cpu->clint.mtimecmp_hi >> 16) & 0xFF; + case 0x02004007: return (cpu->clint.mtimecmp_hi >> 24) & 0xFF; + + case 0x0200bff8: return (cpu->clint.mtime_lo >> 0) & 0xFF; + case 0x0200bff9: return (cpu->clint.mtime_lo >> 8) & 0xFF; + case 0x0200bffa: return (cpu->clint.mtime_lo >> 16) & 0xFF; + case 0x0200bffb: return (cpu->clint.mtime_lo >> 24) & 0xFF; + case 0x0200bffc: return (cpu->clint.mtime_hi >> 0) & 0xFF; + case 0x0200bffd: return (cpu->clint.mtime_hi >> 8) & 0xFF; + case 0x0200bffe: return (cpu->clint.mtime_hi >> 16) & 0xFF; + case 0x0200bfff: return (cpu->clint.mtime_hi >> 24) & 0xFF; + + // UART (first has rbr_thr_ier_iir, second has lcr_mcr_lsr_scr) + case 0x10000000: + if ((UART_GET2(LCR) >> 7) == 0) { + uint rbr = UART_GET1(RBR); + UART_SET1(RBR, 0); + UART_SET2(LSR, (UART_GET2(LSR) & ~LSR_DATA_AVAILABLE)); + uart_update_iir(cpu); + return rbr; + } else { + return 0; + } + case 0x10000001: return UART_GET2(LCR) >> 7 == 0 ? UART_GET1(IER) : 0; + case 0x10000002: return UART_GET1(IIR); + case 0x10000003: return UART_GET2(LCR); + case 0x10000004: return UART_GET2(MCR); + case 0x10000005: return UART_GET2(LSR); + case 0x10000007: return UART_GET2(SCR); + } + + if ((addr & 0x80000000) == 0) { + return 0; + } + return cpu->mem[addr & 0x7FFFFFFF]; } @@ -23,7 +80,63 @@ uint mem_get_word(cpu_t *cpu, uint addr) { } void mem_set_byte(cpu_t *cpu, uint addr, uint val) { - assert(addr & 0x80000000); + if (VERBOSE >= 3) + printf("mem_set_byte(%08x, %08x)\n", addr, val); + + switch (addr) { + // CLINT + case 0x02000000: cpu->clint.msip = (val & 1) != 0; return; + case 0x02000001: return; + case 0x02000002: return; + case 0x02000003: return; + + case 0x02004000: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 0)) | (val << 0); return; + case 0x02004001: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 8)) | (val << 8); return; + case 0x02004002: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 16)) | (val << 16); return; + case 0x02004003: cpu->clint.mtimecmp_lo = (cpu->clint.mtimecmp_lo & ~(0xff << 24)) | (val << 24); return; + case 0x02004004: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 0)) | (val << 0); return; + case 0x02004005: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 8)) | (val << 8); return; + case 0x02004006: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 16)) | (val << 16); return; + case 0x02004007: cpu->clint.mtimecmp_hi = (cpu->clint.mtimecmp_hi & ~(0xff << 24)) | (val << 24); return; + + case 0x0200bff8: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 0)) | (val << 0); return; + case 0x0200bff9: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 8)) | (val << 8); return; + case 0x0200bffa: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 16)) | (val << 16); return; + case 0x0200bffb: cpu->clint.mtime_lo = (cpu->clint.mtime_lo & ~(0xff << 24)) | (val << 24); return; + case 0x0200bffc: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 0)) | (val << 0); return; + case 0x0200bffd: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 8)) | (val << 8); return; + case 0x0200bffe: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 16)) | (val << 16); return; + case 0x0200bfff: cpu->clint.mtime_hi = (cpu->clint.mtime_hi & ~(0xff << 24)) | (val << 24); return; + + // UART (first has rbr_thr_ier_iir, second has lcr_mcr_lsr_scr) + case 0x10000000: + if ((UART_GET2(LCR) >> 7) == 0) { + UART_SET1(THR, val); + UART_SET2(LSR, (UART_GET2(LSR) & ~LSR_THR_EMPTY)); + uart_update_iir(cpu); + } + return; + case 0x10000001: + if (UART_GET2(LCR) >> 7 == 0) { + if ((UART_GET1(IER) & IER_THREINT_BIT) == 0 && + (val & IER_THREINT_BIT) != 0 && + UART_GET1(THR) == 0) + { + cpu->uart.thre_ip = true; + } + UART_SET1(IER, val); + uart_update_iir(cpu); + } + return; + case 0x10000003: UART_SET2(LCR, val); return; + case 0x10000004: UART_SET2(MCR, val); return; + case 0x10000007: UART_SET2(SCR, val); return; + } + + if ((addr & 0x80000000) == 0) { + return; + } + cpu->mem[addr & 0x7FFFFFFF] = val; } diff --git a/src/trap.h b/src/trap.h index 032e2b9c..9a59f35b 100644 --- a/src/trap.h +++ b/src/trap.h @@ -33,6 +33,14 @@ const uint trap_UserExternalInterrupt = interrupt_offset + 8; const uint trap_SupervisorExternalInterrupt = interrupt_offset + 9; const uint trap_MachineExternalInterrupt = interrupt_offset + 11; +const uint MIP_MEIP = 0x800; +const uint MIP_MTIP = 0x080; +const uint MIP_MSIP = 0x008; +const uint MIP_SEIP = 0x200; +const uint MIP_STIP = 0x020; +const uint MIP_SSIP = 0x002; +const uint MIP_ALL = MIP_MEIP | MIP_MTIP | MIP_MSIP | MIP_SEIP | MIP_STIP | MIP_SSIP; + // include after trap_ definitions #include "csr.h" @@ -133,12 +141,39 @@ bool handle_trap(cpu_t *cpu, ins_ret *ret, bool is_interrupt) { write_csr_raw(cpu, CSR_SSTATUS, new_status); } -#ifdef VERBOSE - printf("trap: type=%08x value=%08x (IRQ: %d) moved PC from @%08x to @%08x\n", t.type, t.value, is_interrupt, cpu->pc, ret->pc_val); -#endif - /* cpu->debug_single_step = true; */ + if (VERBOSE >= 1) + printf("trap: type=%08x value=%08x (IRQ: %d) moved PC from @%08x to @%08x\n", t.type, t.value, is_interrupt, cpu->pc, ret->pc_val); return true; } +void handle_irq_and_trap(cpu_t *cpu, ins_ret *ret) { + bool trap = ret->trap.en; + uint mip_reset = MIP_ALL; + uint cur_mip = read_csr_raw(cpu, CSR_MIP); + + if (!trap) { + uint mirq = cur_mip & read_csr_raw(cpu, CSR_MIE); +#define HANDLE(mip, ttype) case mip: mip_reset = mip; ret->trap.en = true; ret->trap.type = ttype; break; + switch (mirq & MIP_ALL) { + HANDLE(MIP_MEIP, trap_MachineExternalInterrupt) + HANDLE(MIP_MSIP, trap_MachineSoftwareInterrupt) + HANDLE(MIP_MTIP, trap_MachineTimerInterrupt) + HANDLE(MIP_SEIP, trap_SupervisorExternalInterrupt) + HANDLE(MIP_SSIP, trap_SupervisorSoftwareInterrupt) + HANDLE(MIP_STIP, trap_SupervisorTimerInterrupt) + } +#undef HANDLE + } + + bool irq = mip_reset != MIP_ALL; + if (trap || irq) { + bool handled = handle_trap(cpu, ret, irq); + if (handled && irq) { + // reset MIP value since IRQ was handled + write_csr_raw(cpu, CSR_MIP, cur_mip & ~mip_reset); + } + } +} + #endif diff --git a/src/types.h b/src/types.h index 38dd52d5..d5472569 100644 --- a/src/types.h +++ b/src/types.h @@ -5,6 +5,9 @@ #include #include +static int VERBOSE = 0; +static bool SINGLE_STEP = false; + typedef uint32_t uint; typedef uint32_t uint; @@ -13,21 +16,38 @@ typedef struct { uint privilege; } csr_state; +typedef struct { + uint rbr_thr_ier_iir; + uint lcr_mcr_lsr_scr; + bool thre_ip; + bool interrupting; +} uart_state; + +typedef struct { + bool msip; + uint mtimecmp_lo; + uint mtimecmp_hi; + uint mtime_lo; + uint mtime_hi; +} clint_state; + typedef struct { uint clock; uint xreg[32]; uint pc; uint8_t *mem; + uint8_t *dtb; csr_state csr; + clint_state clint; + uart_state uart; bool reservation_en; uint reservation_addr; - - bool debug_single_step; } cpu_t; typedef struct { bool en; + bool irq; uint type; uint value; } trap; diff --git a/src/uart.h b/src/uart.h new file mode 100644 index 00000000..372e592d --- /dev/null +++ b/src/uart.h @@ -0,0 +1,72 @@ +#ifndef UART_H +#define UART_H + +#include +#include "types.h" + +const uint SHIFT_RBR = 0; +const uint SHIFT_THR = 8; +const uint SHIFT_IER = 16; +const uint SHIFT_IIR = 24; +const uint SHIFT_LCR = 0; +const uint SHIFT_MCR = 8; +const uint SHIFT_LSR = 16; +const uint SHIFT_SCR = 24; + +#define UART_GET1(x) ((cpu->uart.rbr_thr_ier_iir >> SHIFT_##x) & 0xff) +#define UART_GET2(x) ((cpu->uart.lcr_mcr_lsr_scr >> SHIFT_##x) & 0xff) + +#define UART_SET1(x, val) cpu->uart.rbr_thr_ier_iir = (cpu->uart.rbr_thr_ier_iir & ~(0xff << SHIFT_##x)) | (val << SHIFT_##x) +#define UART_SET2(x, val) cpu->uart.lcr_mcr_lsr_scr = (cpu->uart.lcr_mcr_lsr_scr & ~(0xff << SHIFT_##x)) | (val << SHIFT_##x) + +const uint IER_RXINT_BIT = 0x1; +const uint IER_THREINT_BIT = 0x2; + +const uint IIR_THR_EMPTY = 0x2; +const uint IIR_RD_AVAILABLE = 0x4; +const uint IIR_NO_INTERRUPT = 0x7; + +const uint LSR_DATA_AVAILABLE = 0x1; +const uint LSR_THR_EMPTY = 0x20; + +void uart_update_iir(cpu_t *cpu) { + bool rx_ip = (UART_GET1(IER) & IER_RXINT_BIT) != 0 && UART_GET1(RBR) != 0; + bool thre_ip = (UART_GET1(IER) & IER_THREINT_BIT) != 0 && UART_GET1(THR) == 0; + UART_SET1(IIR, (rx_ip ? IIR_RD_AVAILABLE : (thre_ip ? IIR_THR_EMPTY : IIR_NO_INTERRUPT))); +} + +void uart_tick(cpu_t *cpu) { + bool rx_ip = false; + + if ((cpu->clock % 0x38400) == 0 && UART_GET1(RBR) == 0) { + uint value = 0; // TODO: Add actual input logic + if (value != 0) { + UART_SET1(RBR, value); + UART_SET2(LSR, (UART_GET2(LSR) | LSR_DATA_AVAILABLE)); + uart_update_iir(cpu); + if ((UART_GET1(IER) & IER_RXINT_BIT) != 0) { + rx_ip = true; + } + } + } + + uint thr = UART_GET1(THR); + if ((cpu->clock & 0x16) == 0 && thr != 0) { + printf("%c", (char)thr); + UART_SET1(THR, 0); + UART_SET2(LSR, (UART_GET2(LSR) | LSR_THR_EMPTY)); + uart_update_iir(cpu); + if ((UART_GET1(IER) & IER_THREINT_BIT) != 0) { + cpu->uart.thre_ip = true; + } + } + + if (cpu->uart.thre_ip || rx_ip) { + cpu->uart.interrupting = true; + cpu->uart.thre_ip = false; + } else { + cpu->uart.interrupting = false; + } +} + +#endif diff --git a/test.sh b/test.sh index 91f9fa35..168c4e4f 100755 --- a/test.sh +++ b/test.sh @@ -19,8 +19,8 @@ function run_test { popd - echo "Running: ./rvc \"./riscv-tests/isa/$1\"" - timeout 5s ./rvc "./riscv-tests/isa/$1" + echo "Running: ./rvc -e \"./riscv-tests/isa/$1\"" + timeout 5s ./rvc -e "./riscv-tests/isa/$1" if [ $? -gt 0 ]; then echo "Test failed!" @@ -88,7 +88,17 @@ rv32ua-p-amoswap_w rv32ua-p-amoxor_w rv32mi-p-mcsr rv32mi-p-csr -rv32si-p-csr" +rv32si-p-csr +rv32si-p-scall" + +# excluded tests: +# rv32mi-p-scall, successful, but defined as "success if never returning" +# rv32mi-p-shamt, newer spec says ignoring is OK, so we do that instead +# rv32mi-p-sbreak, breakpoint, I believe from debug spec, no need +# rv32mi-p-illegal, requires virtual machine paging? (TVM) +# rv32mi-p-ma_addr, misaligned address access is ignored (this one passes though?) +# rv32mi-p-ma_fetch, ignored for same reason, fails though + for t in $TESTS; do if [[ "$t" =~ ^# ]]; then continue; fi echo