mirror of
https://github.com/Motorhead1991/qemu.git
synced 2025-08-05 00:33:55 -06:00

LoongArch system has iocsr address space, most iocsr registers are per-board, however some iocsr register spaces banked for percpu such as ipi mailbox and extioi interrupt status. For banked iocsr space, each cpu has the same iocsr space, but separate data. This patch changes iocsr address space per-board rather percpu, for iocsr registers specified for cpu, MemTxAttrs.requester_id can be parsed for the cpu. With this patches, the total address space on board will be simple, only iocsr address space and system memory, rather than the number of cpu and system memory. Signed-off-by: Bibo Mao <maobibo@loongson.cn> Reviewed-by: Song Gao <gaosong@loongson.cn> Message-Id: <20231215100333.3933632-3-maobibo@loongson.cn> Signed-off-by: Song Gao <gaosong@loongson.cn>
866 lines
27 KiB
C
866 lines
27 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 "sysemu/tcg.h"
|
|
#include "sysemu/kvm.h"
|
|
#include "kvm/kvm_loongarch.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 "vec.h"
|
|
#ifdef CONFIG_KVM
|
|
#include <linux/kvm.h>
|
|
#endif
|
|
#ifdef CONFIG_TCG
|
|
#include "exec/cpu_ldst.h"
|
|
#include "tcg/tcg.h"
|
|
#endif
|
|
|
|
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;
|
|
}
|
|
|
|
if (kvm_enabled()) {
|
|
kvm_loongarch_set_interrupt(cpu, irq, level);
|
|
} else if (tcg_enabled()) {
|
|
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;
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_TCG
|
|
#ifndef CONFIG_USER_ONLY
|
|
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
|
|
|
|
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_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_CPUID = cs->cpu_index;
|
|
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_TID = cs->cpu_index;
|
|
|
|
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));
|
|
if (kvm_enabled()) {
|
|
kvm_arch_reset_vcpu(env);
|
|
}
|
|
#endif
|
|
|
|
#ifdef CONFIG_TCG
|
|
restore_fp_status(env);
|
|
#endif
|
|
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);
|
|
}
|
|
|
|
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);
|
|
|
|
qdev_init_gpio_in(DEVICE(cpu), loongarch_cpu_set_irq, N_IRQS);
|
|
#ifdef CONFIG_TCG
|
|
timer_init_ns(&cpu->timer, QEMU_CLOCK_VIRTUAL,
|
|
&loongarch_constant_timer_cb, cpu);
|
|
#endif
|
|
#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);
|
|
}
|
|
|
|
return oc;
|
|
}
|
|
|
|
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 = {
|
|
#ifdef CONFIG_TCG
|
|
.get_phys_page_debug = loongarch_cpu_get_phys_page_debug,
|
|
#endif
|
|
};
|
|
|
|
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)
|