6edd2a9bec
Implement PMU extension for LoongArch kvm mode. Use OnOffAuto type variable pmu to check the PMU feature. If the PMU Feature is not supported with KVM host, it reports error if there is pmu=on command line. If there is no any command line about pmu parameter, it checks whether KVM host supports the PMU Feature and set the corresponding value in cpucfg. This patch is based on lbt patch located at https://lore.kernel.org/qemu-devel/20240904061859.86615-1-maobibo@loongson.cn Co-developed-by: Song Gao <gaosong@loongson.cn> Signed-off-by: Bibo Mao <maobibo@loongson.cn> Reviewed-by: Song Gao <gaosong@loongson.cn> Message-Id: <20240918082315.2345034-1-maobibo@loongson.cn> Signed-off-by: Song Gao <gaosong@loongson.cn>
924 lines
29 KiB
C
924 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 "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",
|
|
};
|
|
|
|
struct TypeExcp {
|
|
int32_t exccode;
|
|
const char * const name;
|
|
};
|
|
|
|
static const struct TypeExcp 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"},
|
|
{EXCP_HLT, "EXCP_HLT"},
|
|
};
|
|
|
|
const char *loongarch_exception_name(int32_t exception)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(excp_names); i++) {
|
|
if (excp_names[i].exccode == exception) {
|
|
return excp_names[i].name;
|
|
}
|
|
}
|
|
return "Unknown";
|
|
}
|
|
|
|
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: exception: %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)
|
|
{
|
|
set_pc(cpu_env(cs), value);
|
|
}
|
|
|
|
static vaddr loongarch_cpu_get_pc(CPUState *cs)
|
|
{
|
|
return cpu_env(cs)->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)
|
|
{
|
|
CPULoongArchState *env = cpu_env(cs);
|
|
bool update_badinstr = 1;
|
|
int cause = -1;
|
|
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) {
|
|
qemu_log_mask(CPU_LOG_INT,
|
|
"%s enter: pc " TARGET_FMT_lx " ERA " TARGET_FMT_lx
|
|
" TLBRERA " TARGET_FMT_lx " exception: %d (%s)\n",
|
|
__func__, env->pc, env->CSR_ERA, env->CSR_TLBRERA,
|
|
cs->exception_index,
|
|
loongarch_exception_name(cs->exception_index));
|
|
}
|
|
|
|
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)
|
|
{
|
|
CPULoongArchState *env = cpu_env(cs);
|
|
|
|
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) {
|
|
CPULoongArchState *env = cpu_env(cs);
|
|
|
|
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)
|
|
{
|
|
tcg_debug_assert(!tcg_cflags_has(cs, CF_PCREL));
|
|
set_pc(cpu_env(cs), tb->pc);
|
|
}
|
|
|
|
static void loongarch_restore_state_to_opc(CPUState *cs,
|
|
const TranslationBlock *tb,
|
|
const uint64_t *data)
|
|
{
|
|
set_pc(cpu_env(cs), data[0]);
|
|
}
|
|
#endif /* CONFIG_TCG */
|
|
|
|
static bool loongarch_cpu_has_work(CPUState *cs)
|
|
{
|
|
#ifdef CONFIG_USER_ONLY
|
|
return true;
|
|
#else
|
|
bool has_work = false;
|
|
|
|
if ((cs->interrupt_request & CPU_INTERRUPT_HARD) &&
|
|
cpu_loongarch_hw_interrupts_pending(cpu_env(cs))) {
|
|
has_work = true;
|
|
}
|
|
|
|
return has_work;
|
|
#endif
|
|
}
|
|
|
|
static int loongarch_cpu_mmu_index(CPUState *cs, bool ifetch)
|
|
{
|
|
CPULoongArchState *env = cpu_env(cs);
|
|
|
|
if (FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PG)) {
|
|
return FIELD_EX64(env->CSR_CRMD, CSR_CRMD, PLV);
|
|
}
|
|
return MMU_DA_IDX;
|
|
}
|
|
|
|
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);
|
|
|
|
env->CSR_PRCFG1 = FIELD_DP64(env->CSR_PRCFG1, CSR_PRCFG1, SAVE_NUM, 8);
|
|
env->CSR_PRCFG1 = FIELD_DP64(env->CSR_PRCFG1, CSR_PRCFG1, TIMER_BITS, 0x2f);
|
|
env->CSR_PRCFG1 = FIELD_DP64(env->CSR_PRCFG1, CSR_PRCFG1, VSMAX, 7);
|
|
|
|
env->CSR_PRCFG2 = 0x3ffff000;
|
|
|
|
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);
|
|
|
|
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, ResetType type)
|
|
{
|
|
CPUState *cs = CPU(obj);
|
|
LoongArchCPUClass *lacc = LOONGARCH_CPU_GET_CLASS(obj);
|
|
CPULoongArchState *env = cpu_env(cs);
|
|
|
|
if (lacc->parent_phases.hold) {
|
|
lacc->parent_phases.hold(obj, type);
|
|
}
|
|
|
|
#ifdef CONFIG_TCG
|
|
env->fcsr0_mask = FCSR0_M1 | FCSR0_M2 | FCSR0_M3;
|
|
#endif
|
|
env->fcsr0 = 0x0;
|
|
|
|
int n;
|
|
/* Set csr registers value after reset, see the manual 6.4. */
|
|
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, 0);
|
|
env->CSR_CRMD = FIELD_DP64(env->CSR_CRMD, CSR_CRMD, DATM, 0);
|
|
|
|
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;
|
|
/*
|
|
* Workaround for edk2-stable202408, CSR PGD register is set only if
|
|
* its value is equal to zero for boot cpu, it causes reboot issue.
|
|
*
|
|
* Here clear CSR registers relative with TLB.
|
|
*/
|
|
env->CSR_PGDH = 0;
|
|
env->CSR_PGDL = 0;
|
|
env->CSR_PWCL = 0;
|
|
env->CSR_PWCH = 0;
|
|
env->CSR_STLBPS = 0;
|
|
env->CSR_EENTRY = 0;
|
|
env->CSR_TLBRENTRY = 0;
|
|
env->CSR_MERRENTRY = 0;
|
|
|
|
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;
|
|
#ifdef CONFIG_TCG
|
|
memset(env->tlb, 0, sizeof(env->tlb));
|
|
#endif
|
|
if (kvm_enabled()) {
|
|
kvm_arch_reset_vcpu(cs);
|
|
}
|
|
#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);
|
|
}
|
|
}
|
|
|
|
static bool loongarch_get_lbt(Object *obj, Error **errp)
|
|
{
|
|
return LOONGARCH_CPU(obj)->lbt != ON_OFF_AUTO_OFF;
|
|
}
|
|
|
|
static void loongarch_set_lbt(Object *obj, bool value, Error **errp)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
|
|
cpu->lbt = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
|
|
}
|
|
|
|
static bool loongarch_get_pmu(Object *obj, Error **errp)
|
|
{
|
|
return LOONGARCH_CPU(obj)->pmu != ON_OFF_AUTO_OFF;
|
|
}
|
|
|
|
static void loongarch_set_pmu(Object *obj, bool value, Error **errp)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
|
|
cpu->pmu = value ? ON_OFF_AUTO_ON : ON_OFF_AUTO_OFF;
|
|
}
|
|
|
|
void loongarch_cpu_post_init(Object *obj)
|
|
{
|
|
LoongArchCPU *cpu = LOONGARCH_CPU(obj);
|
|
|
|
object_property_add_bool(obj, "lsx", loongarch_get_lsx,
|
|
loongarch_set_lsx);
|
|
object_property_add_bool(obj, "lasx", loongarch_get_lasx,
|
|
loongarch_set_lasx);
|
|
/* lbt is enabled only in kvm mode, not supported in tcg mode */
|
|
if (kvm_enabled()) {
|
|
cpu->lbt = ON_OFF_AUTO_AUTO;
|
|
object_property_add_bool(obj, "lbt", loongarch_get_lbt,
|
|
loongarch_set_lbt);
|
|
object_property_set_description(obj, "lbt",
|
|
"Set off to disable Binary Tranlation.");
|
|
|
|
cpu->pmu = ON_OFF_AUTO_AUTO;
|
|
object_property_add_bool(obj, "pmu", loongarch_get_pmu,
|
|
loongarch_set_pmu);
|
|
object_property_set_description(obj, "pmu",
|
|
"Set off to performance monitor unit.");
|
|
|
|
} else {
|
|
cpu->lbt = ON_OFF_AUTO_OFF;
|
|
}
|
|
}
|
|
|
|
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)
|
|
{
|
|
CPULoongArchState *env = cpu_env(cs);
|
|
int i;
|
|
|
|
qemu_fprintf(f, " PC=%016" PRIx64 " ", env->pc);
|
|
qemu_fprintf(f, " FCSR0 0x%08x\n", env->fcsr0);
|
|
|
|
/* 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_PRCFG2, 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 const 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,
|
|
.cpu_exec_halt = loongarch_cpu_has_work,
|
|
.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 = {
|
|
.write_elf64_note = loongarch_cpu_write_elf64_note,
|
|
.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->mmu_index = loongarch_cpu_mmu_index;
|
|
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_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_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)
|