| /* |
| * RISCV emulator |
| * |
| * Copyright (c) 2016 Fabrice Bellard |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a copy |
| * of this software and associated documentation files (the "Software"), to deal |
| * in the Software without restriction, including without limitation the rights |
| * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| * copies of the Software, and to permit persons to whom the Software is |
| * furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice shall be included in |
| * all copies or substantial portions of the Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL |
| * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN |
| * THE SOFTWARE. |
| */ |
| #include <stdlib.h> |
| #include <stdio.h> |
| #include <stdarg.h> |
| #include <string.h> |
| #include <inttypes.h> |
| #include <assert.h> |
| #include <fcntl.h> |
| #include <errno.h> |
| #include <unistd.h> |
| #include <time.h> |
| #ifdef EMSCRIPTEN |
| #include "list.h" |
| #include <emscripten.h> |
| #else |
| #include <getopt.h> |
| #include <termios.h> |
| #endif |
| |
| #include "cutils.h" |
| #include "ide.h" |
| |
| /* |
| TODO: |
| - FS support |
| - various optimizations: cache PC page, optimize interrupt tests |
| */ |
| |
| #ifndef MAX_XLEN |
| //#define MAX_XLEN 32 |
| #define MAX_XLEN 64 |
| //#define MAX_XLEN 128 |
| #endif |
| |
| #ifndef FLEN |
| #if MAX_XLEN == 128 |
| #define FLEN 128 |
| #else |
| #define FLEN 64 |
| #endif |
| #endif /* !FLEN */ |
| |
| |
| #if MAX_XLEN >= 64 |
| #define CONFIG_EXT_DYN_XLEN /* allow dynamic XLEN change */ |
| #endif |
| #define CONFIG_EXT_C /* compressed instructions */ |
| |
| //#define DUMP_INVALID_MEM_ACCESS |
| //#define DUMP_MMU_EXCEPTIONS |
| //#define DUMP_INTERRUPTS |
| //#define DUMP_INVALID_CSR |
| //#define DUMP_EXCEPTIONS |
| //#define DUMP_CSR |
| //#define CONFIG_LOGFILE |
| |
| #if FLEN > 0 |
| #include "softfp.h" |
| #endif |
| |
| #define RTC_BASE_ADDR 0x40000000 |
| #define RAM_BASE_ADDR 0x80000000 |
| #define HTIF_BASE_ADDR 0x40008000 |
| #define IDE_BASE_ADDR 0x40009000 |
| #define PLIC_BASE_ADDR 0x40002000 |
| |
| #if MAX_XLEN == 32 |
| typedef uint32_t target_ulong; |
| typedef int32_t target_long; |
| #define PR_target_ulong "08x" |
| #elif MAX_XLEN == 64 |
| typedef uint64_t target_ulong; |
| typedef int64_t target_long; |
| #define PR_target_ulong "016" PRIx64 |
| #elif MAX_XLEN == 128 |
| typedef uint128_t target_ulong; |
| typedef int128_t target_long; |
| #define PR_target_ulong "016" PRIx64 /* XXX */ |
| #else |
| #error unsupported MAX_XLEN |
| #endif |
| |
| /* FLEN is the floating point register width */ |
| #if FLEN > 0 |
| #if FLEN == 32 |
| typedef uint32_t fp_uint; |
| #elif FLEN == 64 |
| typedef uint64_t fp_uint; |
| #elif FLEN == 128 |
| typedef uint128_t fp_uint; |
| #else |
| #error unsupported FLEN |
| #endif |
| #endif |
| |
| /* MLEN is the maximum memory access width */ |
| #if MAX_XLEN <= 32 && FLEN <= 32 |
| #define MLEN 32 |
| #elif MAX_XLEN <= 64 && FLEN <= 64 |
| #define MLEN 64 |
| #else |
| #define MLEN 128 |
| #endif |
| |
| #if MLEN == 32 |
| typedef uint32_t mem_uint_t; |
| #elif MLEN == 64 |
| typedef uint64_t mem_uint_t; |
| #elif MLEN == 128 |
| typedef uint128_t mem_uint_t; |
| #else |
| #unsupported MLEN |
| #endif |
| |
| #define TLB_SIZE 256 |
| |
| #define CAUSE_MISALIGNED_FETCH 0x0 |
| #define CAUSE_FAULT_FETCH 0x1 |
| #define CAUSE_ILLEGAL_INSTRUCTION 0x2 |
| #define CAUSE_BREAKPOINT 0x3 |
| #define CAUSE_MISALIGNED_LOAD 0x4 |
| #define CAUSE_FAULT_LOAD 0x5 |
| #define CAUSE_MISALIGNED_STORE 0x6 |
| #define CAUSE_FAULT_STORE 0x7 |
| #define CAUSE_USER_ECALL 0x8 |
| #define CAUSE_SUPERVISOR_ECALL 0x9 |
| #define CAUSE_HYPERVISOR_ECALL 0xa |
| #define CAUSE_MACHINE_ECALL 0xb |
| /* Note: converted to correct bit position at runtime */ |
| #define CAUSE_INTERRUPT ((uint32_t)1 << 31) |
| |
| #define PRV_U 0 |
| #define PRV_S 1 |
| #define PRV_H 2 |
| #define PRV_M 3 |
| |
| /* misa CSR */ |
| #define MCPUID_SUPER (1 << ('S' - 'A')) |
| #define MCPUID_USER (1 << ('U' - 'A')) |
| #define MCPUID_I (1 << ('I' - 'A')) |
| #define MCPUID_M (1 << ('M' - 'A')) |
| #define MCPUID_A (1 << ('A' - 'A')) |
| #define MCPUID_F (1 << ('F' - 'A')) |
| #define MCPUID_D (1 << ('D' - 'A')) |
| #define MCPUID_Q (1 << ('Q' - 'A')) |
| #define MCPUID_C (1 << ('C' - 'A')) |
| |
| /* mstatus CSR */ |
| |
| #define MSTATUS_SPIE_SHIFT 5 |
| #define MSTATUS_MPIE_SHIFT 7 |
| #define MSTATUS_SPP_SHIFT 8 |
| #define MSTATUS_MPP_SHIFT 11 |
| #define MSTATUS_VM_SHIFT 24 |
| #define MSTATUS_FS_SHIFT 13 |
| |
| #define MSTATUS_UIE (1 << 0) |
| #define MSTATUS_SIE (1 << 1) |
| #define MSTATUS_HIE (1 << 2) |
| #define MSTATUS_MIE (1 << 3) |
| #define MSTATUS_UPIE (1 << 4) |
| #define MSTATUS_SPIE (1 << MSTATUS_SPIE_SHIFT) |
| #define MSTATUS_HPIE (1 << 6) |
| #define MSTATUS_MPIE (1 << MSTATUS_MPIE_SHIFT) |
| #define MSTATUS_SPP (1 << MSTATUS_SPP_SHIFT) |
| #define MSTATUS_HPP (3 << 9) |
| #define MSTATUS_MPP (3 << MSTATUS_MPP_SHIFT) |
| #define MSTATUS_FS (3 << MSTATUS_FS_SHIFT) |
| #define MSTATUS_XS (3 << 15) |
| #define MSTATUS_MPRV (1 << 17) |
| #define MSTATUS_PUM (1 << 18) |
| #define MSTATUS_MXR (1 << 19) |
| #define MSTATUS_VM (0x1f << MSTATUS_VM_SHIFT) |
| #ifdef CONFIG_EXT_DYN_XLEN |
| /* Note: UB/SB/HB/MB could be moved to the 2 LSBs of |
| utvec/stvec/htvec/mtvec */ |
| #define MSTATUS_UB_SHIFT 32 |
| #define MSTATUS_SB_SHIFT 34 |
| #define MSTATUS_HB_SHIFT 36 |
| #define MSTATUS_MB_SHIFT 38 |
| #define MSTATUS_UPB_SHIFT 40 |
| #define MSTATUS_SPB_SHIFT 42 |
| #define MSTATUS_HPB_SHIFT 44 |
| #define MSTATUS_MPB_SHIFT 46 |
| #define MSTATUS_UB ((uint64_t)1 << MSTATUS_UB_SHIFT) |
| #define MSTATUS_SB ((uint64_t)1 << MSTATUS_SB_SHIFT) |
| #define MSTATUS_HB ((uint64_t)1 << MSTATUS_HB_SHIFT) |
| #define MSTATUS_MB ((uint64_t)1 << MSTATUS_MB_SHIFT) |
| #define MSTATUS_UPB ((uint64_t)1 << MSTATUS_UPB_SHIFT) |
| #define MSTATUS_SPB ((uint64_t)1 << MSTATUS_SPB_SHIFT) |
| #define MSTATUS_HPB ((uint64_t)1 << MSTATUS_HPB_SHIFT) |
| #define MSTATUS_MPB ((uint64_t)1 << MSTATUS_MPB_SHIFT) |
| #else |
| #define MSTATUS_UB 0 |
| #define MSTATUS_SB 0 |
| #define MSTATUS_HB 0 |
| #define MSTATUS_MB 0 |
| #define MSTATUS_UPB 0 |
| #define MSTATUS_SPB 0 |
| #define MSTATUS_HPB 0 |
| #define MSTATUS_MPB 0 |
| #endif |
| |
| #define MIP_USIP (1 << 0) |
| #define MIP_SSIP (1 << 1) |
| #define MIP_HSIP (1 << 2) |
| #define MIP_MSIP (1 << 3) |
| #define MIP_UTIP (1 << 4) |
| #define MIP_STIP (1 << 5) |
| #define MIP_HTIP (1 << 6) |
| #define MIP_MTIP (1 << 7) |
| #define MIP_UEIP (1 << 8) |
| #define MIP_SEIP (1 << 9) |
| #define MIP_HEIP (1 << 10) |
| #define MIP_MEIP (1 << 11) |
| |
| #define PG_SHIFT 12 |
| #define PG_MASK ((1 << PG_SHIFT) - 1) |
| |
| typedef struct { |
| target_ulong vaddr; |
| uintptr_t mem_addend; |
| } TLBEntry; |
| |
| typedef void DeviceWriteFunc(void *opaque, target_ulong offset, |
| target_ulong val, int size_log2); |
| typedef target_ulong DeviceReadFunc(void *opaque, target_ulong offset, |
| int size_log2); |
| |
| #define DEVIO_SIZE8 (1 << 0) |
| #define DEVIO_SIZE16 (1 << 1) |
| #define DEVIO_SIZE32 (1 << 2) |
| #define DEVIO_SIZE64 (1 << 3) |
| |
| typedef struct { |
| target_ulong addr; |
| target_ulong size; |
| BOOL is_ram; |
| uintptr_t phys_mem_offset; |
| void *opaque; |
| DeviceReadFunc *read_func; |
| DeviceWriteFunc *write_func; |
| int devio_flags; |
| } PhysMemoryRange; |
| |
| #define PHYS_MEM_RANGE_MAX 16 |
| |
| typedef struct RISCVMachine RISCVMachine; |
| |
| typedef struct RISCVCPUState { |
| target_ulong pc; |
| target_ulong reg[32]; |
| |
| #if FLEN > 0 |
| fp_uint fp_reg[32]; |
| uint32_t fflags; |
| uint8_t frm; |
| #endif |
| |
| uint8_t cur_xlen; /* current XLEN value, <= MAX_XLEN */ |
| uint8_t priv; /* see PRV_x */ |
| uint8_t fs; /* MSTATUS_FS value */ |
| |
| uint64_t insn_counter; |
| uint64_t timecmp; /* for RTC */ |
| BOOL power_down_flag; |
| |
| /* CSRs */ |
| target_ulong mstatus; |
| target_ulong mie; |
| target_ulong mtvec; |
| target_ulong mscratch; |
| target_ulong mepc; |
| target_ulong mcause; |
| target_ulong mbadaddr; |
| target_ulong mip; |
| target_ulong mhartid; /* ro */ |
| target_ulong misa; /* ro */ |
| target_ulong medeleg; |
| target_ulong mideleg; |
| |
| target_ulong stvec; |
| target_ulong sscratch; |
| target_ulong sepc; |
| target_ulong scause; |
| target_ulong sbadaddr; |
| target_ulong sptbr; |
| |
| target_ulong mcounteren[3]; |
| |
| target_ulong load_res; /* for atomic LR/SC */ |
| |
| uint8_t *phys_mem; |
| target_ulong phys_mem_size; /* in bytes */ |
| |
| int n_phys_mem_range; |
| PhysMemoryRange phys_mem_range[PHYS_MEM_RANGE_MAX]; |
| |
| RISCVMachine *machine_state; |
| |
| TLBEntry tlb_read[TLB_SIZE]; |
| TLBEntry tlb_write[TLB_SIZE]; |
| TLBEntry tlb_code[TLB_SIZE]; |
| } RISCVCPUState; |
| |
| void *mallocz(size_t size) |
| { |
| void *ptr; |
| ptr = malloc(size); |
| if (!ptr) |
| return NULL; |
| memset(ptr, 0, size); |
| return ptr; |
| } |
| |
| static no_inline int target_read_slow(RISCVCPUState *s, mem_uint_t *pval, |
| target_ulong addr, int size_log2); |
| static no_inline int target_write_slow(RISCVCPUState *s, target_ulong addr, |
| mem_uint_t val, int size_log2); |
| static void raise_exception2(RISCVCPUState *s, uint32_t cause, |
| target_ulong badaddr); |
| |
| |
| #ifdef CONFIG_LOGFILE |
| static FILE *log_file; |
| |
| void log_vprintf(const char *fmt, va_list ap) |
| { |
| if (!log_file) |
| log_file = fopen("/tmp/riscemu.log", "wb"); |
| vfprintf(log_file, fmt, ap); |
| } |
| #else |
| void log_vprintf(const char *fmt, va_list ap) |
| { |
| vprintf(fmt, ap); |
| } |
| #endif |
| |
| void __attribute__((format(printf, 1, 2))) log_printf(const char *fmt, ...) |
| { |
| va_list ap; |
| va_start(ap, fmt); |
| log_vprintf(fmt, ap); |
| va_end(ap); |
| } |
| |
| #if MAX_XLEN == 128 |
| static void fprint_target_ulong(FILE *f, target_ulong a) |
| { |
| fprintf(f, "%016" PRIx64 "%016" PRIx64, (uint64_t)(a >> 64), (uint64_t)a); |
| } |
| #else |
| static void fprint_target_ulong(FILE *f, target_ulong a) |
| { |
| fprintf(f, "%" PR_target_ulong, a); |
| } |
| #endif |
| |
| static void print_target_ulong(target_ulong a) |
| { |
| fprint_target_ulong(stdout, a); |
| } |
| |
| void cpu_register_ram(RISCVCPUState *s, target_ulong addr, |
| target_ulong size, |
| uintptr_t phys_mem_offset) |
| { |
| PhysMemoryRange *pr; |
| assert(s->n_phys_mem_range < PHYS_MEM_RANGE_MAX); |
| pr = &s->phys_mem_range[s->n_phys_mem_range++]; |
| pr->addr = addr; |
| pr->size = size; |
| pr->is_ram = TRUE; |
| pr->phys_mem_offset = phys_mem_offset; |
| } |
| |
| void cpu_register_device(RISCVCPUState *s, target_ulong addr, |
| target_ulong size, void *opaque, |
| DeviceReadFunc *read_func, DeviceWriteFunc *write_func, |
| int devio_flags) |
| { |
| PhysMemoryRange *pr; |
| assert(s->n_phys_mem_range < PHYS_MEM_RANGE_MAX); |
| pr = &s->phys_mem_range[s->n_phys_mem_range++]; |
| pr->addr = addr; |
| pr->size = size; |
| pr->is_ram = FALSE; |
| pr->opaque = opaque; |
| pr->read_func = read_func; |
| pr->write_func = write_func; |
| pr->devio_flags = devio_flags; |
| } |
| |
| static char *reg_name[32] = { |
| "zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", |
| "s0", "s1", "a0", "a1", "a2", "a3", "a4", "a5", |
| "a6", "a7", "s2", "s3", "s4", "s5", "s6", "s7", |
| "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6" |
| }; |
| |
| static void dump_regs(RISCVCPUState *s) |
| { |
| int i, cols; |
| const char priv_str[4] = "USHM"; |
| cols = 256 / MAX_XLEN; |
| printf("pc ="); |
| print_target_ulong(s->pc); |
| printf(" "); |
| for(i = 1; i < 32; i++) { |
| printf("%-3s=", reg_name[i]); |
| print_target_ulong(s->reg[i]); |
| if ((i & (cols - 1)) == (cols - 1)) |
| printf("\n"); |
| else |
| printf(" "); |
| } |
| printf("priv=%c", priv_str[s->priv]); |
| printf(" mstatus="); |
| print_target_ulong(s->mstatus); |
| printf(" cycles=%" PRId64, s->insn_counter); |
| printf("\n"); |
| #if 1 |
| printf(" mideleg="); |
| print_target_ulong(s->mideleg); |
| printf(" mie="); |
| print_target_ulong(s->mie); |
| printf(" mip="); |
| print_target_ulong(s->mip); |
| printf("\n"); |
| #endif |
| } |
| |
| static __attribute__((unused)) void cpu_abort(RISCVCPUState *s) |
| { |
| dump_regs(s); |
| abort(); |
| } |
| |
| /* return NULL if not found */ |
| static PhysMemoryRange *get_phys_mem_range(RISCVCPUState *s, target_ulong paddr) |
| { |
| PhysMemoryRange *pr; |
| int i; |
| for(i = 0; i < s->n_phys_mem_range; i++) { |
| pr = &s->phys_mem_range[i]; |
| if (paddr >= pr->addr && paddr < pr->addr + pr->size) |
| return pr; |
| } |
| return NULL; |
| } |
| |
| /* addr must be aligned. Only RAM accesses are supported */ |
| #define PHYS_MEM_READ_WRITE(size, uint_type) \ |
| static inline void phys_write_u ## size(RISCVCPUState *s, target_ulong addr,\ |
| uint_type val) \ |
| {\ |
| PhysMemoryRange *pr = get_phys_mem_range(s, addr);\ |
| if (!pr || !pr->is_ram)\ |
| return;\ |
| *(uint_type *)(s->phys_mem + pr->phys_mem_offset +\ |
| (uintptr_t)(addr - pr->addr)) = val;\ |
| }\ |
| \ |
| static inline uint_type phys_read_u ## size(RISCVCPUState *s, target_ulong addr) \ |
| {\ |
| PhysMemoryRange *pr = get_phys_mem_range(s, addr);\ |
| if (!pr || !pr->is_ram)\ |
| return 0;\ |
| return *(uint_type *)(s->phys_mem + pr->phys_mem_offset +\ |
| (uintptr_t)(addr - pr->addr)); \ |
| } |
| |
| PHYS_MEM_READ_WRITE(8, uint8_t) |
| PHYS_MEM_READ_WRITE(32, uint32_t) |
| PHYS_MEM_READ_WRITE(64, uint64_t) |
| |
| /* return 0 if OK, != 0 if exception */ |
| #define TARGET_READ_WRITE(size, uint_type, size_log2) \ |
| static inline int target_read_u ## size(RISCVCPUState *s, uint_type *pval, target_ulong addr) \ |
| {\ |
| uint32_t tlb_idx;\ |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);\ |
| if (likely(s->tlb_read[tlb_idx].vaddr == (addr & ~(PG_MASK & ~(size - 1))))) { \ |
| *pval = *(uint_type *)(s->tlb_read[tlb_idx].mem_addend + (uintptr_t)addr);\ |
| } else {\ |
| mem_uint_t val;\ |
| int ret;\ |
| ret = target_read_slow(s, &val, addr, size_log2);\ |
| if (ret)\ |
| return ret;\ |
| *pval = val;\ |
| }\ |
| return 0;\ |
| }\ |
| \ |
| static inline int target_write_u ## size(RISCVCPUState *s, target_ulong addr,\ |
| uint_type val) \ |
| {\ |
| uint32_t tlb_idx;\ |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1);\ |
| if (likely(s->tlb_write[tlb_idx].vaddr == (addr & ~(PG_MASK & ~(size - 1))))) { \ |
| *(uint_type *)(s->tlb_write[tlb_idx].mem_addend + (uintptr_t)addr) = val;\ |
| return 0;\ |
| } else {\ |
| return target_write_slow(s, addr, val, size_log2);\ |
| }\ |
| } |
| |
| TARGET_READ_WRITE(8, uint8_t, 0) |
| TARGET_READ_WRITE(16, uint16_t, 1) |
| TARGET_READ_WRITE(32, uint32_t, 2) |
| #if MLEN >= 64 |
| TARGET_READ_WRITE(64, uint64_t, 3) |
| #endif |
| #if MLEN >= 128 |
| TARGET_READ_WRITE(128, uint128_t, 4) |
| #endif |
| |
| #define PTE_V_MASK (1 << 0) |
| #define PTE_U_MASK (1 << 4) |
| #define PTE_A_MASK (1 << 6) |
| #define PTE_D_MASK (1 << 7) |
| |
| #define ACCESS_READ 0 |
| #define ACCESS_WRITE 1 |
| #define ACCESS_CODE 2 |
| |
| /* access = 0: read, 1 = write, 2 = code. Set the exception_pending |
| field if necessary. return 0 if OK, -1 if translation error */ |
| static int get_phys_addr(RISCVCPUState *s, |
| target_ulong *ppaddr, target_ulong vaddr, |
| int access) |
| { |
| int vm, levels, pte_bits, pte_idx, pte_mask, pte_size_log2, xwr, priv; |
| int need_write, vaddr_shift, i; |
| target_ulong pte_addr, pte, vaddr_mask, paddr; |
| |
| if ((s->mstatus & MSTATUS_MPRV) && access != ACCESS_CODE) { |
| /* use previous priviledge */ |
| priv = (s->mstatus >> MSTATUS_MPP_SHIFT) & 3; |
| } else { |
| priv = s->priv; |
| } |
| |
| if (priv == PRV_M) { |
| if (s->cur_xlen < MAX_XLEN) { |
| /* truncate virtual address */ |
| *ppaddr = vaddr & (((target_ulong)1 << s->cur_xlen) - 1); |
| } else { |
| *ppaddr = vaddr; |
| } |
| return 0; |
| } |
| |
| vm = (s->mstatus >> MSTATUS_VM_SHIFT) & 0x1f; |
| switch(vm) { |
| case 0: /* mbare */ |
| default: |
| /* no translation */ |
| *ppaddr = vaddr; |
| return 0; |
| case 8: /* sv32 */ |
| levels = 2; |
| pte_size_log2 = 2; |
| break; |
| #if MAX_XLEN >= 64 |
| case 9: /* sv39 */ |
| case 10: /* sv48 */ |
| levels = vm - 9 + 3; |
| pte_size_log2 = 3; |
| vaddr_shift = MAX_XLEN - (PG_SHIFT + levels * 9); |
| if ((((target_long)vaddr << vaddr_shift) >> vaddr_shift) != vaddr) |
| return -1; |
| break; |
| #endif |
| } |
| pte_bits = 12 - pte_size_log2; |
| pte_addr = s->sptbr << PG_SHIFT; |
| pte_mask = (1 << pte_bits) - 1; |
| for(i = 0; i < levels; i++) { |
| vaddr_shift = PG_SHIFT + pte_bits * (levels - 1 - i); |
| pte_idx = (vaddr >> vaddr_shift) & pte_mask; |
| pte_addr += pte_idx << pte_size_log2; |
| if (pte_size_log2 == 2) |
| pte = phys_read_u32(s, pte_addr); |
| else |
| pte = phys_read_u64(s, pte_addr); |
| //printf("pte=0x%08" PRIx64 "\n", pte); |
| if (!(pte & PTE_V_MASK)) |
| return -1; /* invalid PTE */ |
| paddr = (pte >> 10) << PG_SHIFT; |
| xwr = (pte >> 1) & 7; |
| if (xwr != 0) { |
| if (xwr == 2 || xwr == 6) |
| return -1; |
| /* priviledge check */ |
| if (priv == PRV_S) { |
| if ((pte & PTE_U_MASK) && (s->mstatus & MSTATUS_PUM)) |
| return -1; |
| } else { |
| if (!(pte & PTE_U_MASK)) |
| return -1; |
| } |
| /* protection check */ |
| /* MXR allows read access to execute-only pages */ |
| if (s->mstatus & MSTATUS_MXR) |
| xwr |= (xwr >> 2); |
| |
| if (((xwr >> access) & 1) == 0) |
| return -1; |
| need_write = !(pte & PTE_A_MASK) || |
| (!(pte & PTE_D_MASK) && access == ACCESS_WRITE); |
| pte |= PTE_A_MASK; |
| if (access == ACCESS_WRITE) |
| pte |= PTE_D_MASK; |
| if (need_write) { |
| if (pte_size_log2 == 2) |
| phys_write_u32(s, pte_addr, pte); |
| else |
| phys_write_u64(s, pte_addr, pte); |
| } |
| vaddr_mask = ((target_ulong)1 << vaddr_shift) - 1; |
| *ppaddr = (vaddr & vaddr_mask) | (paddr & ~vaddr_mask); |
| return 0; |
| } else { |
| pte_addr = paddr; |
| } |
| } |
| return -1; |
| } |
| |
| /* return 0 if OK, != 0 if exception */ |
| static no_inline int target_read_slow(RISCVCPUState *s, mem_uint_t *pval, |
| target_ulong addr, int size_log2) |
| { |
| int size, tlb_idx, err, al; |
| target_ulong paddr, offset; |
| uint8_t *ptr; |
| PhysMemoryRange *pr; |
| mem_uint_t ret; |
| |
| /* first handle unaligned accesses */ |
| size = 1 << size_log2; |
| al = addr & (size - 1); |
| if (al != 0) { |
| switch(size_log2) { |
| case 1: |
| { |
| uint8_t v0, v1; |
| err = target_read_u8(s, &v0, addr); |
| if (err) |
| return err; |
| err = target_read_u8(s, &v1, addr + 1); |
| if (err) |
| return err; |
| ret = v0 | (v1 << 8); |
| } |
| break; |
| case 2: |
| { |
| uint32_t v0, v1; |
| addr -= al; |
| err = target_read_u32(s, &v0, addr); |
| if (err) |
| return err; |
| err = target_read_u32(s, &v1, addr + 4); |
| if (err) |
| return err; |
| ret = (v0 >> (al * 8)) | (v1 << (32 - al * 8)); |
| } |
| break; |
| #if MLEN >= 64 |
| case 3: |
| { |
| uint64_t v0, v1; |
| addr -= al; |
| err = target_read_u64(s, &v0, addr); |
| if (err) |
| return err; |
| err = target_read_u64(s, &v1, addr + 8); |
| if (err) |
| return err; |
| ret = (v0 >> (al * 8)) | (v1 << (64 - al * 8)); |
| } |
| break; |
| #endif |
| #if MLEN >= 128 |
| case 4: |
| { |
| uint128_t v0, v1; |
| addr -= al; |
| err = target_read_u128(s, &v0, addr); |
| if (err) |
| return err; |
| err = target_read_u128(s, &v1, addr + 8); |
| if (err) |
| return err; |
| ret = (v0 >> (al * 8)) | (v1 << (128 - al * 8)); |
| } |
| break; |
| #endif |
| default: |
| abort(); |
| } |
| } else { |
| if (get_phys_addr(s, &paddr, addr, ACCESS_READ)) { |
| raise_exception2(s, CAUSE_FAULT_LOAD, addr); |
| return -1; |
| } |
| pr = get_phys_mem_range(s, paddr); |
| if (!pr) { |
| #ifdef DUMP_INVALID_MEM_ACCESS |
| printf("target_read_slow: invalid physical address 0x"); |
| print_target_ulong(paddr); |
| printf("\n"); |
| #endif |
| return 0; |
| } else if (pr->is_ram) { |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| ptr = s->phys_mem + pr->phys_mem_offset + |
| (uintptr_t)(paddr - pr->addr); |
| s->tlb_read[tlb_idx].vaddr = addr & ~PG_MASK; |
| s->tlb_read[tlb_idx].mem_addend = (uintptr_t)ptr - addr; |
| switch(size_log2) { |
| case 0: |
| ret = *(uint8_t *)ptr; |
| break; |
| case 1: |
| ret = *(uint16_t *)ptr; |
| break; |
| case 2: |
| ret = *(uint32_t *)ptr; |
| break; |
| #if MLEN >= 64 |
| case 3: |
| ret = *(uint64_t *)ptr; |
| break; |
| #endif |
| #if MLEN >= 128 |
| case 4: |
| ret = *(uint128_t *)ptr; |
| break; |
| #endif |
| default: |
| abort(); |
| } |
| } else { |
| offset = paddr - pr->addr; |
| if (((pr->devio_flags >> size_log2) & 1) != 0) { |
| ret = pr->read_func(pr->opaque, offset, size_log2); |
| } |
| #if MLEN >= 64 |
| else if ((pr->devio_flags & DEVIO_SIZE32) && size_log2 == 3) { |
| /* emulate 64 bit access */ |
| ret = pr->read_func(pr->opaque, offset, 2); |
| ret |= (uint64_t)pr->read_func(pr->opaque, offset + 4, 2) << 32; |
| |
| } |
| #endif |
| else { |
| #ifdef DUMP_INVALID_MEM_ACCESS |
| printf("unsupported device read access: addr=0x"); |
| print_target_ulong(paddr); |
| printf(" width=%d bits\n", 1 << (3 + size_log2)); |
| #endif |
| ret = 0; |
| } |
| } |
| } |
| *pval = ret; |
| return 0; |
| } |
| |
| /* return 0 if OK, != 0 if exception */ |
| static no_inline int target_write_slow(RISCVCPUState *s, target_ulong addr, |
| mem_uint_t val, int size_log2) |
| { |
| int size, i, tlb_idx, err; |
| target_ulong paddr, offset; |
| uint8_t *ptr; |
| PhysMemoryRange *pr; |
| |
| /* first handle unaligned accesses */ |
| size = 1 << size_log2; |
| if ((addr & (size - 1)) != 0) { |
| /* XXX: should avoid modifying the memory in case of exception */ |
| for(i = 0; i < size; i++) { |
| err = target_write_u8(s, addr + i, (val >> (8 * i)) & 0xff); |
| if (err) |
| return err; |
| } |
| } else { |
| if (get_phys_addr(s, &paddr, addr, ACCESS_WRITE)) { |
| raise_exception2(s, CAUSE_FAULT_STORE, addr); |
| return -1; |
| } |
| pr = get_phys_mem_range(s, paddr); |
| if (!pr) { |
| #ifdef DUMP_INVALID_MEM_ACCESS |
| printf("target_write_slow: invalid physical address 0x"); |
| print_target_ulong(paddr); |
| printf("\n"); |
| #endif |
| } else if (pr->is_ram) { |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| ptr = s->phys_mem + pr->phys_mem_offset + |
| (uintptr_t)(paddr - pr->addr); |
| s->tlb_write[tlb_idx].vaddr = addr & ~PG_MASK; |
| s->tlb_write[tlb_idx].mem_addend = (uintptr_t)ptr - addr; |
| switch(size_log2) { |
| case 0: |
| *(uint8_t *)ptr = val; |
| break; |
| case 1: |
| *(uint16_t *)ptr = val; |
| break; |
| case 2: |
| *(uint32_t *)ptr = val; |
| break; |
| #if MLEN >= 64 |
| case 3: |
| *(uint64_t *)ptr = val; |
| break; |
| #endif |
| #if MLEN >= 128 |
| case 4: |
| *(uint128_t *)ptr = val; |
| break; |
| #endif |
| default: |
| abort(); |
| } |
| } else { |
| offset = paddr - pr->addr; |
| if (((pr->devio_flags >> size_log2) & 1) != 0) { |
| pr->write_func(pr->opaque, offset, val, size_log2); |
| } |
| #if MLEN >= 64 |
| else if ((pr->devio_flags & DEVIO_SIZE32) && size_log2 == 3) { |
| /* emulate 64 bit access */ |
| pr->write_func(pr->opaque, offset, |
| val & 0xffffffff, 2); |
| pr->write_func(pr->opaque, offset + 4, |
| (val >> 32) & 0xffffffff, 2); |
| } |
| #endif |
| else { |
| #ifdef DUMP_INVALID_MEM_ACCESS |
| printf("unsupported device write access: addr=0x"); |
| print_target_ulong(paddr); |
| printf(" width=%d bits\n", 1 << (3 + size_log2)); |
| #endif |
| } |
| } |
| } |
| return 0; |
| } |
| |
| struct __attribute__((packed)) unaligned_u32 { |
| uint32_t u32; |
| }; |
| |
| /* unaligned access at an address known to be a multiple of 2 */ |
| static uint32_t get_insn32(uint8_t *ptr) |
| { |
| #if defined(EMSCRIPTEN) |
| return ((uint16_t *)ptr)[0] | (((uint16_t *)ptr)[1] << 16); |
| #else |
| return ((struct unaligned_u32 *)ptr)->u32; |
| #endif |
| } |
| |
| /* return 0 if OK, != 0 if exception */ |
| static no_inline int target_read_insn_slow(RISCVCPUState *s, |
| uint32_t *pinsn, |
| target_ulong addr, |
| BOOL short_insn) |
| { |
| int tlb_idx, err; |
| target_ulong paddr; |
| uint8_t *ptr; |
| PhysMemoryRange *pr; |
| uint32_t insn, val; |
| |
| if ((addr & PG_MASK) == (PG_MASK - 1) && !short_insn) { |
| /* instruction potentially between two pages */ |
| err = target_read_insn_slow(s, &insn, addr, TRUE); |
| if (err) |
| return err; |
| if ((insn & 3) == 3) { |
| err = target_read_insn_slow(s, &val, addr + 2, TRUE); |
| if (err) |
| return err; |
| insn |= val << 16; |
| } |
| } else { |
| if (get_phys_addr(s, &paddr, addr, ACCESS_CODE)) { |
| raise_exception2(s, CAUSE_FAULT_FETCH, addr); |
| return -1; |
| } |
| pr = get_phys_mem_range(s, paddr); |
| if (!pr || !pr->is_ram) { |
| /* we only access to execute code from RAM */ |
| raise_exception2(s, CAUSE_FAULT_FETCH, addr); |
| return -1; |
| } |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| ptr = s->phys_mem + pr->phys_mem_offset + |
| (uintptr_t)(paddr - pr->addr); |
| s->tlb_code[tlb_idx].vaddr = addr & ~PG_MASK; |
| s->tlb_code[tlb_idx].mem_addend = (uintptr_t)ptr - addr; |
| if (short_insn) { |
| insn = *(uint16_t *)ptr; |
| } else { |
| insn = get_insn32(ptr); |
| } |
| } |
| *pinsn = insn; |
| return 0; |
| } |
| |
| /* it is assumed that addr is even */ |
| /* return 0 if OK, != 0 if exception */ |
| static inline int target_read_insn(RISCVCPUState *s, uint32_t *pinsn, |
| target_ulong addr) |
| { |
| uint32_t tlb_idx; |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| if (likely(s->tlb_code[tlb_idx].vaddr == (addr & ~PG_MASK) && |
| (addr & PG_MASK) != (PG_MASK - 1))) { |
| *pinsn = get_insn32((uint8_t *)(s->tlb_code[tlb_idx].mem_addend + |
| (uintptr_t)addr)); |
| return 0; |
| } else { |
| return target_read_insn_slow(s, pinsn, addr, FALSE); |
| } |
| } |
| |
| static void tlb_init(RISCVCPUState *s) |
| { |
| int i; |
| |
| for(i = 0; i < TLB_SIZE; i++) { |
| s->tlb_read[i].vaddr = -1; |
| s->tlb_write[i].vaddr = -1; |
| s->tlb_code[i].vaddr = -1; |
| } |
| } |
| |
| static void tlb_flush_all(RISCVCPUState *s) |
| { |
| tlb_init(s); |
| } |
| |
| static void tlb_flush_vaddr(RISCVCPUState *s, target_ulong vaddr) |
| { |
| tlb_flush_all(s); |
| } |
| |
| #define SSTATUS_MASK (MSTATUS_UIE | MSTATUS_SIE | \ |
| MSTATUS_UPIE | MSTATUS_SPIE | \ |
| MSTATUS_SPP | \ |
| MSTATUS_FS | MSTATUS_XS | \ |
| MSTATUS_PUM | \ |
| MSTATUS_SB | MSTATUS_SPB) |
| |
| #define MSTATUS_MASK (MSTATUS_UIE | MSTATUS_SIE | MSTATUS_MIE | \ |
| MSTATUS_UPIE | MSTATUS_SPIE | MSTATUS_MPIE | \ |
| MSTATUS_SPP | MSTATUS_MPP | \ |
| MSTATUS_FS | \ |
| MSTATUS_MPRV | MSTATUS_PUM | MSTATUS_MXR | \ |
| MSTATUS_VM | \ |
| MSTATUS_SB | MSTATUS_MB | MSTATUS_SPB | MSTATUS_MPB) |
| |
| static BOOL check_vm(RISCVCPUState *s, int vm) |
| { |
| int vm_max; |
| if (vm == 0) |
| return TRUE; |
| if (s->cur_xlen == 32) |
| vm_max = 8; |
| else |
| vm_max = 10; |
| return (vm >= 8 && vm <= vm_max); |
| } |
| |
| /* return the complete mstatus with the SD bit */ |
| static target_ulong get_mstatus(RISCVCPUState *s, target_ulong mask) |
| { |
| target_ulong val; |
| BOOL sd; |
| val = s->mstatus | (s->fs << MSTATUS_FS_SHIFT); |
| val &= mask; |
| sd = ((val & MSTATUS_FS) == MSTATUS_FS) | |
| ((val & MSTATUS_XS) == MSTATUS_XS); |
| if (sd) |
| val |= (target_ulong)1 << (s->cur_xlen - 1); |
| return val; |
| } |
| |
| static void set_mstatus(RISCVCPUState *s, target_ulong val) |
| { |
| int vm; |
| target_ulong mod, mask; |
| |
| /* flush the TLBs if change of MMU config */ |
| mod = s->mstatus ^ val; |
| if ((mod & (MSTATUS_MPRV | MSTATUS_PUM | MSTATUS_MXR | MSTATUS_VM)) != 0 || |
| ((s->mstatus & MSTATUS_MPRV) && (mod & MSTATUS_MPP) != 0)) { |
| tlb_flush_all(s); |
| } |
| s->fs = (val >> MSTATUS_FS_SHIFT) & 3; |
| |
| mask = MSTATUS_MASK & ~MSTATUS_FS; |
| vm = (val >> MSTATUS_VM_SHIFT) & 0x1f; |
| if (!check_vm(s, vm)) |
| mask &= ~MSTATUS_VM; |
| s->mstatus = (s->mstatus & ~mask) | (val & mask); |
| } |
| |
| static int get_base_from_xlen(int xlen) |
| { |
| if (xlen == 32) |
| return 1; |
| else if (xlen == 64) |
| return 2; |
| else |
| return 3; |
| } |
| |
| #ifdef CONFIG_EXT_DYN_XLEN |
| static int get_valid_base(int base) |
| { |
| int base_max; |
| if (MAX_XLEN == 64) |
| base_max = 2; |
| else if (MAX_XLEN == 128) |
| base_max = 3; |
| else |
| base_max = 1; |
| if (base < 1 || base > base_max) { |
| base = base_max; |
| } |
| return base; |
| } |
| #endif |
| |
| /* return -1 if invalid CSR. 0 if OK. 'will_write' indicate that the |
| csr will be written after (used for CSR access check) */ |
| static int csr_read(RISCVCPUState *s, target_ulong *pval, uint32_t csr, |
| BOOL will_write) |
| { |
| target_ulong val; |
| |
| if (((csr & 0xc00) == 0xc00) && will_write) |
| return -1; /* read-only CSR */ |
| if (s->priv < ((csr >> 8) & 3)) |
| return -1; /* not enough priviledge */ |
| |
| switch(csr) { |
| #if FLEN > 0 |
| case 0x001: /* fflags */ |
| if (s->fs == 0) |
| return -1; |
| val = s->fflags; |
| break; |
| case 0x002: /* frm */ |
| if (s->fs == 0) |
| return -1; |
| val = s->frm; |
| break; |
| case 0x003: |
| if (s->fs == 0) |
| return -1; |
| val = s->fflags | (s->frm << 5); |
| break; |
| #endif |
| case 0xc00: /* ucycle */ |
| case 0xc02: /* uinstret */ |
| if (s->priv <= PRV_H && |
| ((s->mcounteren[s->priv] >> (csr & 0x1f)) & 1) == 0) |
| goto invalid_csr; |
| val = (int64_t)s->insn_counter; |
| break; |
| case 0xc80: /* mcycleh */ |
| case 0xc82: /* minstreth */ |
| if (s->cur_xlen != 32) |
| goto invalid_csr; |
| if (s->priv <= PRV_H && |
| ((s->mcounteren[s->priv] >> (csr & 0x1f)) & 1) == 0) |
| goto invalid_csr; |
| val = s->insn_counter >> 32; |
| break; |
| |
| case 0x100: |
| val = get_mstatus(s, SSTATUS_MASK); |
| break; |
| case 0x104: /* sie */ |
| val = s->mie & s->mideleg; |
| break; |
| case 0x105: |
| val = s->stvec; |
| break; |
| case 0x140: |
| val = s->sscratch; |
| break; |
| case 0x141: |
| val = s->sepc; |
| break; |
| case 0x142: |
| val = s->scause; |
| break; |
| case 0x143: |
| val = s->sbadaddr; |
| break; |
| case 0x144: /* sip */ |
| val = s->mip & s->mideleg; |
| break; |
| case 0x180: |
| val = s->sptbr; |
| break; |
| case 0x300: |
| val = get_mstatus(s, (target_ulong)-1); |
| break; |
| case 0x301: |
| val = s->misa; |
| val |= (target_ulong)get_base_from_xlen(s->cur_xlen) << |
| (s->cur_xlen - 2); |
| break; |
| case 0x302: |
| val = s->medeleg; |
| break; |
| case 0x303: |
| val = s->mideleg; |
| break; |
| case 0x304: |
| val = s->mie; |
| break; |
| case 0x305: |
| val = s->mtvec; |
| break; |
| case 0x320: |
| case 0x321: |
| val = s->mcounteren[csr & 3]; |
| break; |
| case 0x340: |
| val = s->mscratch; |
| break; |
| case 0x341: |
| val = s->mepc; |
| break; |
| case 0x342: |
| val = s->mcause; |
| break; |
| case 0x343: |
| val = s->mbadaddr; |
| break; |
| case 0x344: |
| val = s->mip; |
| break; |
| case 0xb00: /* mcycle */ |
| case 0xb02: /* minstret */ |
| val = (int64_t)s->insn_counter; |
| break; |
| case 0xb80: /* mcycleh */ |
| case 0xb82: /* minstreth */ |
| if (s->cur_xlen != 32) |
| goto invalid_csr; |
| val = s->insn_counter >> 32; |
| break; |
| case 0xf14: |
| val = s->mhartid; |
| break; |
| default: |
| invalid_csr: |
| #ifdef DUMP_INVALID_CSR |
| printf("csr_read: invalid CSR=0x%x\n", csr); |
| #endif |
| *pval = 0; |
| return -1; |
| } |
| *pval = val; |
| return 0; |
| } |
| |
| #if FLEN > 0 |
| static void set_frm(RISCVCPUState *s, unsigned int val) |
| { |
| if (val >= 5) |
| val = 0; |
| s->frm = val; |
| } |
| |
| /* return -1 if invalid roundind mode */ |
| static int get_insn_rm(RISCVCPUState *s, unsigned int rm) |
| { |
| if (rm == 7) |
| return s->frm; |
| if (rm >= 5) |
| return -1; |
| else |
| return rm; |
| } |
| #endif |
| |
| /* return -1 if invalid CSR, 0 if OK, 1 if the interpreter loop must be |
| exited (e.g. XLEN was modified) */ |
| static int csr_write(RISCVCPUState *s, uint32_t csr, target_ulong val) |
| { |
| target_ulong mask; |
| |
| #if defined(DUMP_CSR) |
| printf("csr_write: csr=0x%03x val=0x", csr); |
| print_target_ulong(val); |
| printf("\n"); |
| #endif |
| switch(csr) { |
| #if FLEN > 0 |
| case 0x001: /* fflags */ |
| s->fflags = val & 0x1f; |
| s->fs = 3; |
| break; |
| case 0x002: /* frm */ |
| set_frm(s, val & 7); |
| s->fs = 3; |
| break; |
| case 0x003: /* fcsr */ |
| set_frm(s, (val >> 5) & 7); |
| s->fflags = val & 0x1f; |
| s->fs = 3; |
| break; |
| #endif |
| case 0x100: /* sstatus */ |
| set_mstatus(s, (s->mstatus & ~SSTATUS_MASK) | (val & SSTATUS_MASK)); |
| break; |
| case 0x104: /* sie */ |
| mask = s->mideleg; |
| s->mie = (s->mie & ~mask) | (val & mask); |
| break; |
| case 0x105: |
| s->stvec = val & ~3; |
| break; |
| case 0x140: |
| s->sscratch = val; |
| break; |
| case 0x141: |
| s->sepc = val & ~1; |
| break; |
| case 0x142: |
| s->scause = val; |
| break; |
| case 0x143: |
| s->sbadaddr = val; |
| break; |
| case 0x144: /* sip */ |
| mask = s->mideleg; |
| s->mip = (s->mip & ~mask) | (val & mask); |
| break; |
| case 0x180: |
| /* no ASID */ |
| if (s->cur_xlen == 32) { |
| s->sptbr = val & (((target_ulong)1 << 22) - 1); |
| } |
| #if MAX_XLEN >= 64 |
| else { |
| s->sptbr = val & (((target_ulong)1 << 38) - 1); |
| } |
| #endif |
| break; |
| |
| case 0x300: |
| set_mstatus(s, val); |
| break; |
| case 0x301: /* misa */ |
| #ifdef CONFIG_EXT_DYN_XLEN |
| { |
| int base, new_xlen; |
| base = get_valid_base((val >> (s->cur_xlen - 2)) & 3); |
| new_xlen = 1 << (base + 4); |
| if (s->cur_xlen != new_xlen) { |
| s->cur_xlen = new_xlen; |
| return 1; |
| } |
| } |
| #endif |
| break; |
| case 0x302: |
| mask = (1 << (CAUSE_MACHINE_ECALL + 1)) - 1; |
| s->medeleg = (s->medeleg & ~mask) | (val & mask); |
| break; |
| case 0x303: |
| mask = MIP_SSIP | MIP_STIP | MIP_SEIP; |
| s->mideleg = (s->mideleg & ~mask) | (val & mask); |
| break; |
| case 0x304: |
| mask = MIP_MSIP | MIP_MTIP | MIP_SSIP | MIP_STIP | MIP_SEIP; |
| s->mie = (s->mie & ~mask) | (val & mask); |
| break; |
| case 0x305: |
| s->mtvec = val & ~3; |
| break; |
| case 0x340: |
| s->mscratch = val; |
| break; |
| case 0x341: |
| s->mepc = val & ~1; |
| break; |
| case 0x342: |
| s->mcause = val; |
| break; |
| case 0x343: |
| s->mbadaddr = val; |
| break; |
| case 0x344: |
| mask = MIP_SSIP | MIP_STIP; |
| s->mip = (s->mip & ~mask) | (val & mask); |
| break; |
| case 0x320: |
| case 0x321: |
| s->mcounteren[csr & 3] = val & 7; /* Note: RDTIME is handle in software */ |
| break; |
| default: |
| #ifdef DUMP_INVALID_CSR |
| printf("csr_write: invalid CSR=0x%x\n", csr); |
| #endif |
| return -1; |
| } |
| return 0; |
| } |
| |
| static void set_priv(RISCVCPUState *s, int priv) |
| { |
| if (s->priv != priv) { |
| tlb_flush_all(s); |
| s->priv = priv; |
| } |
| } |
| |
| static void raise_exception2(RISCVCPUState *s, uint32_t cause, |
| target_ulong badaddr) |
| { |
| BOOL has_badaddr, deleg; |
| target_ulong causel; |
| |
| has_badaddr = (cause == CAUSE_MISALIGNED_FETCH || |
| cause == CAUSE_FAULT_FETCH || |
| cause == CAUSE_MISALIGNED_LOAD || |
| cause == CAUSE_FAULT_LOAD || |
| cause == CAUSE_MISALIGNED_STORE || |
| cause == CAUSE_FAULT_STORE); |
| #if defined(DUMP_EXCEPTIONS) || defined(DUMP_MMU_EXCEPTIONS) || defined(DUMP_INTERRUPTS) |
| { |
| int flag; |
| flag = 0; |
| #ifdef DUMP_MMU_EXCEPTIONS |
| flag |= has_badaddr; |
| #endif |
| #ifdef DUMP_INTERRUPTS |
| flag |= (cause & CAUSE_INTERRUPT) != 0; |
| #endif |
| #ifdef DUMP_EXCEPTIONS |
| flag = 1; |
| flag = (cause & CAUSE_INTERRUPT) == 0; |
| #endif |
| if (flag) { |
| log_printf("raise_exception: cause=0x%08x", cause); |
| if (has_badaddr) { |
| log_printf(" badaddr=0x"); |
| #ifdef CONFIG_LOGFILE |
| fprint_target_ulong(log_file, badaddr); |
| #else |
| print_target_ulong(badaddr); |
| #endif |
| } |
| log_printf("\n"); |
| // dump_regs(s); |
| } |
| } |
| #endif |
| |
| if (s->priv <= PRV_S) { |
| /* delegate the exception to the supervisor priviledge */ |
| if (cause & CAUSE_INTERRUPT) |
| deleg = (s->mideleg >> (cause & (MAX_XLEN - 1))) & 1; |
| else |
| deleg = (s->medeleg >> cause) & 1; |
| } else { |
| deleg = 0; |
| } |
| |
| causel = cause & 0x7fffffff; |
| if (cause & CAUSE_INTERRUPT) |
| causel |= (target_ulong)1 << (s->cur_xlen - 1); |
| |
| if (deleg) { |
| s->scause = causel; |
| s->sepc = s->pc; |
| if (has_badaddr) |
| s->sbadaddr = badaddr; |
| s->mstatus = (s->mstatus & ~MSTATUS_SPIE) | |
| (((s->mstatus >> s->priv) & 1) << MSTATUS_SPIE_SHIFT); |
| s->mstatus = (s->mstatus & ~MSTATUS_SPP) | |
| (s->priv << MSTATUS_SPP_SHIFT); |
| s->mstatus &= ~MSTATUS_SIE; |
| #ifdef CONFIG_EXT_DYN_XLEN |
| s->mstatus = (s->mstatus & ~MSTATUS_SPB) | |
| ((uint64_t)get_base_from_xlen(s->cur_xlen) << MSTATUS_SPB_SHIFT); |
| s->cur_xlen = 1 << |
| (4 + get_valid_base((s->mstatus >> MSTATUS_SB_SHIFT) & 3)); |
| #endif |
| set_priv(s, PRV_S); |
| s->pc = s->stvec; |
| } else { |
| s->mcause = causel; |
| s->mepc = s->pc; |
| if (has_badaddr) |
| s->mbadaddr = badaddr; |
| s->mstatus = (s->mstatus & ~MSTATUS_MPIE) | |
| (((s->mstatus >> s->priv) & 1) << MSTATUS_MPIE_SHIFT); |
| s->mstatus = (s->mstatus & ~MSTATUS_MPP) | |
| (s->priv << MSTATUS_MPP_SHIFT); |
| s->mstatus &= ~MSTATUS_MIE; |
| #ifdef CONFIG_EXT_DYN_XLEN |
| s->mstatus = (s->mstatus & ~MSTATUS_MPB) | |
| ((uint64_t)get_base_from_xlen(s->cur_xlen) << MSTATUS_MPB_SHIFT); |
| s->cur_xlen = 1 << |
| (4 + get_valid_base((s->mstatus >> MSTATUS_MB_SHIFT) & 3)); |
| #endif |
| set_priv(s, PRV_M); |
| s->pc = s->mtvec; |
| } |
| } |
| |
| static void raise_exception(RISCVCPUState *s, uint32_t cause) |
| { |
| raise_exception2(s, cause, 0); |
| } |
| |
| static void handle_sret(RISCVCPUState *s) |
| { |
| int spp, spie; |
| spp = (s->mstatus >> MSTATUS_SPP_SHIFT) & 1; |
| /* set the IE state to previous IE state */ |
| spie = (s->mstatus >> MSTATUS_SPIE_SHIFT) & 1; |
| s->mstatus = (s->mstatus & ~(1 << spp)) | |
| (spie << spp); |
| /* set SPIE to 1 */ |
| s->mstatus |= MSTATUS_SPIE; |
| /* set SPP to U */ |
| s->mstatus &= ~MSTATUS_SPP; |
| #ifdef CONFIG_EXT_DYN_XLEN |
| { |
| int spb; |
| spb = get_valid_base((s->mstatus >> MSTATUS_SPB_SHIFT) & 3); |
| s->cur_xlen = 1 << (4 + spb); |
| s->mstatus &= ~MSTATUS_SPB; |
| } |
| #endif |
| set_priv(s, spp); |
| s->pc = s->sepc; |
| } |
| |
| static void handle_mret(RISCVCPUState *s) |
| { |
| int mpp, mpie; |
| mpp = (s->mstatus >> MSTATUS_MPP_SHIFT) & 3; |
| /* set the IE state to previous IE state */ |
| mpie = (s->mstatus >> MSTATUS_MPIE_SHIFT) & 1; |
| s->mstatus = (s->mstatus & ~(1 << mpp)) | |
| (mpie << mpp); |
| /* set MPIE to 1 */ |
| s->mstatus |= MSTATUS_MPIE; |
| /* set MPP to U */ |
| s->mstatus &= ~MSTATUS_MPP; |
| #ifdef CONFIG_EXT_DYN_XLEN |
| { |
| int mpb; |
| mpb = get_valid_base((s->mstatus >> MSTATUS_MPB_SHIFT) & 3); |
| s->cur_xlen = 1 << (4 + mpb); |
| s->mstatus &= ~MSTATUS_MPB; |
| } |
| #endif |
| set_priv(s, mpp); |
| s->pc = s->mepc; |
| } |
| |
| static uint32_t ctz32(uint32_t a) |
| { |
| int i; |
| if (a == 0) |
| return 32; |
| for(i = 0; i < 32; i++) { |
| if ((a >> i) & 1) |
| return i; |
| } |
| return 32; |
| } |
| |
| static inline uint32_t get_pending_irq_mask(RISCVCPUState *s) |
| { |
| uint32_t pending_ints, enabled_ints; |
| |
| pending_ints = s->mip & s->mie; |
| if (pending_ints == 0) |
| return 0; |
| |
| enabled_ints = 0; |
| switch(s->priv) { |
| case PRV_M: |
| if (s->mstatus & MSTATUS_MIE) |
| enabled_ints = ~s->mideleg; |
| break; |
| case PRV_S: |
| enabled_ints = ~s->mideleg; |
| if (s->mstatus & MSTATUS_SIE) |
| enabled_ints |= s->mideleg; |
| break; |
| default: |
| case PRV_U: |
| enabled_ints = -1; |
| break; |
| } |
| return pending_ints & enabled_ints; |
| } |
| |
| static void raise_interrupt(RISCVCPUState *s) |
| { |
| uint32_t mask; |
| int irq_num; |
| |
| mask = get_pending_irq_mask(s); |
| if (mask == 0) |
| return; |
| irq_num = ctz32(mask); |
| raise_exception(s, irq_num | CAUSE_INTERRUPT); |
| } |
| |
| static inline int32_t sext(int32_t val, int n) |
| { |
| return (val << (32 - n)) >> (32 - n); |
| } |
| |
| static inline uint32_t get_field1(uint32_t val, int src_pos, |
| int dst_pos, int dst_pos_max) |
| { |
| int mask; |
| assert(dst_pos_max >= dst_pos); |
| mask = ((1 << (dst_pos_max - dst_pos + 1)) - 1) << dst_pos; |
| if (dst_pos >= src_pos) |
| return (val << (dst_pos - src_pos)) & mask; |
| else |
| return (val >> (src_pos - dst_pos)) & mask; |
| } |
| |
| #define XLEN 32 |
| #include "riscvemu_template.h" |
| |
| #if MAX_XLEN >= 64 |
| #define XLEN 64 |
| #include "riscvemu_template.h" |
| #endif |
| |
| #if MAX_XLEN >= 128 |
| #define XLEN 128 |
| #include "riscvemu_template.h" |
| #endif |
| |
| static void no_inline riscv_cpu_interp(RISCVCPUState *s, |
| int n_cycles) |
| { |
| uint64_t timeout; |
| |
| timeout = s->insn_counter + n_cycles; |
| while (!s->power_down_flag && |
| (int)(timeout - s->insn_counter) > 0) { |
| n_cycles = timeout - s->insn_counter; |
| switch(s->cur_xlen) { |
| case 32: |
| riscv_cpu_interp32(s, n_cycles); |
| break; |
| #if MAX_XLEN >= 64 |
| case 64: |
| riscv_cpu_interp64(s, n_cycles); |
| break; |
| #endif |
| #if MAX_XLEN >= 128 |
| case 128: |
| riscv_cpu_interp128(s, n_cycles); |
| break; |
| #endif |
| default: |
| abort(); |
| } |
| } |
| } |
| |
| RISCVCPUState *riscv_cpu_init(RISCVMachine *machine_state, |
| unsigned int ram_size) |
| { |
| RISCVCPUState *s; |
| uint32_t low_ram_size; |
| |
| if (ram_size == 0 || |
| (ram_size & PG_MASK) != 0) |
| return NULL; |
| s = mallocz(sizeof(*s)); |
| s->machine_state = machine_state; |
| |
| s->phys_mem_size = 0; |
| cpu_register_ram(s, RAM_BASE_ADDR, ram_size, s->phys_mem_size); |
| s->phys_mem_size += ram_size; |
| low_ram_size = 0x100000; /* 1MB should be enough */ |
| cpu_register_ram(s, 0x00000000, low_ram_size, s->phys_mem_size); |
| s->phys_mem_size += low_ram_size; |
| |
| s->phys_mem = mallocz(s->phys_mem_size); |
| s->pc = 0x1000; |
| s->priv = PRV_M; |
| s->cur_xlen = MAX_XLEN; |
| s->misa |= MCPUID_SUPER | MCPUID_USER | MCPUID_I | MCPUID_M | MCPUID_A; |
| #if FLEN >= 32 |
| s->misa |= MCPUID_F; |
| #endif |
| #if FLEN >= 64 |
| s->misa |= MCPUID_D; |
| #endif |
| #if FLEN >= 128 |
| s->misa |= MCPUID_Q; |
| #endif |
| #ifdef CONFIG_EXT_C |
| s->misa |= MCPUID_C; |
| #endif |
| tlb_init(s); |
| return s; |
| } |
| |
| void riscv_cpu_end(RISCVCPUState *s) |
| { |
| free(s->phys_mem); |
| free(s); |
| } |
| |
| /* RISCV machine */ |
| |
| typedef struct { |
| void *opaque; |
| void (*write_data)(void *opaque, const uint8_t *buf, int len); |
| int (*read_data)(void *opaque, uint8_t *buf, int len); |
| } CharacterDevice; |
| |
| struct RISCVMachine { |
| RISCVCPUState *cpu_state; |
| BlockDevice *drives[3]; |
| IDEIFState *ide_state; |
| /* RTC */ |
| BOOL rtc_real_time; |
| uint64_t rtc_start_time; |
| /* PLIC */ |
| uint32_t plic_pending_irq, plic_served_irq; |
| /* HTIF */ |
| CharacterDevice *htif_dev; |
| uint64_t htif_tohost, htif_fromhost; |
| }; |
| |
| #define RTC_FREQ 10000000 |
| #define RTC_FREQ_DIV 16 /* arbitrary, relative to CPU freq to have a |
| 10 MHz frequency */ |
| |
| static uint64_t rtc_get_real_time(RISCVMachine *s) |
| { |
| struct timespec ts; |
| clock_gettime(CLOCK_MONOTONIC, &ts); |
| return (uint64_t)ts.tv_sec * RTC_FREQ + |
| (ts.tv_nsec / (1000000000 / RTC_FREQ)); |
| } |
| |
| static uint64_t rtc_get_time(RISCVCPUState *s) |
| { |
| RISCVMachine *m = s->machine_state; |
| uint64_t val; |
| if (m->rtc_real_time) { |
| val = rtc_get_real_time(m) - m->rtc_start_time; |
| } else { |
| val = s->insn_counter / RTC_FREQ_DIV; |
| } |
| // printf("rtc_time=%" PRId64 "\n", val); |
| return val; |
| } |
| |
| static target_ulong htif_read(void *opaque, target_ulong offset, |
| int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| uint32_t val; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0: |
| val = s->htif_tohost; |
| break; |
| case 4: |
| val = s->htif_tohost >> 32; |
| break; |
| case 8: |
| val = s->htif_fromhost; |
| break; |
| case 12: |
| val = s->htif_fromhost >> 32; |
| break; |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void htif_handle_cmd(RISCVMachine *s) |
| { |
| uint32_t device, cmd; |
| |
| device = s->htif_tohost >> 56; |
| cmd = (s->htif_tohost >> 48) & 0xff; |
| if (s->htif_tohost == 1) { |
| /* shuthost */ |
| printf("\nPower off.\n"); |
| exit(0); |
| } else if (device == 1 && cmd == 1) { |
| uint8_t buf[1]; |
| buf[0] = s->htif_tohost & 0xff; |
| s->htif_dev->write_data(s->htif_dev->opaque, buf, 1); |
| s->htif_tohost = 0; |
| s->htif_fromhost = ((uint64_t)device << 56) | ((uint64_t)cmd << 48); |
| } else if (device == 1 && cmd == 0) { |
| /* request keyboard interrupt */ |
| s->htif_tohost = 0; |
| } else { |
| printf("HTIF: unsupported tohost=0x%016" PRIx64 "\n", s->htif_tohost); |
| } |
| } |
| |
| static void htif_write(void *opaque, target_ulong offset, target_ulong val, |
| int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0: |
| s->htif_tohost = (s->htif_tohost & ~0xffffffff) | val; |
| break; |
| case 4: |
| s->htif_tohost = (s->htif_tohost & 0xffffffff) | ((uint64_t)val << 32); |
| htif_handle_cmd(s); |
| break; |
| case 8: |
| s->htif_fromhost = (s->htif_fromhost & ~0xffffffff) | val; |
| break; |
| case 12: |
| s->htif_fromhost = (s->htif_fromhost & 0xffffffff) | |
| (uint64_t)val << 32; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void htif_poll(RISCVMachine *s) |
| { |
| uint8_t buf[1]; |
| int ret; |
| |
| if (s->htif_fromhost == 0) { |
| ret = s->htif_dev->read_data(s->htif_dev->opaque, buf, 1); |
| if (ret == 1) { |
| s->htif_fromhost = ((uint64_t)1 << 56) | ((uint64_t)0 << 48) | |
| buf[0]; |
| } |
| } |
| } |
| |
| static target_ulong rtc_read(void *opaque, target_ulong offset, int size_log2) |
| { |
| RISCVCPUState *s = opaque; |
| uint32_t val; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0: |
| val = rtc_get_time(s); |
| break; |
| case 4: |
| val = rtc_get_time(s) >> 32; |
| break; |
| case 8: |
| val = s->timecmp; |
| break; |
| case 12: |
| val = s->timecmp >> 32; |
| break; |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void rtc_write(void *opaque, target_ulong offset, target_ulong val, |
| int size_log2) |
| { |
| RISCVCPUState *s = opaque; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 8: |
| s->timecmp = (s->timecmp & ~0xffffffff) | val; |
| s->mip &= ~MIP_MTIP; |
| break; |
| case 12: |
| s->timecmp = (s->timecmp & 0xffffffff) | ((uint64_t)val << 32); |
| s->mip &= ~MIP_MTIP; |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void plic_update_mip(RISCVMachine *s) |
| { |
| RISCVCPUState *cpu = s->cpu_state; |
| uint32_t mask; |
| mask = s->plic_pending_irq & ~s->plic_served_irq; |
| if (mask) |
| cpu->mip |= MIP_MEIP | MIP_SEIP; |
| else |
| cpu->mip &= ~(MIP_MEIP | MIP_SEIP); |
| /* exit from power down if an interrupt is pending */ |
| if (cpu->power_down_flag && (cpu->mip & cpu->mie) != 0) |
| cpu->power_down_flag = FALSE; |
| } |
| |
| static target_ulong plic_read(void *opaque, target_ulong offset, int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| uint32_t val, mask; |
| int i; |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 0: |
| val = 0; |
| break; |
| case 4: |
| mask = s->plic_pending_irq & ~s->plic_served_irq; |
| if (mask != 0) { |
| i = ctz32(mask); |
| s->plic_served_irq |= 1 << i; |
| plic_update_mip(s); |
| val = i + 1; |
| } else { |
| val = 0; |
| } |
| break; |
| default: |
| val = 0; |
| break; |
| } |
| return val; |
| } |
| |
| static void plic_write(void *opaque, target_ulong offset, target_ulong val, |
| int size_log2) |
| { |
| RISCVMachine *s = opaque; |
| |
| assert(size_log2 == 2); |
| switch(offset) { |
| case 4: |
| val--; |
| if (val < 32) { |
| s->plic_served_irq &= ~(1 << val); |
| plic_update_mip(s); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void plic_set_irq(RISCVMachine *s, int irq_num, int state) |
| { |
| uint32_t mask; |
| |
| mask = 1 << irq_num; |
| if (state) |
| s->plic_pending_irq |= mask; |
| else |
| s->plic_pending_irq &= ~mask; |
| plic_update_mip(s); |
| } |
| |
| |
| static void setup_linux_config(RISCVCPUState *s, target_ulong ram_size) |
| { |
| char buf[1024]; |
| uint32_t config_addr; |
| int len, i; |
| |
| snprintf(buf, sizeof(buf), |
| "platform {\n" |
| " vendor ucb;\n" |
| " arch spike;\n" |
| "};\n" |
| "rtc {\n" |
| " addr 0x%" PRIx64 ";\n" |
| "};\n" |
| "ram {\n" |
| " 0 {\n" |
| " addr 0x%" PRIx64 ";\n" |
| " size 0x%" PRIx64 ";\n" |
| " };\n" |
| "};\n" |
| "core {\n" |
| " 0" " {\n" |
| " " "0 {\n" |
| " isa " "rv64imafd" ";\n" |
| " timecmp 0x%" PRIx64 ";\n" |
| " ipi 0x" "40001000" ";\n" |
| " };\n" |
| " };\n" |
| "};\n", |
| (uint64_t)RTC_BASE_ADDR, |
| (uint64_t)RAM_BASE_ADDR, |
| (uint64_t)ram_size, |
| (uint64_t)RTC_BASE_ADDR + 8); |
| |
| config_addr = 0x1000 + 8 * 4; |
| /* jump to 0x80000000 */ |
| phys_write_u32(s, 0x1000, 0x297 + 0x80000000 - 0x1000); |
| phys_write_u32(s, 0x1004, 0x00028067); |
| phys_write_u32(s, 0x100c, config_addr); |
| |
| len = strlen(buf); |
| for(i = 0; i < len; i++) |
| phys_write_u8(s, config_addr + i, buf[i]); |
| } |
| |
| static void ide_set_irq(void *opaque, int state) |
| { |
| RISCVMachine *s = opaque; |
| plic_set_irq(s, 1, state); |
| } |
| |
| static target_ulong ide_read(void *opaque, target_ulong offset, |
| int size_log2) |
| { |
| IDEIFState *s = opaque; |
| // printf("read offset=0x%x\n", (int)offset); |
| return ide_mmio_read_u32(s, offset); |
| } |
| |
| static void ide_write(void *opaque, target_ulong offset, target_ulong val, |
| int size_log2) |
| { |
| IDEIFState *s = opaque; |
| // printf("write offset=0x%x\n", (int)offset); |
| ide_mmio_write_u32(s, offset, val); |
| } |
| |
| RISCVMachine *riscv_machine_init(uint64_t ram_size, |
| BOOL rtc_real_time, |
| CharacterDevice *console_dev, |
| BlockDevice *drive) |
| { |
| RISCVMachine *s; |
| |
| s = mallocz(sizeof(*s)); |
| |
| s->cpu_state = riscv_cpu_init(s, ram_size); |
| s->rtc_real_time = rtc_real_time; |
| if (rtc_real_time) { |
| s->rtc_start_time = rtc_get_real_time(s); |
| } |
| cpu_register_device(s->cpu_state, RTC_BASE_ADDR, 16, s->cpu_state, |
| rtc_read, rtc_write, DEVIO_SIZE32); |
| cpu_register_device(s->cpu_state, PLIC_BASE_ADDR, 8, s, |
| plic_read, plic_write, DEVIO_SIZE32); |
| cpu_register_device(s->cpu_state, HTIF_BASE_ADDR, 16, |
| s, htif_read, htif_write, DEVIO_SIZE32); |
| s->htif_dev = console_dev; |
| |
| /* IDE */ |
| s->drives[0] = drive; |
| s->drives[1] = NULL; |
| |
| s->ide_state = ide_init(s->drives, ide_set_irq, s); |
| cpu_register_device(s->cpu_state, IDE_BASE_ADDR, 9 * 4, |
| s->ide_state, ide_read, ide_write, |
| DEVIO_SIZE8 | DEVIO_SIZE16); |
| return s; |
| } |
| |
| void riscv_machine_end(RISCVMachine *s) |
| { |
| /* XXX: stop all */ |
| riscv_cpu_end(s->cpu_state); |
| } |
| |
| #ifdef EMSCRIPTEN |
| |
| static void load_image(RISCVMachine *s, const char *filename); |
| static void load_image_onerror(void *opaque); |
| static void load_image_onload(void *opaque, void *data, int size); |
| void riscv_machine_run(void *opaque); |
| |
| uint32_t ram_size; |
| |
| static uint8_t console_fifo[64]; |
| static int console_fifo_windex; |
| static int console_fifo_rindex; |
| static int console_fifo_count; |
| |
| static void console_write(void *opaque, const uint8_t *buf, int len) |
| { |
| int val, i; |
| |
| for(i = 0; i < len; i++) { |
| val = buf[i]; |
| EM_ASM_({ |
| term.write(String.fromCharCode($0)); |
| }, val); |
| } |
| } |
| |
| static int console_read(void *opaque, uint8_t *buf, int len) |
| { |
| if (console_fifo_count == 0) |
| return 0; |
| buf[0] = console_fifo[console_fifo_rindex]; |
| if (++console_fifo_rindex == sizeof(console_fifo)) |
| console_fifo_rindex = 0; |
| console_fifo_count--; |
| return 1; |
| } |
| |
| void console_queue_char(int c) |
| { |
| if (console_fifo_count < sizeof(console_fifo)) { |
| console_fifo[console_fifo_windex] = c; |
| if (++console_fifo_windex == sizeof(console_fifo)) |
| console_fifo_windex = 0; |
| console_fifo_count++; |
| } |
| } |
| |
| CharacterDevice *console_init(void) |
| { |
| CharacterDevice *dev; |
| dev = mallocz(sizeof(*dev)); |
| dev->write_data = console_write; |
| dev->read_data = console_read; |
| return dev; |
| } |
| |
| typedef struct CachedBlock { |
| struct list_head link; |
| unsigned int block_num; |
| uint8_t data[0]; |
| } CachedBlock; |
| |
| typedef struct BlockDeviceHTTP { |
| FILE *f; |
| int64_t nb_sectors; |
| int block_size; /* in sectors */ |
| int nb_blocks; |
| char url[1024]; |
| struct list_head cached_blocks; /* list of CachedBlock */ |
| int n_cached_blocks; |
| int n_cached_blocks_max; |
| |
| /* current read request */ |
| uint64_t sector_num; |
| int sector_index, sector_count; |
| BlockDeviceCompletionFunc *cb; |
| void *opaque; |
| uint8_t *io_buf; |
| } BlockDeviceHTTP; |
| |
| static void bf_read_onload(void *opaque, void *data, int size); |
| static void bf_read_onerror(void *opaque); |
| |
| static CachedBlock *bf_find_block(BlockDeviceHTTP *bf, unsigned int block_num) |
| { |
| CachedBlock *b; |
| struct list_head *el; |
| |
| list_for_each(el, &bf->cached_blocks) { |
| b = list_entry(el, CachedBlock, link); |
| if (b->block_num == block_num) { |
| /* move to front */ |
| if (bf->cached_blocks.next != el) { |
| list_del(&b->link); |
| list_add(&b->link, &bf->cached_blocks); |
| } |
| return b; |
| } |
| } |
| return NULL; |
| } |
| |
| static void bf_free_block(BlockDeviceHTTP *bf, CachedBlock *b) |
| { |
| bf->n_cached_blocks--; |
| list_del(&b->link); |
| free(b); |
| } |
| |
| static CachedBlock *bf_add_block(BlockDeviceHTTP *bf, unsigned int block_num) |
| { |
| CachedBlock *b; |
| if (bf->n_cached_blocks >= bf->n_cached_blocks_max && 0) { |
| /* free leat used block */ |
| b = list_entry(bf->cached_blocks.prev, CachedBlock, link); |
| bf_free_block(bf, b); |
| } |
| b = malloc(sizeof(CachedBlock) + bf->block_size * 512); |
| b->block_num = block_num; |
| list_add(&b->link, &bf->cached_blocks); |
| bf->n_cached_blocks++; |
| return b; |
| } |
| |
| static int64_t bf_get_sector_count(BlockDevice *bs) |
| { |
| BlockDeviceHTTP *bf = bs->opaque; |
| return bf->nb_sectors; |
| } |
| |
| static int bf_read_async1(BlockDevice *bs, BOOL is_sync) |
| { |
| BlockDeviceHTTP *bf = bs->opaque; |
| char filename[1024]; |
| int offset, block_num, n; |
| CachedBlock *b; |
| |
| for(;;) { |
| n = bf->sector_count - bf->sector_index; |
| if (n == 0) |
| break; |
| block_num = bf->sector_num / bf->block_size; |
| offset = bf->sector_num % bf->block_size; |
| n = min_int(n, bf->block_size - offset); |
| |
| b = bf_find_block(bf, block_num); |
| if (b) { |
| memcpy(bf->io_buf + bf->sector_index * 512, |
| b->data + offset * 512, n * 512); |
| bf->sector_index += n; |
| bf->sector_num += n; |
| } else { |
| /* make a XHR to read the block */ |
| snprintf(filename, sizeof(filename), bf->url, block_num); |
| // printf("wget %s\n", filename); |
| emscripten_async_wget_data(filename, bs, bf_read_onload, |
| bf_read_onerror); |
| return 1; |
| } |
| } |
| |
| if (!is_sync) { |
| /* end of request */ |
| bf->cb(bf->opaque); |
| } |
| return 0; |
| } |
| |
| static int bf_read_async(BlockDevice *bs, |
| uint64_t sector_num, uint8_t *buf, int n, |
| BlockDeviceCompletionFunc *cb, void *opaque) |
| { |
| BlockDeviceHTTP *bf = bs->opaque; |
| // printf("bf_read_async: sector_num=%" PRId64 " n=%d\n", sector_num, n); |
| bf->sector_num = sector_num; |
| bf->io_buf = buf; |
| bf->sector_count = n; |
| bf->sector_index = 0; |
| bf->cb = cb; |
| bf->opaque = opaque; |
| return bf_read_async1(bs, TRUE); |
| } |
| |
| static void bf_read_onload(void *opaque, void *data, int size) |
| { |
| BlockDevice *bs = opaque; |
| BlockDeviceHTTP *bf = bs->opaque; |
| int block_num; |
| CachedBlock *b; |
| |
| assert(size == bf->block_size * 512); |
| block_num = bf->sector_num / bf->block_size; |
| b = bf_add_block(bf, block_num); |
| memcpy(b->data, data, size); |
| |
| /* continue reading */ |
| bf_read_async1(bs, FALSE); |
| } |
| |
| static void bf_read_onerror(void *opaque) |
| { |
| abort(); |
| } |
| |
| static int bf_write_async(BlockDevice *bs, |
| uint64_t sector_num, const uint8_t *buf, int n, |
| BlockDeviceCompletionFunc *cb, void *opaque) |
| { |
| /* no write */ |
| // printf("bf_write_async: sector_num=%" PRId64 " n=%d\n", sector_num, n); |
| return -1; |
| } |
| |
| static BlockDevice *block_device_init_http(const char *url, |
| int block_size_kb, int nb_blocks, |
| int max_cache_size_kb) |
| { |
| BlockDevice *bs; |
| BlockDeviceHTTP *bf; |
| |
| bs = mallocz(sizeof(*bs)); |
| bf = mallocz(sizeof(*bf)); |
| strcpy(bf->url, url); |
| bf->block_size = block_size_kb * 2; |
| bf->nb_sectors = bf->block_size * (uint64_t)nb_blocks; |
| bf->nb_blocks = nb_blocks; |
| init_list_head(&bf->cached_blocks); |
| bf->n_cached_blocks = 0; |
| bf->n_cached_blocks_max = max_int(1, max_cache_size_kb / block_size_kb); |
| |
| bs->opaque = bf; |
| bs->get_sector_count = bf_get_sector_count; |
| bs->read_async = bf_read_async; |
| bs->write_async = bf_write_async; |
| return bs; |
| } |
| |
| #if MAX_XLEN == 64 |
| #define KERNEL_URL "bbl64.bin" |
| #define ROOT_URL "root-riscv64/blk%09u.bin" |
| #define ROOT_BLOCKS 1024 |
| #else |
| #define KERNEL_URL "bbl32.bin" |
| #define ROOT_URL "root-riscv32/blk%09u.bin" |
| #define ROOT_BLOCKS 256 |
| #endif |
| |
| int main(int argc, char **argv) |
| { |
| RISCVMachine *s; |
| BlockDevice *drive; |
| BOOL rtc_real_time; |
| CharacterDevice *console; |
| |
| EM_ASM({ |
| var term_rx_fifo = ""; |
| console_write1 = cwrap('console_queue_char', null, ['number']); |
| function term_handler(str) |
| { |
| var i; |
| for(i = 0; i < str.length; i++) { |
| console_write1(str.charCodeAt(i)); |
| } |
| } |
| |
| term = new Term(80, 30, term_handler); |
| term.open(); |
| }); |
| |
| drive = block_device_init_http(ROOT_URL, 256, ROOT_BLOCKS, 8192); |
| console = console_init(); |
| |
| rtc_real_time = TRUE; |
| ram_size = 256 << 20; |
| s = riscv_machine_init(ram_size, rtc_real_time, console, drive); |
| |
| load_image(s, KERNEL_URL); |
| return 0; |
| } |
| |
| static void load_image(RISCVMachine *s, const char *filename) |
| { |
| emscripten_async_wget_data(filename, s, load_image_onload, |
| load_image_onerror); |
| } |
| |
| static void load_image_onerror(void *opaque) |
| { |
| printf("error while loading image\n"); |
| } |
| |
| static void load_image_onload(void *opaque, void *data, int size) |
| { |
| RISCVMachine *s = opaque; |
| // printf("loaded %d bytes\n", size); |
| if (size > ram_size) { |
| printf("image too big\n"); |
| return; |
| } |
| memcpy(s->cpu_state->phys_mem, data, size); |
| |
| setup_linux_config(s->cpu_state, ram_size); |
| |
| emscripten_async_call(riscv_machine_run, s, 0); |
| } |
| |
| #define MAX_EXEC_CYCLE 1000000 |
| |
| void riscv_machine_run(void *opaque) |
| { |
| RISCVMachine *m = opaque; |
| RISCVCPUState *s = m->cpu_state; |
| int delay1; |
| |
| /* wait for an event: the only asynchronous event is the RTC timer */ |
| if (!(s->mip & MIP_MTIP)) { |
| delay1 = s->timecmp - rtc_get_time(s); |
| if (delay1 <= 0) { |
| s->mip |= MIP_MTIP; |
| s->power_down_flag = FALSE; |
| } |
| } |
| htif_poll(m); |
| if (!s->power_down_flag) { |
| riscv_cpu_interp(s, MAX_EXEC_CYCLE); |
| emscripten_async_call(riscv_machine_run, m, 0); |
| } else { |
| if (!m->rtc_real_time) |
| s->insn_counter += MAX_EXEC_CYCLE; |
| emscripten_async_call(riscv_machine_run, m, 10); |
| } |
| } |
| |
| #else |
| |
| static struct termios oldtty; |
| static int old_fd0_flags; |
| |
| static void term_exit(void) |
| { |
| tcsetattr (0, TCSANOW, &oldtty); |
| fcntl(0, F_SETFL, old_fd0_flags); |
| } |
| |
| static void term_init(BOOL allow_ctrlc) |
| { |
| struct termios tty; |
| |
| memset(&tty, 0, sizeof(tty)); |
| tcgetattr (0, &tty); |
| oldtty = tty; |
| old_fd0_flags = fcntl(0, F_GETFL); |
| |
| tty.c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP |
| |INLCR|IGNCR|ICRNL|IXON); |
| tty.c_oflag |= OPOST; |
| tty.c_lflag &= ~(ECHO|ECHONL|ICANON|IEXTEN); |
| if (!allow_ctrlc) |
| tty.c_lflag &= ~ISIG; |
| tty.c_cflag &= ~(CSIZE|PARENB); |
| tty.c_cflag |= CS8; |
| tty.c_cc[VMIN] = 1; |
| tty.c_cc[VTIME] = 0; |
| |
| tcsetattr (0, TCSANOW, &tty); |
| |
| atexit(term_exit); |
| |
| fcntl(0, F_SETFL, O_NONBLOCK); |
| } |
| |
| static void console_write(void *opaque, const uint8_t *buf, int len) |
| { |
| fwrite(buf, 1, len, stdout); |
| fflush(stdout); |
| } |
| |
| int console_esc_state; |
| |
| static int console_read(void *opaque, uint8_t *buf, int len) |
| { |
| int ret; |
| uint8_t ch; |
| |
| if (len <= 0) |
| return 0; |
| |
| ret = read(0, &ch, 1); |
| if (ret < 0) |
| return 0; |
| if (ret == 0) { |
| /* EOF */ |
| exit(1); |
| } |
| |
| if (console_esc_state) { |
| console_esc_state = 0; |
| switch(ch) { |
| case 'x': |
| printf("Terminated\n"); |
| exit(0); |
| case 'h': |
| printf("\n" |
| "C-a h print this help\n" |
| "C-a x exit emulator\n" |
| "C-a C-a send C-a\n"); |
| break; |
| case 1: |
| goto output_char; |
| default: |
| break; |
| } |
| ret = 0; |
| } else { |
| if (ch == 1) { |
| console_esc_state = 1; |
| ret = 0; |
| } else { |
| output_char: |
| buf[0] = ch; |
| ret = 1; |
| } |
| } |
| return ret; |
| } |
| |
| CharacterDevice *console_init(BOOL allow_ctrlc) |
| { |
| CharacterDevice *dev; |
| term_init(allow_ctrlc); |
| dev = mallocz(sizeof(*dev)); |
| dev->write_data = console_write; |
| dev->read_data = console_read; |
| return dev; |
| } |
| |
| static void load_image(RISCVCPUState *s, const char *filename) |
| { |
| FILE *f; |
| int size; |
| |
| f = fopen(filename, "rb"); |
| if (!f) { |
| perror(filename); |
| exit(1); |
| } |
| fseek(f, 0, SEEK_END); |
| size = ftell(f); |
| fseek(f, 0, SEEK_SET); |
| if (size > s->phys_mem_size) { |
| fprintf(stderr, "%s: image too big\n", filename); |
| exit(1); |
| } |
| if (fread(s->phys_mem, 1, size, f) != size) { |
| fprintf(stderr, "%s: read error\n", filename); |
| exit(1); |
| } |
| fclose(f); |
| } |
| |
| typedef enum { |
| BF_MODE_RO, |
| BF_MODE_RW, |
| BF_MODE_SNAPSHOT, |
| } BlockDeviceModeEnum; |
| |
| #define SECTOR_SIZE 512 |
| |
| typedef struct BlockDeviceFile { |
| FILE *f; |
| int64_t nb_sectors; |
| BlockDeviceModeEnum mode; |
| uint8_t **sector_table; |
| } BlockDeviceFile; |
| |
| static int64_t bf_get_sector_count(BlockDevice *bs) |
| { |
| BlockDeviceFile *bf = bs->opaque; |
| return bf->nb_sectors; |
| } |
| |
| static int bf_read_async(BlockDevice *bs, |
| uint64_t sector_num, uint8_t *buf, int n, |
| BlockDeviceCompletionFunc *cb, void *opaque) |
| { |
| BlockDeviceFile *bf = bs->opaque; |
| // printf("bf_read_async: sector_num=%" PRId64 " n=%d\n", sector_num, n); |
| if (!bf->f) |
| return -1; |
| if (bf->mode == BF_MODE_SNAPSHOT) { |
| int i; |
| for(i = 0; i < n; i++) { |
| if (!bf->sector_table[sector_num]) { |
| fseek(bf->f, sector_num * SECTOR_SIZE, SEEK_SET); |
| fread(buf, 1, SECTOR_SIZE, bf->f); |
| } else { |
| memcpy(buf, bf->sector_table[sector_num], SECTOR_SIZE); |
| } |
| sector_num++; |
| buf += SECTOR_SIZE; |
| } |
| } else { |
| fseek(bf->f, sector_num * SECTOR_SIZE, SEEK_SET); |
| fread(buf, 1, n * SECTOR_SIZE, bf->f); |
| } |
| /* synchronous read */ |
| return 0; |
| } |
| |
| static int bf_write_async(BlockDevice *bs, |
| uint64_t sector_num, const uint8_t *buf, int n, |
| BlockDeviceCompletionFunc *cb, void *opaque) |
| { |
| BlockDeviceFile *bf = bs->opaque; |
| int ret; |
| |
| switch(bf->mode) { |
| case BF_MODE_RO: |
| ret = -1; /* error */ |
| break; |
| case BF_MODE_RW: |
| fseek(bf->f, sector_num * SECTOR_SIZE, SEEK_SET); |
| fwrite(buf, 1, n * SECTOR_SIZE, bf->f); |
| ret = 0; |
| break; |
| case BF_MODE_SNAPSHOT: |
| { |
| int i; |
| if ((sector_num + n) > bf->nb_sectors) |
| return -1; |
| for(i = 0; i < n; i++) { |
| if (!bf->sector_table[sector_num]) { |
| bf->sector_table[sector_num] = malloc(SECTOR_SIZE); |
| } |
| memcpy(bf->sector_table[sector_num], buf, SECTOR_SIZE); |
| sector_num++; |
| buf += SECTOR_SIZE; |
| } |
| ret = 0; |
| } |
| break; |
| default: |
| abort(); |
| } |
| |
| return ret; |
| } |
| |
| static BlockDevice *block_device_init(const char *filename, |
| BlockDeviceModeEnum mode) |
| { |
| BlockDevice *bs; |
| BlockDeviceFile *bf; |
| int64_t file_size; |
| FILE *f; |
| const char *mode_str; |
| |
| if (mode == BF_MODE_RW) { |
| mode_str = "r+b"; |
| } else { |
| mode_str = "rb"; |
| } |
| |
| f = fopen(filename, mode_str); |
| if (!f) { |
| perror(filename); |
| exit(1); |
| } |
| fseek(f, 0, SEEK_END); |
| file_size = ftello(f); |
| |
| bs = mallocz(sizeof(*bs)); |
| bf = mallocz(sizeof(*bf)); |
| |
| bf->mode = mode; |
| bf->nb_sectors = file_size / 512; |
| bf->f = f; |
| |
| if (mode == BF_MODE_SNAPSHOT) { |
| bf->sector_table = mallocz(sizeof(bf->sector_table[0]) * |
| bf->nb_sectors); |
| } |
| |
| bs->opaque = bf; |
| bs->get_sector_count = bf_get_sector_count; |
| bs->read_async = bf_read_async; |
| bs->write_async = bf_write_async; |
| return bs; |
| } |
| |
| #define MAX_EXEC_CYCLE 500000 |
| #define MAX_SLEEP_TIME (RTC_FREQ / 100) /* period of 1/RTC_FREQ seconds */ |
| |
| void riscv_machine_run(RISCVMachine *m) |
| { |
| RISCVCPUState *s = m->cpu_state; |
| int64_t delay1; |
| fd_set rfds; |
| int fd_max, ret, delay; |
| struct timeval tv; |
| |
| /* wait for an event: the only asynchronous event is the RTC timer */ |
| if (s->power_down_flag) { |
| delay = MAX_SLEEP_TIME; |
| } else { |
| delay = 0; |
| } |
| if (!(s->mip & MIP_MTIP)) { |
| delay1 = s->timecmp - rtc_get_time(s); |
| if (delay1 <= 0) { |
| s->mip |= MIP_MTIP; |
| s->power_down_flag = FALSE; |
| delay = 0; |
| } else { |
| if (delay1 < delay) |
| delay = delay1; |
| } |
| } |
| /* wait for an event */ |
| FD_ZERO(&rfds); |
| fd_max = -1; |
| if (m->htif_fromhost == 0) { |
| FD_SET(0, &rfds); |
| fd_max = 0; |
| } |
| tv.tv_sec = 0; |
| tv.tv_usec = delay / (RTC_FREQ / 1000000); |
| ret = select(fd_max + 1, &rfds, NULL, NULL, &tv); |
| if (ret > 0) { |
| if (FD_ISSET(0, &rfds)) { |
| htif_poll(m); |
| } |
| } |
| |
| if (!s->power_down_flag) { |
| riscv_cpu_interp(s, MAX_EXEC_CYCLE); |
| } |
| } |
| |
| static struct option options[] = { |
| { "help", no_argument, NULL, 'h' }, |
| { "ctrlc", no_argument }, |
| { "rw", no_argument }, |
| { "ro", no_argument }, |
| { NULL }, |
| }; |
| |
| #define DEFAULT_RAM_SIZE 256 |
| |
| void help(void) |
| { |
| printf("riscvemu version " CONFIG_VERSION ", Copyright (c) 2016 Fabrice Bellard\n" |
| "usage: riscvemu [options] kernel.bin [hdimage.bin]\n" |
| "options are:\n" |
| "-b [32|64|128] set the integer register width in bits\n" |
| "-m ram_size set the RAM size in MB (default=%d)\n" |
| "-rw allow write access to the disk image (default=snapshot)\n" |
| "-ctrlc the C-c key stops the emulator instead of being sent to the\n" |
| " emulated software\n" |
| "\n" |
| "Console keys:\n" |
| "Press C-a x to exit the emulator, C-a h to get some help.\n", |
| DEFAULT_RAM_SIZE); |
| exit(1); |
| } |
| |
| void launch_alternate_executable(char **argv, int xlen) |
| { |
| char filename[1024]; |
| char new_exename[64]; |
| const char *p, *exename; |
| int len; |
| |
| snprintf(new_exename, sizeof(new_exename), "riscvemu%d", xlen); |
| exename = argv[0]; |
| p = strrchr(exename, '/'); |
| if (p) { |
| len = p - exename + 1; |
| } else { |
| len = 0; |
| } |
| if (len + strlen(new_exename) > sizeof(filename) - 1) { |
| fprintf(stderr, "%s: filename too long\n", exename); |
| exit(1); |
| } |
| memcpy(filename, exename, len); |
| filename[len] = '\0'; |
| strcat(filename, new_exename); |
| argv[0] = filename; |
| |
| if (execvp(argv[0], argv) < 0) { |
| perror(argv[0]); |
| exit(1); |
| } |
| } |
| |
| int main(int argc, char **argv) |
| { |
| RISCVMachine *s; |
| uint64_t ram_size; |
| BlockDevice *drive; |
| BOOL rtc_real_time; |
| CharacterDevice *console; |
| const char *kernel_filename; |
| int c, option_index; |
| BOOL allow_ctrlc; |
| BlockDeviceModeEnum drive_mode; |
| |
| ram_size = (uint64_t)DEFAULT_RAM_SIZE << 20; |
| allow_ctrlc = FALSE; |
| drive_mode = BF_MODE_SNAPSHOT; |
| for(;;) { |
| c = getopt_long_only(argc, argv, "hb:m:", options, &option_index); |
| if (c == -1) |
| break; |
| switch(c) { |
| case 0: |
| switch(option_index) { |
| case 1: /* ctrlc */ |
| allow_ctrlc = TRUE; |
| break; |
| case 2: /* rw */ |
| drive_mode = BF_MODE_RW; |
| break; |
| case 3: /* ro */ |
| drive_mode = BF_MODE_RO; |
| break; |
| default: |
| fprintf(stderr, "unknown option index: %d\n", option_index); |
| exit(1); |
| } |
| break; |
| case 'h': |
| help(); |
| break; |
| case 'b': |
| { |
| int xlen; |
| xlen = atoi(optarg); |
| if (xlen != 32 && xlen != 64 && xlen != 128) { |
| fprintf(stderr, "Invalid integer register width\n"); |
| exit(1); |
| } |
| if (xlen != MAX_XLEN) { |
| launch_alternate_executable(argv, xlen); |
| } |
| } |
| break; |
| case 'm': |
| ram_size = (uint64_t)strtoul(optarg, NULL, 0) << 20; |
| break; |
| default: |
| exit(1); |
| } |
| } |
| |
| if (optind >= argc) { |
| help(); |
| } |
| |
| kernel_filename = argv[optind++]; |
| |
| drive = NULL; |
| if (optind < argc) { |
| drive = block_device_init(argv[optind++], drive_mode); |
| } |
| |
| console = console_init(allow_ctrlc); |
| rtc_real_time = TRUE; |
| s = riscv_machine_init(ram_size, rtc_real_time, console, drive); |
| |
| load_image(s->cpu_state, kernel_filename); |
| setup_linux_config(s->cpu_state, ram_size); |
| |
| for(;;) { |
| riscv_machine_run(s); |
| } |
| riscv_machine_end(s); |
| return 0; |
| } |
| |
| #endif |