| /* |
| * RISCV CPU emulator |
| * |
| * Copyright (c) 2016-2017 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 "cutils.h" |
| #include "iomem.h" |
| #include "riscv_cpu.h" |
| |
| #ifndef MAX_XLEN |
| #error MAX_XLEN must be defined |
| #endif |
| #ifndef CONFIG_RISCV_MAX_XLEN |
| #error CONFIG_RISCV_MAX_XLEN must be defined |
| #endif |
| |
| //#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 |
| |
| #include "riscv_cpu_priv.h" |
| |
| #if FLEN > 0 |
| #include "softfp.h" |
| #endif |
| |
| #ifdef USE_GLOBAL_STATE |
| static RISCVCPUState riscv_cpu_global_state; |
| #endif |
| #ifdef USE_GLOBAL_VARIABLES |
| #define code_ptr s->__code_ptr |
| #define code_end s->__code_end |
| #define code_to_pc_addend s->__code_to_pc_addend |
| #endif |
| |
| #ifdef CONFIG_LOGFILE |
| static FILE *log_file; |
| |
| static 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 |
| static void log_vprintf(const char *fmt, va_list ap) |
| { |
| vprintf(fmt, ap); |
| } |
| #endif |
| |
| static void __attribute__((format(printf, 1, 2), unused)) 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); |
| } |
| |
| 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(); |
| } |
| |
| /* addr must be aligned. Only RAM accesses are supported */ |
| #define PHYS_MEM_READ_WRITE(size, uint_type) \ |
| static __maybe_unused inline void phys_write_u ## size(RISCVCPUState *s, target_ulong addr,\ |
| uint_type val) \ |
| {\ |
| PhysMemoryRange *pr = get_phys_mem_range(s->mem_map, addr);\ |
| if (!pr || !pr->is_ram)\ |
| return;\ |
| *(uint_type *)(pr->phys_mem + \ |
| (uintptr_t)(addr - pr->addr)) = val;\ |
| }\ |
| \ |
| static __maybe_unused inline uint_type phys_read_u ## size(RISCVCPUState *s, target_ulong addr) \ |
| {\ |
| PhysMemoryRange *pr = get_phys_mem_range(s->mem_map, addr);\ |
| if (!pr || !pr->is_ram)\ |
| return 0;\ |
| return *(uint_type *)(pr->phys_mem + \ |
| (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) |
| |
| #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 mode, levels, pte_bits, pte_idx, pte_mask, pte_size_log2, xwr, priv; |
| int need_write, vaddr_shift, i, pte_addr_bits; |
| 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; |
| } |
| #if MAX_XLEN == 32 |
| /* 32 bits */ |
| mode = s->satp >> 31; |
| if (mode == 0) { |
| /* bare: no translation */ |
| *ppaddr = vaddr; |
| return 0; |
| } else { |
| /* sv32 */ |
| levels = 2; |
| pte_size_log2 = 2; |
| pte_addr_bits = 22; |
| } |
| #else |
| mode = (s->satp >> 60) & 0xf; |
| if (mode == 0) { |
| /* bare: no translation */ |
| *ppaddr = vaddr; |
| return 0; |
| } else { |
| /* sv39/sv48 */ |
| levels = mode - 8 + 3; |
| pte_size_log2 = 3; |
| vaddr_shift = MAX_XLEN - (PG_SHIFT + levels * 9); |
| if ((((target_long)vaddr << vaddr_shift) >> vaddr_shift) != vaddr) |
| return -1; |
| pte_addr_bits = 44; |
| } |
| #endif |
| pte_addr = (s->satp & (((target_ulong)1 << pte_addr_bits) - 1)) << PG_SHIFT; |
| pte_bits = 12 - pte_size_log2; |
| 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_SUM)) |
| 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 */ |
| 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 + 16); |
| 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)) { |
| s->pending_tval = addr; |
| s->pending_exception = CAUSE_LOAD_PAGE_FAULT; |
| return -1; |
| } |
| pr = get_phys_mem_range(s->mem_map, 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 = pr->phys_mem + (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 */ |
| 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)) { |
| s->pending_tval = addr; |
| s->pending_exception = CAUSE_STORE_PAGE_FAULT; |
| return -1; |
| } |
| pr = get_phys_mem_range(s->mem_map, 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) { |
| phys_mem_set_dirty_bit(pr, paddr - pr->addr); |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| ptr = pr->phys_mem + (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 __exception int target_read_insn_slow(RISCVCPUState *s, |
| uint8_t **pptr, |
| target_ulong addr) |
| { |
| int tlb_idx; |
| target_ulong paddr; |
| uint8_t *ptr; |
| PhysMemoryRange *pr; |
| |
| if (get_phys_addr(s, &paddr, addr, ACCESS_CODE)) { |
| s->pending_tval = addr; |
| s->pending_exception = CAUSE_FETCH_PAGE_FAULT; |
| return -1; |
| } |
| pr = get_phys_mem_range(s->mem_map, paddr); |
| if (!pr || !pr->is_ram) { |
| /* XXX: we only access to execute code from RAM */ |
| s->pending_tval = addr; |
| s->pending_exception = CAUSE_FAULT_FETCH; |
| return -1; |
| } |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| ptr = pr->phys_mem + (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; |
| *pptr = ptr; |
| return 0; |
| } |
| |
| /* addr must be aligned */ |
| static inline __exception int target_read_insn_u16(RISCVCPUState *s, uint16_t *pinsn, |
| target_ulong addr) |
| { |
| uint32_t tlb_idx; |
| uint8_t *ptr; |
| |
| tlb_idx = (addr >> PG_SHIFT) & (TLB_SIZE - 1); |
| if (likely(s->tlb_code[tlb_idx].vaddr == (addr & ~PG_MASK))) { |
| ptr = (uint8_t *)(s->tlb_code[tlb_idx].mem_addend + |
| (uintptr_t)addr); |
| } else { |
| if (target_read_insn_slow(s, &ptr, addr)) |
| return -1; |
| } |
| *pinsn = *(uint16_t *)ptr; |
| return 0; |
| } |
| |
| 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); |
| } |
| |
| /* XXX: inefficient but not critical as long as it is seldom used */ |
| static void glue(riscv_cpu_flush_tlb_write_range_ram, |
| MAX_XLEN)(RISCVCPUState *s, |
| uint8_t *ram_ptr, size_t ram_size) |
| { |
| uint8_t *ptr, *ram_end; |
| int i; |
| |
| ram_end = ram_ptr + ram_size; |
| for(i = 0; i < TLB_SIZE; i++) { |
| if (s->tlb_write[i].vaddr != -1) { |
| ptr = (uint8_t *)(s->tlb_write[i].mem_addend + |
| (uintptr_t)s->tlb_write[i].vaddr); |
| if (ptr >= ram_ptr && ptr < ram_end) { |
| s->tlb_write[i].vaddr = -1; |
| } |
| } |
| } |
| } |
| |
| |
| #define SSTATUS_MASK0 (MSTATUS_UIE | MSTATUS_SIE | \ |
| MSTATUS_UPIE | MSTATUS_SPIE | \ |
| MSTATUS_SPP | \ |
| MSTATUS_FS | MSTATUS_XS | \ |
| MSTATUS_SUM | MSTATUS_MXR) |
| #if MAX_XLEN >= 64 |
| #define SSTATUS_MASK (SSTATUS_MASK0 | MSTATUS_UXL_MASK) |
| #else |
| #define SSTATUS_MASK SSTATUS_MASK0 |
| #endif |
| |
| |
| #define MSTATUS_MASK (MSTATUS_UIE | MSTATUS_SIE | MSTATUS_MIE | \ |
| MSTATUS_UPIE | MSTATUS_SPIE | MSTATUS_MPIE | \ |
| MSTATUS_SPP | MSTATUS_MPP | \ |
| MSTATUS_FS | \ |
| MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR) |
| |
| /* cycle and insn counters */ |
| #define COUNTEREN_MASK ((1 << 0) | (1 << 2)) |
| |
| /* 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 int get_base_from_xlen(int xlen) |
| { |
| if (xlen == 32) |
| return 1; |
| else if (xlen == 64) |
| return 2; |
| else |
| return 3; |
| } |
| |
| static void set_mstatus(RISCVCPUState *s, target_ulong val) |
| { |
| target_ulong mod, mask; |
| |
| /* flush the TLBs if change of MMU config */ |
| mod = s->mstatus ^ val; |
| if ((mod & (MSTATUS_MPRV | MSTATUS_SUM | MSTATUS_MXR)) != 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; |
| #if MAX_XLEN >= 64 |
| { |
| int uxl, sxl; |
| uxl = (val >> MSTATUS_UXL_SHIFT) & 3; |
| if (uxl >= 1 && uxl <= get_base_from_xlen(MAX_XLEN)) |
| mask |= MSTATUS_UXL_MASK; |
| sxl = (val >> MSTATUS_UXL_SHIFT) & 3; |
| if (sxl >= 1 && sxl <= get_base_from_xlen(MAX_XLEN)) |
| mask |= MSTATUS_SXL_MASK; |
| } |
| #endif |
| s->mstatus = (s->mstatus & ~mask) | (val & mask); |
| } |
| |
| /* 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 */ |
| { |
| uint32_t counteren; |
| if (s->priv < PRV_M) { |
| if (s->priv < PRV_S) |
| counteren = s->scounteren; |
| else |
| counteren = s->mcounteren; |
| if (((counteren >> (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; |
| { |
| uint32_t counteren; |
| if (s->priv < PRV_M) { |
| if (s->priv < PRV_S) |
| counteren = s->scounteren; |
| else |
| counteren = s->mcounteren; |
| if (((counteren >> (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 0x106: |
| val = s->scounteren; |
| break; |
| case 0x140: |
| val = s->sscratch; |
| break; |
| case 0x141: |
| val = s->sepc; |
| break; |
| case 0x142: |
| val = s->scause; |
| break; |
| case 0x143: |
| val = s->stval; |
| break; |
| case 0x144: /* sip */ |
| val = s->mip & s->mideleg; |
| break; |
| case 0x180: |
| val = s->satp; |
| break; |
| case 0x300: |
| val = get_mstatus(s, (target_ulong)-1); |
| break; |
| case 0x301: |
| val = s->misa; |
| val |= (target_ulong)s->mxl << (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 0x306: |
| val = s->mcounteren; |
| break; |
| case 0x340: |
| val = s->mscratch; |
| break; |
| case 0x341: |
| val = s->mepc; |
| break; |
| case 0x342: |
| val = s->mcause; |
| break; |
| case 0x343: |
| val = s->mtval; |
| 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 |
| /* the 'time' counter is usually emulated */ |
| if (csr != 0xc01 && csr != 0xc81) { |
| 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), 2 if TLBs have been flushed. */ |
| 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 0x106: |
| s->scounteren = val & COUNTEREN_MASK; |
| break; |
| case 0x140: |
| s->sscratch = val; |
| break; |
| case 0x141: |
| s->sepc = val & ~1; |
| break; |
| case 0x142: |
| s->scause = val; |
| break; |
| case 0x143: |
| s->stval = val; |
| break; |
| case 0x144: /* sip */ |
| mask = s->mideleg; |
| s->mip = (s->mip & ~mask) | (val & mask); |
| break; |
| case 0x180: |
| /* no ASID implemented */ |
| #if MAX_XLEN == 32 |
| { |
| int new_mode; |
| new_mode = (val >> 31) & 1; |
| s->satp = (val & (((target_ulong)1 << 22) - 1)) | |
| (new_mode << 31); |
| } |
| #else |
| { |
| int mode, new_mode; |
| mode = s->satp >> 60; |
| new_mode = (val >> 60) & 0xf; |
| if (new_mode == 0 || (new_mode >= 8 && new_mode <= 9)) |
| mode = new_mode; |
| s->satp = (val & (((uint64_t)1 << 44) - 1)) | |
| ((uint64_t)mode << 60); |
| } |
| #endif |
| tlb_flush_all(s); |
| return 2; |
| |
| case 0x300: |
| set_mstatus(s, val); |
| break; |
| case 0x301: /* misa */ |
| #if MAX_XLEN >= 64 |
| { |
| int new_mxl; |
| new_mxl = (val >> (s->cur_xlen - 2)) & 3; |
| if (new_mxl >= 1 && new_mxl <= get_base_from_xlen(MAX_XLEN)) { |
| /* Note: misa is only modified in M level, so cur_xlen |
| = 2^(mxl + 4) */ |
| if (s->mxl != new_mxl) { |
| s->mxl = new_mxl; |
| s->cur_xlen = 1 << (new_mxl + 4); |
| return 1; |
| } |
| } |
| } |
| #endif |
| break; |
| case 0x302: |
| mask = (1 << (CAUSE_STORE_PAGE_FAULT + 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 0x306: |
| s->mcounteren = val & COUNTEREN_MASK; |
| break; |
| case 0x340: |
| s->mscratch = val; |
| break; |
| case 0x341: |
| s->mepc = val & ~1; |
| break; |
| case 0x342: |
| s->mcause = val; |
| break; |
| case 0x343: |
| s->mtval = val; |
| break; |
| case 0x344: |
| mask = MIP_SSIP | MIP_STIP; |
| s->mip = (s->mip & ~mask) | (val & mask); |
| 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); |
| #if MAX_XLEN >= 64 |
| /* change the current xlen */ |
| { |
| int mxl; |
| if (priv == PRV_S) |
| mxl = (s->mstatus >> MSTATUS_SXL_SHIFT) & 3; |
| else if (priv == PRV_U) |
| mxl = (s->mstatus >> MSTATUS_UXL_SHIFT) & 3; |
| else |
| mxl = s->mxl; |
| s->cur_xlen = 1 << (4 + mxl); |
| } |
| #endif |
| s->priv = priv; |
| } |
| } |
| |
| static void raise_exception2(RISCVCPUState *s, uint32_t cause, |
| target_ulong tval) |
| { |
| BOOL deleg; |
| target_ulong causel; |
| |
| #if defined(DUMP_EXCEPTIONS) || defined(DUMP_MMU_EXCEPTIONS) || defined(DUMP_INTERRUPTS) |
| { |
| int flag; |
| flag = 0; |
| #ifdef DUMP_MMU_EXCEPTIONS |
| if (cause == CAUSE_FAULT_FETCH || |
| cause == CAUSE_FAULT_LOAD || |
| cause == CAUSE_FAULT_STORE || |
| cause == CAUSE_FETCH_PAGE_FAULT || |
| cause == CAUSE_LOAD_PAGE_FAULT || |
| cause == CAUSE_STORE_PAGE_FAULT) |
| flag = 1; |
| #endif |
| #ifdef DUMP_INTERRUPTS |
| flag |= (cause & CAUSE_INTERRUPT) != 0; |
| #endif |
| #ifdef DUMP_EXCEPTIONS |
| flag = 1; |
| flag = (cause & CAUSE_INTERRUPT) == 0; |
| if (cause == CAUSE_SUPERVISOR_ECALL || cause == CAUSE_ILLEGAL_INSTRUCTION) |
| flag = 0; |
| #endif |
| if (flag) { |
| log_printf("raise_exception: cause=0x%08x tval=0x", cause); |
| #ifdef CONFIG_LOGFILE |
| fprint_target_ulong(log_file, tval); |
| #else |
| print_target_ulong(tval); |
| #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; |
| s->stval = tval; |
| 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; |
| set_priv(s, PRV_S); |
| s->pc = s->stvec; |
| } else { |
| s->mcause = causel; |
| s->mepc = s->pc; |
| s->mtval = tval; |
| 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; |
| 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; |
| 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; |
| set_priv(s, mpp); |
| s->pc = s->mepc; |
| } |
| |
| 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 __exception int raise_interrupt(RISCVCPUState *s) |
| { |
| uint32_t mask; |
| int irq_num; |
| |
| mask = get_pending_irq_mask(s); |
| if (mask == 0) |
| return 0; |
| irq_num = ctz32(mask); |
| raise_exception(s, irq_num | CAUSE_INTERRUPT); |
| return -1; |
| } |
| |
| 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 "riscv_cpu_template.h" |
| |
| #if MAX_XLEN >= 64 |
| #define XLEN 64 |
| #include "riscv_cpu_template.h" |
| #endif |
| |
| #if MAX_XLEN >= 128 |
| #define XLEN 128 |
| #include "riscv_cpu_template.h" |
| #endif |
| |
| static void glue(riscv_cpu_interp, MAX_XLEN)(RISCVCPUState *s, int n_cycles) |
| { |
| #ifdef USE_GLOBAL_STATE |
| s = &riscv_cpu_global_state; |
| #endif |
| 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_interp_x32(s, n_cycles); |
| break; |
| #if MAX_XLEN >= 64 |
| case 64: |
| riscv_cpu_interp_x64(s, n_cycles); |
| break; |
| #endif |
| #if MAX_XLEN >= 128 |
| case 128: |
| riscv_cpu_interp_x128(s, n_cycles); |
| break; |
| #endif |
| default: |
| abort(); |
| } |
| } |
| } |
| |
| /* Note: the value is not accurate when called in riscv_cpu_interp() */ |
| static uint64_t glue(riscv_cpu_get_cycles, MAX_XLEN)(RISCVCPUState *s) |
| { |
| return s->insn_counter; |
| } |
| |
| static void glue(riscv_cpu_set_mip, MAX_XLEN)(RISCVCPUState *s, uint32_t mask) |
| { |
| s->mip |= mask; |
| /* exit from power down if an interrupt is pending */ |
| if (s->power_down_flag && (s->mip & s->mie) != 0) |
| s->power_down_flag = FALSE; |
| } |
| |
| static void glue(riscv_cpu_reset_mip, MAX_XLEN)(RISCVCPUState *s, uint32_t mask) |
| { |
| s->mip &= ~mask; |
| } |
| |
| static uint32_t glue(riscv_cpu_get_mip, MAX_XLEN)(RISCVCPUState *s) |
| { |
| return s->mip; |
| } |
| |
| static BOOL glue(riscv_cpu_get_power_down, MAX_XLEN)(RISCVCPUState *s) |
| { |
| return s->power_down_flag; |
| } |
| |
| static RISCVCPUState *glue(riscv_cpu_init, MAX_XLEN)(PhysMemoryMap *mem_map) |
| { |
| RISCVCPUState *s; |
| |
| #ifdef USE_GLOBAL_STATE |
| s = &riscv_cpu_global_state; |
| #else |
| s = mallocz(sizeof(*s)); |
| #endif |
| s->common.class_ptr = &glue(riscv_cpu_class, MAX_XLEN); |
| s->mem_map = mem_map; |
| s->pc = 0x1000; |
| s->priv = PRV_M; |
| s->cur_xlen = MAX_XLEN; |
| s->mxl = get_base_from_xlen(MAX_XLEN); |
| s->mstatus = ((uint64_t)s->mxl << MSTATUS_UXL_SHIFT) | |
| ((uint64_t)s->mxl << MSTATUS_SXL_SHIFT); |
| 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; |
| } |
| |
| static void glue(riscv_cpu_end, MAX_XLEN)(RISCVCPUState *s) |
| { |
| #ifdef USE_GLOBAL_STATE |
| free(s); |
| #endif |
| } |
| |
| static uint32_t glue(riscv_cpu_get_misa, MAX_XLEN)(RISCVCPUState *s) |
| { |
| return s->misa; |
| } |
| |
| const RISCVCPUClass glue(riscv_cpu_class, MAX_XLEN) = { |
| glue(riscv_cpu_init, MAX_XLEN), |
| glue(riscv_cpu_end, MAX_XLEN), |
| glue(riscv_cpu_interp, MAX_XLEN), |
| glue(riscv_cpu_get_cycles, MAX_XLEN), |
| glue(riscv_cpu_set_mip, MAX_XLEN), |
| glue(riscv_cpu_reset_mip, MAX_XLEN), |
| glue(riscv_cpu_get_mip, MAX_XLEN), |
| glue(riscv_cpu_get_power_down, MAX_XLEN), |
| glue(riscv_cpu_get_misa, MAX_XLEN), |
| glue(riscv_cpu_flush_tlb_write_range_ram, MAX_XLEN), |
| }; |
| |
| #if CONFIG_RISCV_MAX_XLEN == MAX_XLEN |
| RISCVCPUState *riscv_cpu_init(PhysMemoryMap *mem_map, int max_xlen) |
| { |
| const RISCVCPUClass *c; |
| switch(max_xlen) { |
| /* with emscripten we compile a single CPU */ |
| #if defined(EMSCRIPTEN) |
| case MAX_XLEN: |
| c = &glue(riscv_cpu_class, MAX_XLEN); |
| break; |
| #else |
| case 32: |
| c = &riscv_cpu_class32; |
| break; |
| case 64: |
| c = &riscv_cpu_class64; |
| break; |
| #if CONFIG_RISCV_MAX_XLEN == 128 |
| case 128: |
| c = &riscv_cpu_class128; |
| break; |
| #endif |
| #endif /* !EMSCRIPTEN */ |
| default: |
| return NULL; |
| } |
| return c->riscv_cpu_init(mem_map); |
| } |
| #endif /* CONFIG_RISCV_MAX_XLEN == MAX_XLEN */ |
| |