be45144bee
Timer emulation sometimes is problematic especially when vm is running in kvm mode. This patch adds registers dump support relative with timer hardware, so that it is easier to find the problems. Signed-off-by: Bibo Mao <maobibo@loongson.cn> Reviewed-by: Song Gao <gaosong@loongson.cn> Message-Id: <20231206081839.2290178-1-maobibo@loongson.cn> Signed-off-by: Song Gao <gaosong@loongson.cn>
912 lines
29 KiB
C
912 lines
29 KiB
C
/* SPDX-License-Identifier: GPL-2.0-or-later */
|
|
/*
|
|
* QEMU LoongArch CPU
|
|
*
|
|
* Copyright (c) 2021 Loongson Technology Corporation Limited
|
|
*/
|
|
|
|
#include "qemu/osdep.h"
|
|
#include "qemu/log.h"
|
|
#include "qemu/qemu-print.h"
|
|
#include "qapi/error.h"
|
|
#include "qemu/module.h"
|
|
#include "sysemu/qtest.h"
|
|
#include "exec/cpu_ldst.h"
|
|
#include "exec/exec-all.h"
|
|
#include "cpu.h"
|
|
#include "internals.h"
|
|
#include "fpu/softfloat-helpers.h"
|
|
#include "cpu-csr.h"
|
|
#ifndef CONFIG_USER_ONLY
|
|
#include "sysemu/reset.h"
|
|
#endif
|
|
#include "tcg/tcg.h"
|
|
#include "vec.h"
|
|
|
|
const char * const regnames[32] = {
|
|
"r0", "r1", "r2", "r3", "r4", "r5", "r6", "r7",
|
|
"r8", "r9", "r10", "r11", "r12", "r13", "r14", "r15",
|
|
"r16", "r17", "r18", "r19", "r20", "r21", "r22", "r23",
|
|
"r24", "r25", "r26", "r27", "r28", "r29", "r30", "r31",
|
|
};
|
|
|
|
const char * const fregnames[32] = {
|
|
"f0", "f1", "f2", "f3", "f4", "f5", "f6", "f7",
|
|
"f8", "f9", "f10", "f11", "f12", "f13", "f14", "f15",
|
|
"f16", "f17", "f18", "f19", "f20", "f21", "f22", "f23",
|
|
"f24", "f25", "f26", "f27", "f28", "f29", "f30", "f31",
|
|
};
|
|
|
|
static const char * const excp_names[] = {
|
|
[EXCCODE_INT] = "Interrupt",
|
|
[EXCCODE_PIL] = "Page invalid exception for load",
|
|
[EXCCODE_PIS] = "Page invalid exception for store",
|
|
[EXCCODE_PIF] = "Page invalid exception for fetch",
|
|
[EXCCODE_PME] = "Page modified exception",
|
|
[EXCCODE_PNR] = "Page Not Readable exception",
|
|
[EXCCODE_PNX] = "Page Not Executable exception",
|
|
[EXCCODE_PPI] = "Page Privilege error",
|
|
[EXCCODE_ADEF] = "Address error for instruction fetch",
|
|
[EXCCODE_ADEM] = "Address error for Memory access",
|
|
[EXCCODE_SYS] = "Syscall",
|
|
[EXCCODE_BRK] = "Break",
|
|
[EXCCODE_INE] = "Instruction Non-Existent",
|
|
[EXCCODE_IPE] = "Instruction privilege error",
|
|
[EXCCODE_FPD] = "Floating Point Disabled",
|
|
[EXCCODE_FPE] = "Floating Point Exception",
|
|
[EXCCODE_DBP] = "Debug breakpoint",
|
|
[EXCCODE_BCE] = "Bound Check Exception",
|
|
[EXCCODE_SXD] = "128 bit vector instructions Disable exception",
|
|
[EXCCODE_ASXD] = "256 bit vector instructions Disable exception",
|
|
};
|
|
|
|
const char *loongarch_exception_name(int32_t exception)
|
|
{
|
|
assert(excp_names[exception]);
|
|
return excp_names[exception];
|
|
}
|
|
|
|
void G_NORETURN do_raise_exception(CPULoongArchState *env,
|
|
uint32_t exception,
|
|
uintptr_t pc)
|
|
{
|
|
CPUState *cs = env_cpu(env);
|
|
|
|
qemu_log_mask(CPU_LOG_INT, "%s: %d (%s)\n",
|
|
__func__,
|
|
exception,
|
|
loongarch_exception_name(exception));
|
|
cs->exception_index = exception;
|
|
|
|
cpu_loop_exit_restore(cs, pc);
|
|
}
|
|
|
|
static void loongarch_cpu_set_pc(CPUState *cs, vaddr value)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
set_pc(env, value);
|
|
}
|
|
|
|
static vaddr loongarch_cpu_get_pc(CPUState *cs)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
return env->pc;
|
|
}
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
#include "hw/loongarch/virt.h"
|
|
|
|
void loongarch_cpu_set_irq(void *opaque, int irq, int level)
|
|
{
|
|
LoongArchCPU *cpu = opaque;
|
|
CPULoongArchState *env = &cpu->env;
|
|
CPUState *cs = CPU(cpu);
|
|
|
|
if (irq < 0 || irq >= N_IRQS) {
|
|
return;
|
|
}
|
|
|
|
env->CSR_ESTAT = deposit64(env->CSR_ESTAT, irq, 1, level != 0);
|
|
|
|
if (FIELD_EX64(env->CSR_ESTAT, CSR_ESTAT, IS)) {
|
|
cpu_interrupt(cs, CPU_INTERRUPT_HARD);
|
|
} else {
|
|
cpu_reset_interrupt(cs, CPU_INTERRUPT_HARD);
|
|
}
|
|
}
|
|
|
|
static inline bool cpu_loongarch_hw_interrupts_enabled(CPULoongArchState *env)
|
|
{
|
|
bool ret = 0;
|
|
|
|
ret = (FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE) &&
|
|
!(FIELD_EX64(env->CSR_DBG, CSR_DBG, DST)));
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Check if there is pending and not masked out interrupt */
|
|
static inline bool cpu_loongarch_hw_interrupts_pending(CPULoongArchState *env)
|
|
{
|
|
uint32_t pending;
|
|
uint32_t status;
|
|
|
|
pending = FIELD_EX64(env->CSR_ESTAT, CSR_ESTAT, IS);
|
|
status = FIELD_EX64(env->CSR_ECFG, CSR_ECFG, LIE);
|
|
|
|
return (pending & status) != 0;
|
|
}
|
|
|
|
static void loongarch_cpu_do_interrupt(CPUState *cs)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
bool update_badinstr = 1;
|
|
int cause = -1;
|
|
const char *name;
|
|
bool tlbfill = FIELD_EX64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR);
|
|
uint32_t vec_size = FIELD_EX64(env->CSR_ECFG, CSR_ECFG, VS);
|
|
|
|
if (cs->exception_index != EXCCODE_INT) {
|
|
if (cs->exception_index < 0 ||
|
|
cs->exception_index >= ARRAY_SIZE(excp_names)) {
|
|
name = "unknown";
|
|
} else {
|
|
name = excp_names[cs->exception_index];
|
|
}
|
|
|
|
qemu_log_mask(CPU_LOG_INT,
|
|
"%s enter: pc " TARGET_FMT_lx " ERA " TARGET_FMT_lx
|
|
" TLBRERA " TARGET_FMT_lx " %s exception\n", __func__,
|
|
env->pc, env->CSR_ERA, env->CSR_TLBRERA, name);
|
|
}
|
|
|
|
switch (cs->exception_index) {
|
|
case EXCCODE_DBP:
|
|
env->CSR_DBG = FIELD_DP64(env->CSR_DBG, CSR_DBG, DCL, 1);
|
|
env->CSR_DBG = FIELD_DP64(env->CSR_DBG, CSR_DBG, ECODE, 0xC);
|
|
goto set_DERA;
|
|
set_DERA:
|
|
env->CSR_DERA = env->pc;
|
|
env->CSR_DBG = FIELD_DP64(env->CSR_DBG, CSR_DBG, DST, 1);
|
|
set_pc(env, env->CSR_EENTRY + 0x480);
|
|
break;
|
|
case EXCCODE_INT:
|
|
if (FIELD_EX64(env->CSR_DBG, CSR_DBG, DST)) {
|
|
env->CSR_DBG = FIELD_DP64(env->CSR_DBG, CSR_DBG, DEI, 1);
|
|
goto set_DERA;
|
|
}
|
|
QEMU_FALLTHROUGH;
|
|
case EXCCODE_PIF:
|
|
case EXCCODE_ADEF:
|
|
cause = cs->exception_index;
|
|
update_badinstr = 0;
|
|
break;
|
|
case EXCCODE_SYS:
|
|
case EXCCODE_BRK:
|
|
case EXCCODE_INE:
|
|
case EXCCODE_IPE:
|
|
case EXCCODE_FPD:
|
|
case EXCCODE_FPE:
|
|
case EXCCODE_SXD:
|
|
case EXCCODE_ASXD:
|
|
env->CSR_BADV = env->pc;
|
|
QEMU_FALLTHROUGH;
|
|
case EXCCODE_BCE:
|
|
case EXCCODE_ADEM:
|
|
case EXCCODE_PIL:
|
|
case EXCCODE_PIS:
|
|
case EXCCODE_PME:
|
|
case EXCCODE_PNR:
|
|
case EXCCODE_PNX:
|
|
case EXCCODE_PPI:
|
|
cause = cs->exception_index;
|
|
break;
|
|
default:
|
|
qemu_log("Error: exception(%d) has not been supported\n",
|
|
cs->exception_index);
|
|
abort();
|
|
}
|
|
|
|
if (update_badinstr) {
|
|
env->CSR_BADI = cpu_ldl_code(env, env->pc);
|
|
}
|
|
|
|
/* Save PLV and IE */
|
|
if (tlbfill) {
|
|
env->CSR_TLBRPRMD = FIELD_DP64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PPLV,
|
|
FIELD_EX64(env->CSR_CRMD,
|
|
CSR_CRMD, PLV));
|
|
env->CSR_TLBRPRMD = FIELD_DP64(env->CSR_TLBRPRMD, CSR_TLBRPRMD, PIE,
|
|
FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE));
|
|
/* set the DA mode */
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DA, 1);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PG, 0);
|
|
env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA,
|
|
PC, (env->pc >> 2));
|
|
} else {
|
|
env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, ECODE,
|
|
EXCODE_MCODE(cause));
|
|
env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, ESUBCODE,
|
|
EXCODE_SUBCODE(cause));
|
|
env->CSR_PRMD = FIELD_DP64(env->CSR_PRMD, CSR_PRMD, PPLV,
|
|
FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV));
|
|
env->CSR_PRMD = FIELD_DP64(env->CSR_PRMD, CSR_PRMD, PIE,
|
|
FIELD_EX64(env->CSR_CRMD, CSR_CRMD, IE));
|
|
env->CSR_ERA = env->pc;
|
|
}
|
|
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PLV, 0);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, IE, 0);
|
|
|
|
if (vec_size) {
|
|
vec_size = (1 << vec_size) * 4;
|
|
}
|
|
|
|
if (cs->exception_index == EXCCODE_INT) {
|
|
/* Interrupt */
|
|
uint32_t vector = 0;
|
|
uint32_t pending = FIELD_EX64(env->CSR_ESTAT, CSR_ESTAT, IS);
|
|
pending &= FIELD_EX64(env->CSR_ECFG, CSR_ECFG, LIE);
|
|
|
|
/* Find the highest-priority interrupt. */
|
|
vector = 31 - clz32(pending);
|
|
set_pc(env, env->CSR_EENTRY + \
|
|
(EXCCODE_EXTERNAL_INT + vector) * vec_size);
|
|
qemu_log_mask(CPU_LOG_INT,
|
|
"%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
|
|
" cause %d\n" " A " TARGET_FMT_lx " D "
|
|
TARGET_FMT_lx " vector = %d ExC " TARGET_FMT_lx "ExS"
|
|
TARGET_FMT_lx "\n",
|
|
__func__, env->pc, env->CSR_ERA,
|
|
cause, env->CSR_BADV, env->CSR_DERA, vector,
|
|
env->CSR_ECFG, env->CSR_ESTAT);
|
|
} else {
|
|
if (tlbfill) {
|
|
set_pc(env, env->CSR_TLBRENTRY);
|
|
} else {
|
|
set_pc(env, env->CSR_EENTRY + EXCODE_MCODE(cause) * vec_size);
|
|
}
|
|
qemu_log_mask(CPU_LOG_INT,
|
|
"%s: PC " TARGET_FMT_lx " ERA " TARGET_FMT_lx
|
|
" cause %d%s\n, ESTAT " TARGET_FMT_lx
|
|
" EXCFG " TARGET_FMT_lx " BADVA " TARGET_FMT_lx
|
|
"BADI " TARGET_FMT_lx " SYS_NUM " TARGET_FMT_lu
|
|
" cpu %d asid " TARGET_FMT_lx "\n", __func__, env->pc,
|
|
tlbfill ? env->CSR_TLBRERA : env->CSR_ERA,
|
|
cause, tlbfill ? "(refill)" : "", env->CSR_ESTAT,
|
|
env->CSR_ECFG,
|
|
tlbfill ? env->CSR_TLBRBADV : env->CSR_BADV,
|
|
env->CSR_BADI, env->gpr[11], cs->cpu_index,
|
|
env->CSR_ASID);
|
|
}
|
|
cs->exception_index = -1;
|
|
}
|
|
|
|
static void loongarch_cpu_do_transaction_failed(CPUState *cs, hwaddr physaddr,
|
|
vaddr addr, unsigned size,
|
|
MMUAccessType access_type,
|
|
int mmu_idx, MemTxAttrs attrs,
|
|
MemTxResult response,
|
|
uintptr_t retaddr)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
if (access_type == MMU_INST_FETCH) {
|
|
do_raise_exception(env, EXCCODE_ADEF, retaddr);
|
|
} else {
|
|
do_raise_exception(env, EXCCODE_ADEM, retaddr);
|
|
}
|
|
}
|
|
|
|
static bool loongarch_cpu_exec_interrupt(CPUState *cs, int interrupt_request)
|
|
{
|
|
if (interrupt_request & CPU_INTERRUPT_HARD) {
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
if (cpu_loongarch_hw_interrupts_enabled(env) &&
|
|
cpu_loongarch_hw_interrupts_pending(env)) {
|
|
/* Raise it */
|
|
cs->exception_index = EXCCODE_INT;
|
|
loongarch_cpu_do_interrupt(cs);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_TCG
|
|
static void loongarch_cpu_synchronize_from_tb(CPUState *cs,
|
|
const TranslationBlock *tb)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
tcg_debug_assert(!(cs->tcg_cflags & CF_PCREL));
|
|
set_pc(env, tb->pc);
|
|
}
|
|
|
|
static void loongarch_restore_state_to_opc(CPUState *cs,
|
|
const TranslationBlock *tb,
|
|
const uint64_t *data)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
set_pc(env, data[0]);
|
|
}
|
|
#endif /* CONFIG_TCG */
|
|
|
|
static bool loongarch_cpu_has_work(CPUState *cs)
|
|
{
|
|
#ifdef CONFIG_USER_ONLY
|
|
return true;
|
|
#else
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
bool has_work = false;
|
|
|
|
if ((cs->interrupt_request & CPU_INTERRUPT_HARD) &&
|
|
cpu_loongarch_hw_interrupts_pending(env)) {
|
|
has_work = true;
|
|
}
|
|
|
|
return has_work;
|
|
#endif
|
|
}
|
|
|
|
static void loongarch_la464_initfn(Object *obj)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
CPULoongArchState *env = &cpu->env;
|
|
int i;
|
|
|
|
for (i = 0; i < 21; i++) {
|
|
env->cpucfg[i] = 0x0;
|
|
}
|
|
|
|
cpu->dtb_compatible = "loongarch,Loongson-3A5000";
|
|
env->cpucfg[0] = 0x14c010; /* PRID */
|
|
|
|
uint32_t data = 0;
|
|
data = FIELD_DP32(data, CPUCFG1, ARCH, 2);
|
|
data = FIELD_DP32(data, CPUCFG1, PGMMU, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, IOCSR, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, PALEN, 0x2f);
|
|
data = FIELD_DP32(data, CPUCFG1, VALEN, 0x2f);
|
|
data = FIELD_DP32(data, CPUCFG1, UAL, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, RI, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, EP, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, RPLV, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, HP, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, IOCSR_BRD, 1);
|
|
env->cpucfg[1] = data;
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG2, FP, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, FP_SP, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, FP_DP, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, FP_VER, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, LSX, 1),
|
|
data = FIELD_DP32(data, CPUCFG2, LASX, 1),
|
|
data = FIELD_DP32(data, CPUCFG2, LLFTP, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, LLFTP_VER, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, LSPW, 1);
|
|
data = FIELD_DP32(data, CPUCFG2, LAM, 1);
|
|
env->cpucfg[2] = data;
|
|
|
|
env->cpucfg[4] = 100 * 1000 * 1000; /* Crystal frequency */
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG5, CC_MUL, 1);
|
|
data = FIELD_DP32(data, CPUCFG5, CC_DIV, 1);
|
|
env->cpucfg[5] = data;
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG16, L1_IUPRE, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L1_DPRE, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L2_IUPRE, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L2_IUUNIFY, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L2_IUPRIV, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L3_IUPRE, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L3_IUUNIFY, 1);
|
|
data = FIELD_DP32(data, CPUCFG16, L3_IUINCL, 1);
|
|
env->cpucfg[16] = data;
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG17, L1IU_WAYS, 3);
|
|
data = FIELD_DP32(data, CPUCFG17, L1IU_SETS, 8);
|
|
data = FIELD_DP32(data, CPUCFG17, L1IU_SIZE, 6);
|
|
env->cpucfg[17] = data;
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG18, L1D_WAYS, 3);
|
|
data = FIELD_DP32(data, CPUCFG18, L1D_SETS, 8);
|
|
data = FIELD_DP32(data, CPUCFG18, L1D_SIZE, 6);
|
|
env->cpucfg[18] = data;
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG19, L2IU_WAYS, 15);
|
|
data = FIELD_DP32(data, CPUCFG19, L2IU_SETS, 8);
|
|
data = FIELD_DP32(data, CPUCFG19, L2IU_SIZE, 6);
|
|
env->cpucfg[19] = data;
|
|
|
|
data = 0;
|
|
data = FIELD_DP32(data, CPUCFG20, L3IU_WAYS, 15);
|
|
data = FIELD_DP32(data, CPUCFG20, L3IU_SETS, 14);
|
|
data = FIELD_DP32(data, CPUCFG20, L3IU_SIZE, 6);
|
|
env->cpucfg[20] = data;
|
|
|
|
env->CSR_ASID = FIELD_DP64(0, CSR_ASID, ASIDBITS, 0xa);
|
|
loongarch_cpu_post_init(obj);
|
|
}
|
|
|
|
static void loongarch_la132_initfn(Object *obj)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
int i;
|
|
|
|
for (i = 0; i < 21; i++) {
|
|
env->cpucfg[i] = 0x0;
|
|
}
|
|
|
|
cpu->dtb_compatible = "loongarch,Loongson-1C103";
|
|
env->cpucfg[0] = 0x148042; /* PRID */
|
|
|
|
uint32_t data = 0;
|
|
data = FIELD_DP32(data, CPUCFG1, ARCH, 1); /* LA32 */
|
|
data = FIELD_DP32(data, CPUCFG1, PGMMU, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, IOCSR, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, PALEN, 0x1f); /* 32 bits */
|
|
data = FIELD_DP32(data, CPUCFG1, VALEN, 0x1f); /* 32 bits */
|
|
data = FIELD_DP32(data, CPUCFG1, UAL, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, RI, 0);
|
|
data = FIELD_DP32(data, CPUCFG1, EP, 0);
|
|
data = FIELD_DP32(data, CPUCFG1, RPLV, 0);
|
|
data = FIELD_DP32(data, CPUCFG1, HP, 1);
|
|
data = FIELD_DP32(data, CPUCFG1, IOCSR_BRD, 1);
|
|
env->cpucfg[1] = data;
|
|
}
|
|
|
|
static void loongarch_max_initfn(Object *obj)
|
|
{
|
|
/* '-cpu max' for TCG: we use cpu la464. */
|
|
loongarch_la464_initfn(obj);
|
|
}
|
|
|
|
static void loongarch_cpu_list_entry(gpointer data, gpointer user_data)
|
|
{
|
|
const char *typename = object_class_get_name(OBJECT_CLASS(data));
|
|
|
|
qemu_printf("%s\n", typename);
|
|
}
|
|
|
|
void loongarch_cpu_list(void)
|
|
{
|
|
GSList *list;
|
|
list = object_class_get_list_sorted(TYPE_LOONGARCH_CPU, false);
|
|
g_slist_foreach(list, loongarch_cpu_list_entry, NULL);
|
|
g_slist_free(list);
|
|
}
|
|
|
|
static void loongarch_cpu_reset_hold(Object *obj)
|
|
{
|
|
CPUState *cs = CPU(obj);
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
LoongArchCPUClass *lacc = LOONGARCH_CPU_GET_CLASS(cpu);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
if (lacc->parent_phases.hold) {
|
|
lacc->parent_phases.hold(obj);
|
|
}
|
|
|
|
env->fcsr0_mask = FCSR0_M1 | FCSR0_M2 | FCSR0_M3;
|
|
env->fcsr0 = 0x0;
|
|
|
|
int n;
|
|
/* Set csr registers value after reset */
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PLV, 0);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, IE, 0);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DA, 1);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, PG, 0);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DATF, 1);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DATM, 1);
|
|
|
|
env->CSR_EUEN = FIELD_DP64(env->CSR_EUEN, CSR_EUEN, FPE, 0);
|
|
env->CSR_EUEN = FIELD_DP64(env->CSR_EUEN, CSR_EUEN, SXE, 0);
|
|
env->CSR_EUEN = FIELD_DP64(env->CSR_EUEN, CSR_EUEN, ASXE, 0);
|
|
env->CSR_EUEN = FIELD_DP64(env->CSR_EUEN, CSR_EUEN, BTE, 0);
|
|
|
|
env->CSR_MISC = 0;
|
|
|
|
env->CSR_ECFG = FIELD_DP64(env->CSR_ECFG, CSR_ECFG, VS, 0);
|
|
env->CSR_ECFG = FIELD_DP64(env->CSR_ECFG, CSR_ECFG, LIE, 0);
|
|
|
|
env->CSR_ESTAT = env->CSR_ESTAT & (~MAKE_64BIT_MASK(0, 2));
|
|
env->CSR_RVACFG = FIELD_DP64(env->CSR_RVACFG, CSR_RVACFG, RBITS, 0);
|
|
env->CSR_TCFG = FIELD_DP64(env->CSR_TCFG, CSR_TCFG, EN, 0);
|
|
env->CSR_LLBCTL = FIELD_DP64(env->CSR_LLBCTL, CSR_LLBCTL, KLO, 0);
|
|
env->CSR_TLBRERA = FIELD_DP64(env->CSR_TLBRERA, CSR_TLBRERA, ISTLBR, 0);
|
|
env->CSR_MERRCTL = FIELD_DP64(env->CSR_MERRCTL, CSR_MERRCTL, ISMERR, 0);
|
|
|
|
env->CSR_PRCFG3 = FIELD_DP64(env->CSR_PRCFG3, CSR_PRCFG3, TLB_TYPE, 2);
|
|
env->CSR_PRCFG3 = FIELD_DP64(env->CSR_PRCFG3, CSR_PRCFG3, MTLB_ENTRY, 63);
|
|
env->CSR_PRCFG3 = FIELD_DP64(env->CSR_PRCFG3, CSR_PRCFG3, STLB_WAYS, 7);
|
|
env->CSR_PRCFG3 = FIELD_DP64(env->CSR_PRCFG3, CSR_PRCFG3, STLB_SETS, 8);
|
|
|
|
for (n = 0; n < 4; n++) {
|
|
env->CSR_DMW[n] = FIELD_DP64(env->CSR_DMW[n], CSR_DMW, PLV0, 0);
|
|
env->CSR_DMW[n] = FIELD_DP64(env->CSR_DMW[n], CSR_DMW, PLV1, 0);
|
|
env->CSR_DMW[n] = FIELD_DP64(env->CSR_DMW[n], CSR_DMW, PLV2, 0);
|
|
env->CSR_DMW[n] = FIELD_DP64(env->CSR_DMW[n], CSR_DMW, PLV3, 0);
|
|
}
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
env->pc = 0x1c000000;
|
|
memset(env->tlb, 0, sizeof(env->tlb));
|
|
#endif
|
|
|
|
restore_fp_status(env);
|
|
cs->exception_index = -1;
|
|
}
|
|
|
|
static void loongarch_cpu_disas_set_info(CPUState *s, disassemble_info *info)
|
|
{
|
|
info->print_insn = print_insn_loongarch;
|
|
}
|
|
|
|
static void loongarch_cpu_realizefn(DeviceState *dev, Error **errp)
|
|
{
|
|
CPUState *cs = CPU(dev);
|
|
LoongArchCPUClass *lacc = LOONGARCH_CPU_GET_CLASS(dev);
|
|
Error *local_err = NULL;
|
|
|
|
cpu_exec_realizefn(cs, &local_err);
|
|
if (local_err != NULL) {
|
|
error_propagate(errp, local_err);
|
|
return;
|
|
}
|
|
|
|
loongarch_cpu_register_gdb_regs_for_features(cs);
|
|
|
|
cpu_reset(cs);
|
|
qemu_init_vcpu(cs);
|
|
|
|
lacc->parent_realize(dev, errp);
|
|
}
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
static void loongarch_qemu_write(void *opaque, hwaddr addr,
|
|
uint64_t val, unsigned size)
|
|
{
|
|
qemu_log_mask(LOG_UNIMP, "[%s]: Unimplemented reg 0x%" HWADDR_PRIx "\n",
|
|
__func__, addr);
|
|
}
|
|
|
|
static uint64_t loongarch_qemu_read(void *opaque, hwaddr addr, unsigned size)
|
|
{
|
|
switch (addr) {
|
|
case VERSION_REG:
|
|
return 0x11ULL;
|
|
case FEATURE_REG:
|
|
return 1ULL << IOCSRF_MSI | 1ULL << IOCSRF_EXTIOI |
|
|
1ULL << IOCSRF_CSRIPI;
|
|
case VENDOR_REG:
|
|
return 0x6e6f73676e6f6f4cULL; /* "Loongson" */
|
|
case CPUNAME_REG:
|
|
return 0x303030354133ULL; /* "3A5000" */
|
|
case MISC_FUNC_REG:
|
|
return 1ULL << IOCSRM_EXTIOI_EN;
|
|
}
|
|
return 0ULL;
|
|
}
|
|
|
|
static const MemoryRegionOps loongarch_qemu_ops = {
|
|
.read = loongarch_qemu_read,
|
|
.write = loongarch_qemu_write,
|
|
.endianness = DEVICE_LITTLE_ENDIAN,
|
|
.valid = {
|
|
.min_access_size = 4,
|
|
.max_access_size = 8,
|
|
},
|
|
.impl = {
|
|
.min_access_size = 8,
|
|
.max_access_size = 8,
|
|
},
|
|
};
|
|
#endif
|
|
|
|
static bool loongarch_get_lsx(Object *obj, Error **errp)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
bool ret;
|
|
|
|
if (FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LSX)) {
|
|
ret = true;
|
|
} else {
|
|
ret = false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void loongarch_set_lsx(Object *obj, bool value, Error **errp)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
|
|
if (value) {
|
|
cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LSX, 1);
|
|
} else {
|
|
cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LSX, 0);
|
|
cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LASX, 0);
|
|
}
|
|
}
|
|
|
|
static bool loongarch_get_lasx(Object *obj, Error **errp)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
bool ret;
|
|
|
|
if (FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LASX)) {
|
|
ret = true;
|
|
} else {
|
|
ret = false;
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static void loongarch_set_lasx(Object *obj, bool value, Error **errp)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
|
|
if (value) {
|
|
if (!FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LSX)) {
|
|
cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LSX, 1);
|
|
}
|
|
cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LASX, 1);
|
|
} else {
|
|
cpu->env.cpucfg[2] = FIELD_DP32(cpu->env.cpucfg[2], CPUCFG2, LASX, 0);
|
|
}
|
|
}
|
|
|
|
void loongarch_cpu_post_init(Object *obj)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
|
|
if (FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LSX)) {
|
|
object_property_add_bool(obj, "lsx", loongarch_get_lsx,
|
|
loongarch_set_lsx);
|
|
}
|
|
if (FIELD_EX32(cpu->env.cpucfg[2], CPUCFG2, LASX)) {
|
|
object_property_add_bool(obj, "lasx", loongarch_get_lasx,
|
|
loongarch_set_lasx);
|
|
}
|
|
}
|
|
|
|
static void loongarch_cpu_init(Object *obj)
|
|
{
|
|
#ifndef CONFIG_USER_ONLY
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
CPULoongArchState *env = &cpu->env;
|
|
|
|
qdev_init_gpio_in(DEVICE(cpu), loongarch_cpu_set_irq, N_IRQS);
|
|
timer_init_ns(&cpu->timer, QEMU_CLOCK_VIRTUAL,
|
|
&loongarch_constant_timer_cb, cpu);
|
|
memory_region_init_io(&env->system_iocsr, OBJECT(cpu), NULL,
|
|
env, "iocsr", UINT64_MAX);
|
|
address_space_init(&env->address_space_iocsr, &env->system_iocsr, "IOCSR");
|
|
memory_region_init_io(&env->iocsr_mem, OBJECT(cpu), &loongarch_qemu_ops,
|
|
NULL, "iocsr_misc", 0x428);
|
|
memory_region_add_subregion(&env->system_iocsr, 0, &env->iocsr_mem);
|
|
#endif
|
|
}
|
|
|
|
static ObjectClass *loongarch_cpu_class_by_name(const char *cpu_model)
|
|
{
|
|
ObjectClass *oc;
|
|
|
|
oc = object_class_by_name(cpu_model);
|
|
if (!oc) {
|
|
g_autofree char *typename
|
|
= g_strdup_printf(LOONGARCH_CPU_TYPE_NAME("%s"), cpu_model);
|
|
oc = object_class_by_name(typename);
|
|
if (!oc) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (object_class_dynamic_cast(oc, TYPE_LOONGARCH_CPU)) {
|
|
return oc;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void loongarch_cpu_dump_state(CPUState *cs, FILE *f, int flags)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
CPULoongArchState *env = &cpu->env;
|
|
int i;
|
|
|
|
qemu_fprintf(f, " PC=%016" PRIx64 " ", env->pc);
|
|
qemu_fprintf(f, " FCSR0 0x%08x fp_status 0x%02x\n", env->fcsr0,
|
|
get_float_exception_flags(&env->fp_status));
|
|
|
|
/* gpr */
|
|
for (i = 0; i < 32; i++) {
|
|
if ((i & 3) == 0) {
|
|
qemu_fprintf(f, " GPR%02d:", i);
|
|
}
|
|
qemu_fprintf(f, " %s %016" PRIx64, regnames[i], env->gpr[i]);
|
|
if ((i & 3) == 3) {
|
|
qemu_fprintf(f, "\n");
|
|
}
|
|
}
|
|
|
|
qemu_fprintf(f, "CRMD=%016" PRIx64 "\n", env->CSR_CRMD);
|
|
qemu_fprintf(f, "PRMD=%016" PRIx64 "\n", env->CSR_PRMD);
|
|
qemu_fprintf(f, "EUEN=%016" PRIx64 "\n", env->CSR_EUEN);
|
|
qemu_fprintf(f, "ESTAT=%016" PRIx64 "\n", env->CSR_ESTAT);
|
|
qemu_fprintf(f, "ERA=%016" PRIx64 "\n", env->CSR_ERA);
|
|
qemu_fprintf(f, "BADV=%016" PRIx64 "\n", env->CSR_BADV);
|
|
qemu_fprintf(f, "BADI=%016" PRIx64 "\n", env->CSR_BADI);
|
|
qemu_fprintf(f, "EENTRY=%016" PRIx64 "\n", env->CSR_EENTRY);
|
|
qemu_fprintf(f, "PRCFG1=%016" PRIx64 ", PRCFG2=%016" PRIx64 ","
|
|
" PRCFG3=%016" PRIx64 "\n",
|
|
env->CSR_PRCFG1, env->CSR_PRCFG3, env->CSR_PRCFG3);
|
|
qemu_fprintf(f, "TLBRENTRY=%016" PRIx64 "\n", env->CSR_TLBRENTRY);
|
|
qemu_fprintf(f, "TLBRBADV=%016" PRIx64 "\n", env->CSR_TLBRBADV);
|
|
qemu_fprintf(f, "TLBRERA=%016" PRIx64 "\n", env->CSR_TLBRERA);
|
|
qemu_fprintf(f, "TCFG=%016" PRIx64 "\n", env->CSR_TCFG);
|
|
qemu_fprintf(f, "TVAL=%016" PRIx64 "\n", env->CSR_TVAL);
|
|
|
|
/* fpr */
|
|
if (flags & CPU_DUMP_FPU) {
|
|
for (i = 0; i < 32; i++) {
|
|
qemu_fprintf(f, " %s %016" PRIx64, fregnames[i], env->fpr[i].vreg.D(0));
|
|
if ((i & 3) == 3) {
|
|
qemu_fprintf(f, "\n");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifdef CONFIG_TCG
|
|
#include "hw/core/tcg-cpu-ops.h"
|
|
|
|
static struct TCGCPUOps loongarch_tcg_ops = {
|
|
.initialize = loongarch_translate_init,
|
|
.synchronize_from_tb = loongarch_cpu_synchronize_from_tb,
|
|
.restore_state_to_opc = loongarch_restore_state_to_opc,
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
.tlb_fill = loongarch_cpu_tlb_fill,
|
|
.cpu_exec_interrupt = loongarch_cpu_exec_interrupt,
|
|
.do_interrupt = loongarch_cpu_do_interrupt,
|
|
.do_transaction_failed = loongarch_cpu_do_transaction_failed,
|
|
#endif
|
|
};
|
|
#endif /* CONFIG_TCG */
|
|
|
|
#ifndef CONFIG_USER_ONLY
|
|
#include "hw/core/sysemu-cpu-ops.h"
|
|
|
|
static const struct SysemuCPUOps loongarch_sysemu_ops = {
|
|
.get_phys_page_debug = loongarch_cpu_get_phys_page_debug,
|
|
};
|
|
|
|
static int64_t loongarch_cpu_get_arch_id(CPUState *cs)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(cs);
|
|
|
|
return cpu->phy_id;
|
|
}
|
|
#endif
|
|
|
|
static void loongarch_cpu_class_init(ObjectClass *c, void *data)
|
|
{
|
|
LoongArchCPUClass *lacc = LOONGARCH_CPU_CLASS(c);
|
|
CPUClass *cc = CPU_CLASS(c);
|
|
DeviceClass *dc = DEVICE_CLASS(c);
|
|
ResettableClass *rc = RESETTABLE_CLASS(c);
|
|
|
|
device_class_set_parent_realize(dc, loongarch_cpu_realizefn,
|
|
&lacc->parent_realize);
|
|
resettable_class_set_parent_phases(rc, NULL, loongarch_cpu_reset_hold, NULL,
|
|
&lacc->parent_phases);
|
|
|
|
cc->class_by_name = loongarch_cpu_class_by_name;
|
|
cc->has_work = loongarch_cpu_has_work;
|
|
cc->dump_state = loongarch_cpu_dump_state;
|
|
cc->set_pc = loongarch_cpu_set_pc;
|
|
cc->get_pc = loongarch_cpu_get_pc;
|
|
#ifndef CONFIG_USER_ONLY
|
|
cc->get_arch_id = loongarch_cpu_get_arch_id;
|
|
dc->vmsd = &vmstate_loongarch_cpu;
|
|
cc->sysemu_ops = &loongarch_sysemu_ops;
|
|
#endif
|
|
cc->disas_set_info = loongarch_cpu_disas_set_info;
|
|
cc->gdb_read_register = loongarch_cpu_gdb_read_register;
|
|
cc->gdb_write_register = loongarch_cpu_gdb_write_register;
|
|
cc->gdb_stop_before_watchpoint = true;
|
|
|
|
#ifdef CONFIG_TCG
|
|
cc->tcg_ops = &loongarch_tcg_ops;
|
|
#endif
|
|
}
|
|
|
|
static const gchar *loongarch32_gdb_arch_name(CPUState *cs)
|
|
{
|
|
return "loongarch32";
|
|
}
|
|
|
|
static void loongarch32_cpu_class_init(ObjectClass *c, void *data)
|
|
{
|
|
CPUClass *cc = CPU_CLASS(c);
|
|
|
|
cc->gdb_num_core_regs = 35;
|
|
cc->gdb_core_xml_file = "loongarch-base32.xml";
|
|
cc->gdb_arch_name = loongarch32_gdb_arch_name;
|
|
}
|
|
|
|
static const gchar *loongarch64_gdb_arch_name(CPUState *cs)
|
|
{
|
|
return "loongarch64";
|
|
}
|
|
|
|
static void loongarch64_cpu_class_init(ObjectClass *c, void *data)
|
|
{
|
|
CPUClass *cc = CPU_CLASS(c);
|
|
|
|
cc->gdb_num_core_regs = 35;
|
|
cc->gdb_core_xml_file = "loongarch-base64.xml";
|
|
cc->gdb_arch_name = loongarch64_gdb_arch_name;
|
|
}
|
|
|
|
#define DEFINE_LOONGARCH_CPU_TYPE(size, model, initfn) \
|
|
{ \
|
|
.parent = TYPE_LOONGARCH##size##_CPU, \
|
|
.instance_init = initfn, \
|
|
.name = LOONGARCH_CPU_TYPE_NAME(model), \
|
|
}
|
|
|
|
static const TypeInfo loongarch_cpu_type_infos[] = {
|
|
{
|
|
.name = TYPE_LOONGARCH_CPU,
|
|
.parent = TYPE_CPU,
|
|
.instance_size = sizeof(LoongArchCPU),
|
|
.instance_align = __alignof(LoongArchCPU),
|
|
.instance_init = loongarch_cpu_init,
|
|
|
|
.abstract = true,
|
|
.class_size = sizeof(LoongArchCPUClass),
|
|
.class_init = loongarch_cpu_class_init,
|
|
},
|
|
{
|
|
.name = TYPE_LOONGARCH32_CPU,
|
|
.parent = TYPE_LOONGARCH_CPU,
|
|
|
|
.abstract = true,
|
|
.class_init = loongarch32_cpu_class_init,
|
|
},
|
|
{
|
|
.name = TYPE_LOONGARCH64_CPU,
|
|
.parent = TYPE_LOONGARCH_CPU,
|
|
|
|
.abstract = true,
|
|
.class_init = loongarch64_cpu_class_init,
|
|
},
|
|
DEFINE_LOONGARCH_CPU_TYPE(64, "la464", loongarch_la464_initfn),
|
|
DEFINE_LOONGARCH_CPU_TYPE(32, "la132", loongarch_la132_initfn),
|
|
DEFINE_LOONGARCH_CPU_TYPE(64, "max", loongarch_max_initfn),
|
|
};
|
|
|
|
DEFINE_TYPES(loongarch_cpu_type_infos)
|