1377 lines
37 KiB
C
1377 lines
37 KiB
C
/*
|
|
* 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 */
|
|
|