blob: ba4fa4c77a8900615e48c411afdeac87060f6db0 [file] [log] [blame] [raw]
/*
* 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 */