Message ID | 20220415094058.3584233-26-yangxiaojuan@loongson.cn (mailing list archive) |
---|---|
State | New, archived |
Headers | show |
Series | Add LoongArch softmmu support | expand |
On 4/15/22 02:40, Xiaojuan Yang wrote: > +int cpu_csr_offset(unsigned csr_num); ... > +static const uint64_t csr_offsets[] = { There's no reason for this array to be uint64_t. It really should match the function. > +target_ulong helper_csrwr_estat(CPULoongArchState *env, target_ulong val) > +{ > + int64_t old_v = env->CSR_ESTAT; > + > + /* Only IS[1:0] can be written */ > + env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, IS, val & 0x3); > + > + return old_v; > +} This is incorrect. You're writing to all 13 bits of ESTAT.IS with the low 2 bits of val -- i.e. clearing bits [12:2]. You want to use: env->CSR_ESTAT = deposit64(env->CSR_ESTAT, 0, 2, val); > +target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val) > +{ > + int64_t old_v = env->CSR_ASID; > + > + /* Only ASID filed of CSR_ASID can be written */ > + env->CSR_ASID = FIELD_DP64(env->CSR_ASID, CSR_ASID, ASID, > + val & R_CSR_ASID_ASID_MASK); You do not need to mask the 4th argument of FIELD_DP64 -- that happens as part of the deposit operation. > + if (old_v != val) { > + tlb_flush(env_cpu(env)); > + } You shouldn't be comparing val to old_v, but old_v to the updated CSR_ASID. > +void helper_csr_update(CPULoongArchState *env, target_ulong new_val, > + target_ulong csr_offset) > +{ > + uint64_t *csr = (void *)env + csr_offset; > + > + *csr = new_val; > +} This function should not exist. > +static void output_r_csr(DisasContext *ctx, arg_r_csr *a, > + const char *mnemonic) > +{ > + output(ctx, mnemonic, "r%d, %d", a->rd, a->csr); > +} > + > +static void output_rr_csr(DisasContext *ctx, arg_rr_csr *a, > + const char *mnemonic) > +{ > + output(ctx, mnemonic, "r%d, r%d, %d", a->rd, a->rj, a->csr); > +} While this is fine, it would be nicer to print the name of CSR, when valid. > +static void gen_disas_exit(DisasContext *ctx) > +{ > + tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4); > + ctx->base.is_jmp = DISAS_EXIT; > +} Why this function, and not simply place the movi in loongarch_tr_tb_stop too? Or even just put the tcg_gen_exit_tb() here, and set DISAS_NORETURN. I would say: one way or the other but not this mix... > +static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a) > +{ > + TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE); > + > + if (check_plv(ctx)) { > + return false; > + } > + > + unsigned csr_offset = cpu_csr_offset(a->csr); This is incorrect -- csr_offset must be signed, 'int', to match cpu_csr_offset and the single negative value that exists within (CPUID). > +static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a) > +{ > + TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE); > + TCGv src1 = gpr_src(ctx, a->rd, EXT_NONE); > + > + if (check_plv(ctx) || ro_csr(a->csr)) { > + return false; > + } > + > + unsigned csr_offset = cpu_csr_offset(a->csr); Again, 'int'. > + if (csr_offset == 0) { > + /* CSR is undefined: write ignored. */ > + return true; > + } > + > + switch (a->csr) { > + case LOONGARCH_CSR_ESTAT: > + gen_helper_csrwr_estat(dest, cpu_env, src1); > + break; > + case LOONGARCH_CSR_ASID: > + gen_helper_csrwr_asid(dest, cpu_env, src1); > + gen_disas_exit(ctx); > + break; > + case LOONGARCH_CSR_TCFG: > + gen_helper_csrwr_tcfg(dest, cpu_env, src1); > + break; > + case LOONGARCH_CSR_TICLR: > + gen_helper_csrwr_ticlr(dest, cpu_env, src1); > + break; > + default: > + { > + TCGv temp = tcg_temp_new(); > + tcg_gen_ld_tl(temp, cpu_env, csr_offset); > + tcg_gen_st_tl(src1, cpu_env, csr_offset); > + tcg_gen_mov_tl(dest, temp); > + tcg_temp_free(temp); > + > + /* Cpu state may be changed, need exit */ > + if ((a->csr == LOONGARCH_CSR_CRMD) || > + (a->csr == LOONGARCH_CSR_EUEN)) { > + gen_disas_exit(ctx); > + } > + } > + } I said before that you needed to split out the body of this function for re-use by csrxchg. > + tcg_gen_not_tl(t1, mask); > + tcg_gen_and_tl(t1, old_val, t1); This is tcg_gen_andc_t1 (t1, old_val, mask). > + switch (a->csr) { > + case LOONGARCH_CSR_ESTAT: > + gen_helper_csrwr_estat(dest, cpu_env, new_val); > + break; > + case LOONGARCH_CSR_ASID: > + gen_helper_csrwr_asid(dest, cpu_env, new_val); > + break; > + case LOONGARCH_CSR_TCFG: > + gen_helper_csrwr_tcfg(dest, cpu_env, new_val); > + break; > + case LOONGARCH_CSR_TICLR: > + gen_helper_csrwr_ticlr(dest, cpu_env, new_val); > + break; > + default: > + tcg_gen_mov_tl(dest, old_val); > + } > + > + gen_helper_csr_update(cpu_env, new_val, tcg_constant_tl(csr_offset)); Note that helper_csr_update is nothing more than the store to csr_offset. > + > + if ((a->csr == LOONGARCH_CSR_ASID) || (a->csr == LOONGARCH_CSR_CRMD) || > + (a->csr == LOONGARCH_CSR_EUEN)) { > + gen_disas_exit(ctx); > + } Note that this list does not match the list in trans_csrwr. This is *exactly* why I said that you should split out a function for use between csrwr and csrxchg. r~
On 2022/4/16 上午9:04, Richard Henderson wrote: >> +int cpu_csr_offset(unsigned csr_num); > ... >> +static const uint64_t csr_offsets[] = { > > There's no reason for this array to be uint64_t. > It really should match the function. Yes, we shoud do this. If we use 'int', we may get a warning: ../target/loongarch/csr_helper.c:49:30: warning: overflow in implicit constant conversion [-Woverflow] [LOONGARCH_CSR_CPUID] = offsetof(CPUState, cpu_index) ^~~~~~~~ How about use 'long'? I had tested it no warning. Thanks. Xiaojuan
On 4/18/22 05:38, yangxiaojuan wrote: > > On 2022/4/16 上午9:04, Richard Henderson wrote: >>> +int cpu_csr_offset(unsigned csr_num); >> ... >>> +static const uint64_t csr_offsets[] = { >> >> There's no reason for this array to be uint64_t. >> It really should match the function. > Yes, we shoud do this. > > If we use 'int', we may get a warning: > ../target/loongarch/csr_helper.c:49:30: warning: overflow in implicit constant conversion > [-Woverflow] > [LOONGARCH_CSR_CPUID] = offsetof(CPUState, cpu_index) > ^~~~~~~~ > How about use 'long'? I had tested it no warning. That is because offsetof() result is size_t, which is unsigned, and a "negative" unsigned number is a large positive. Cast each offsetof to int. r~
On 2022/4/16 上午9:04, Richard Henderson wrote: > On 4/15/22 02:40, Xiaojuan Yang wrote: ... >> +void helper_csr_update(CPULoongArchState *env, target_ulong new_val, >> + target_ulong csr_offset) >> +{ >> + uint64_t *csr = (void *)env + csr_offset; >> + >> + *csr = new_val; >> +} > This function should not exist ... >> + switch (a->csr) { >> + case LOONGARCH_CSR_ESTAT: >> + gen_helper_csrwr_estat(dest, cpu_env, new_val); >> + break; >> + case LOONGARCH_CSR_ASID: >> + gen_helper_csrwr_asid(dest, cpu_env, new_val); >> + break; >> + case LOONGARCH_CSR_TCFG: >> + gen_helper_csrwr_tcfg(dest, cpu_env, new_val); >> + break; >> + case LOONGARCH_CSR_TICLR: >> + gen_helper_csrwr_ticlr(dest, cpu_env, new_val); >> + break; >> + default: >> + tcg_gen_mov_tl(dest, old_val); >> + } >> + >> + gen_helper_csr_update(cpu_env, new_val, >> tcg_constant_tl(csr_offset)); > > Note that helper_csr_update is nothing more than the store to csr_offset. On trans_csrxchg() , I am don't know how to use a TCGv value 'new_val 'to update an uint64_t value "CSR_XXX", So I use helper_csr_update(), Thanks. Xiaojuan > r~
On 4/19/22 00:33, yangxiaojuan wrote: > > On 2022/4/16 上午9:04, Richard Henderson wrote: >> On 4/15/22 02:40, Xiaojuan Yang wrote: > ... >>> +void helper_csr_update(CPULoongArchState *env, target_ulong new_val, >>> + target_ulong csr_offset) >>> +{ >>> + uint64_t *csr = (void *)env + csr_offset; >>> + >>> + *csr = new_val; >>> +} >> This function should not exist > ... >>> + switch (a->csr) { >>> + case LOONGARCH_CSR_ESTAT: >>> + gen_helper_csrwr_estat(dest, cpu_env, new_val); >>> + break; >>> + case LOONGARCH_CSR_ASID: >>> + gen_helper_csrwr_asid(dest, cpu_env, new_val); >>> + break; >>> + case LOONGARCH_CSR_TCFG: >>> + gen_helper_csrwr_tcfg(dest, cpu_env, new_val); >>> + break; >>> + case LOONGARCH_CSR_TICLR: >>> + gen_helper_csrwr_ticlr(dest, cpu_env, new_val); >>> + break; >>> + default: >>> + tcg_gen_mov_tl(dest, old_val); >>> + } >>> + >>> + gen_helper_csr_update(cpu_env, new_val, tcg_constant_tl(csr_offset)); >> >> Note that helper_csr_update is nothing more than the store to csr_offset. > On trans_csrxchg() , I am don't know how to use a TCGv value 'new_val 'to update an > uint64_t value "CSR_XXX", So I use helper_csr_update(), You'd use a store, just like you were already doing in trans_csrwr. But here's how I'd improve this. For avoidance of doubt, all of this would go in trans_priviledged.c.inc -- there's no use of csr_offsets[] outside of that file. r~ ---- typedef void (*GenCSRRead)(TCGv dest, TCGv_ptr env); typedef void (*GenCSRWrite)(TCGv dest, TCGv_ptr env, TCGv src); typedef struct { int offset; int flags; GenCSRRead readfn; GenCSRWrite writefn; } CSRInfo; enum { CSRFL_READONLY = (1 << 0), CSRFL_EXITTB = (1 << 1), CSRFL_IO = (1 << 2), }; #define CSR_OFF_FUNCS(NAME, FL, RD, WR) \ [LOONGARCH_CSR_##NAME] = { \ .offset = offsetof(CPULoongArchState, CSR_##NAME), \ .flags = FL, .readfn = RD, .writefn = WR \ } #define CSR_OFF_FLAGS(NAME, FL) \ CSR_OFF_FUNCS(NAME, FL, NULL, NULL) #define CSR_OFF(NAME) \ CSR_OFF_FLAGS(NAME, 0) static const CSRInfo csr_info[] = { CSR_OFF(CRMD), CSR_OFF_FLAGS(CPUID, CSRFL_READONLY), CSR_OFF_FUNCS(TCFG, CSRFL_IO, NULL, gen_helper_csrwr_tcfg), CSR_OFF_FUNCS(TVAL, CSRFL_READONLY | CSRFL_IO, gen_helper_csrrd_tval, NULL), CSR_OFF_FUNCS(TICLR, CSRFL_IO, NULL, gen_helper_csrwr_ticlr), CSR_OFF_FUNCS(ESTAT, CSRFL_EXITTB, NULL, gen_helper_csrwr_estat), ... }; static const CSRInfo *get_csr(unsigned csr_num) { const CSRInfo *csr; if (csr_num < ARRAY_SIZE(csr_info)) { return NULL; } csr = &csr_info[csr_num]; if (csr->offset == 0) { return NULL; /* undefined */ } return csr; } static bool check_csr_flags(DisasContext *ctx, const CSRInfo *csr, bool write) { if ((info->flags & CSRFL_READONLY) && write) { return false; } if ((info->flags & CSRFL_IO) && (tb_cflags(ctx->base.tb) & CF_USE_ICOUNT)) { gen_io_start(); ctx->base.is_jmp = DISAS_EXIT_UPDATE; } else if ((info->flags & CSRFL_EXITTB) && write) { ctx->base.is_jmp = DISAS_EXIT_UPDATE; } return true; } static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a) { TCGv dest; const CSRInfo *csr; if (check_plv(ctx)) { return false; } csr = get_csr(a->csr); if (csr == NULL) { /* CSR is undefined: read as 0 */ dest = tcg_constant_tl(0); } else { check_csr_flags(ctx, csr, false); dest = gpr_dst(ctx, a->rd, EXT_NONE); if (csr->readfn) { csr_readfn(dest, cpu_env); } else { tcg_gen_ld_tl(dest, cpu_env, csr->offset); } } gen_set_gpr(a->rd, dest, EXT_NONE); return true; } static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a) { TCGv dest, src1; const CSRInfo *csr; if (check_plv(ctx)) { return false; } csr = get_csr_info(a->csr); if (csr == NULL) { /* CSR is undefined: write ignored, read old value as 0. */ gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE); return true; } if (!check_csr_flags(ctx, csr, true)) { /* CSR is readonly: trap. */ return false; } src1 = gpr_src(ctx, a->rd, EXT_NONE); if (csr->writefn) { dest = gpr_dst(ctx, a->rd, EXT_NONE); csr->writefn(dest, cpu_env, src1); } else { dest = temp_new(ctx); tcg_gen_ld_tl(dest, cpu_env, csr->offset); tcg_gen_st_tl(src1, cpu_env, csr->offset); } gen_set_gpr(a->rd, dest, EXT_NONE); return true; } static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a) { TCGv src1, mask, oldv, newv, temp; const CSRInfo *csr; if (check_plv(ctx)) { return false; } csr = get_csr_info(a->csr); if (csr == NULL) { /* CSR is undefined: write ignored, read old value as 0. */ gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE); return true; } if (!check_csr_flags(ctx, csr, true)) { /* CSR is readonly: trap. */ return false; } /* So far only readonly csrs have readfn. */ assert(csr->readfn == NULL); src1 = gpr_src(ctx, a->rd, EXT_NONE); mask = gpr_src(ctx, a->rj, EXT_NONE); oldv = tcg_temp_new(); newv = tcg_temp_new(); temp = tcg_temp_new(); tcg_gen_ld_tl(oldv, cpu_env, csr->offset); tcg_gen_and_tl(newv, src1, mask); tcg_gen_andc_tl(temp, oldv, mask); tcg_gen_or_tl(newv, newv, temp); if (csr->writefn) { csr->writefn(oldv, cpu_env, newv); } else { tcg_gen_st_tl(newv, cpu_env, csr->offset); } gen_set_gpr(a->rd, oldv, EXT_NONE); tcg_temp_free(temp); tcg_temp_free(newv); tcg_temp_free(oldv); return true; } and then in loongarch_tr_tb_stop: case DISAS_EXIT_UPDATE: tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next); /* fall through */ case DISAS_EXIT: tcg_gen_exit_tb(NULL, 0); break;
On 2022/4/20 上午1:05, Richard Henderson wrote: > You'd use a store, just like you were already doing in trans_csrwr. > > But here's how I'd improve this. For avoidance of doubt, all of this > would go in trans_priviledged.c.inc -- there's no use of csr_offsets[] > outside of that file. Thanks you very much, I had tested this with bios, it worked well, and I have two questions. 1. CSRFL_IO, how to use it. I don't understand CPUState options 'can_do_Io', 2./* fall through */, this may have warning, should we care about this? Thanks. Xiaojuan > r~ > > ---- > > > typedef void (*GenCSRRead)(TCGv dest, TCGv_ptr env); > typedef void (*GenCSRWrite)(TCGv dest, TCGv_ptr env, TCGv src); > > typedef struct { > int offset; > int flags; > GenCSRRead readfn; > GenCSRWrite writefn; > } CSRInfo; > > enum { > CSRFL_READONLY = (1 << 0), > CSRFL_EXITTB = (1 << 1), > CSRFL_IO = (1 << 2), > }; > > #define CSR_OFF_FUNCS(NAME, FL, RD, WR) \ > [LOONGARCH_CSR_##NAME] = { \ > .offset = offsetof(CPULoongArchState, CSR_##NAME), \ > .flags = FL, .readfn = RD, .writefn = WR \ > } > #define CSR_OFF_FLAGS(NAME, FL) \ > CSR_OFF_FUNCS(NAME, FL, NULL, NULL) > #define CSR_OFF(NAME) \ > CSR_OFF_FLAGS(NAME, 0) > > static const CSRInfo csr_info[] = { > CSR_OFF(CRMD), > CSR_OFF_FLAGS(CPUID, CSRFL_READONLY), > CSR_OFF_FUNCS(TCFG, CSRFL_IO, NULL, gen_helper_csrwr_tcfg), > CSR_OFF_FUNCS(TVAL, CSRFL_READONLY | CSRFL_IO, > gen_helper_csrrd_tval, NULL), > CSR_OFF_FUNCS(TICLR, CSRFL_IO, NULL, gen_helper_csrwr_ticlr), > CSR_OFF_FUNCS(ESTAT, CSRFL_EXITTB, NULL, gen_helper_csrwr_estat), > ... > }; > > static const CSRInfo *get_csr(unsigned csr_num) > { > const CSRInfo *csr; > if (csr_num < ARRAY_SIZE(csr_info)) { > return NULL; > } > csr = &csr_info[csr_num]; > if (csr->offset == 0) { > return NULL; /* undefined */ > } > return csr; > } > > static bool check_csr_flags(DisasContext *ctx, const CSRInfo *csr, > bool write) > { > if ((info->flags & CSRFL_READONLY) && write) { > return false; > } > if ((info->flags & CSRFL_IO) && > (tb_cflags(ctx->base.tb) & CF_USE_ICOUNT)) { > gen_io_start(); > ctx->base.is_jmp = DISAS_EXIT_UPDATE; > } else if ((info->flags & CSRFL_EXITTB) && write) { > ctx->base.is_jmp = DISAS_EXIT_UPDATE; > } > return true; > } > > static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a) > { > TCGv dest; > const CSRInfo *csr; > > if (check_plv(ctx)) { > return false; > } > csr = get_csr(a->csr); > if (csr == NULL) { > /* CSR is undefined: read as 0 */ > dest = tcg_constant_tl(0); > } else { > check_csr_flags(ctx, csr, false); > dest = gpr_dst(ctx, a->rd, EXT_NONE); > if (csr->readfn) { > csr_readfn(dest, cpu_env); > } else { > tcg_gen_ld_tl(dest, cpu_env, csr->offset); > } > } > gen_set_gpr(a->rd, dest, EXT_NONE); > return true; > } > > static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a) > { > TCGv dest, src1; > const CSRInfo *csr; > > if (check_plv(ctx)) { > return false; > } > csr = get_csr_info(a->csr); > if (csr == NULL) { > /* CSR is undefined: write ignored, read old value as 0. */ > gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE); > return true; > } > if (!check_csr_flags(ctx, csr, true)) { > /* CSR is readonly: trap. */ > return false; > } > src1 = gpr_src(ctx, a->rd, EXT_NONE); > if (csr->writefn) { > dest = gpr_dst(ctx, a->rd, EXT_NONE); > csr->writefn(dest, cpu_env, src1); > } else { > dest = temp_new(ctx); > tcg_gen_ld_tl(dest, cpu_env, csr->offset); > tcg_gen_st_tl(src1, cpu_env, csr->offset); > } > gen_set_gpr(a->rd, dest, EXT_NONE); > return true; > } > > static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a) > { > TCGv src1, mask, oldv, newv, temp; > const CSRInfo *csr; > > if (check_plv(ctx)) { > return false; > } > csr = get_csr_info(a->csr); > if (csr == NULL) { > /* CSR is undefined: write ignored, read old value as 0. */ > gen_set_gpr(a->rd, tcg_constant_tl(0), EXT_NONE); > return true; > } > if (!check_csr_flags(ctx, csr, true)) { > /* CSR is readonly: trap. */ > return false; > } > > /* So far only readonly csrs have readfn. */ > assert(csr->readfn == NULL); > > src1 = gpr_src(ctx, a->rd, EXT_NONE); > mask = gpr_src(ctx, a->rj, EXT_NONE); > oldv = tcg_temp_new(); > newv = tcg_temp_new(); > temp = tcg_temp_new(); > > tcg_gen_ld_tl(oldv, cpu_env, csr->offset); > tcg_gen_and_tl(newv, src1, mask); > tcg_gen_andc_tl(temp, oldv, mask); > tcg_gen_or_tl(newv, newv, temp); > > if (csr->writefn) { > csr->writefn(oldv, cpu_env, newv); > } else { > tcg_gen_st_tl(newv, cpu_env, csr->offset); > } > gen_set_gpr(a->rd, oldv, EXT_NONE); > > tcg_temp_free(temp); > tcg_temp_free(newv); > tcg_temp_free(oldv); > return true; > } > > and then in loongarch_tr_tb_stop: > > case DISAS_EXIT_UPDATE: > tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next); > > /* fall through */ > case DISAS_EXIT: > > tcg_gen_exit_tb(NULL, 0); > > break;
On 4/21/22 02:15, yangxiaojuan wrote: > > On 2022/4/20 上午1:05, Richard Henderson wrote: >> You'd use a store, just like you were already doing in trans_csrwr. >> >> But here's how I'd improve this. For avoidance of doubt, all of this would go in >> trans_priviledged.c.inc -- there's no use of csr_offsets[] outside of that file. > > Thanks you very much, I had tested this with bios, it worked well, and I have two > questions. > > 1. CSRFL_IO, how to use it. I don't understand CPUState options 'can_do_Io', Whenever a cpu touches a device, like a timer or clock, must have io flag set. Missing this flag should result in assertion failures when running with -icount. > 2./* fall through */, this may have warning, should we care about this? It should not have a warning, as the comment itself should suppress that. For more complex cases we also have QEMU_FALLTHROUGH. r~
diff --git a/target/loongarch/cpu-csr.h b/target/loongarch/cpu-csr.h index 5c89605d1a..7a96ec7b6a 100644 --- a/target/loongarch/cpu-csr.h +++ b/target/loongarch/cpu-csr.h @@ -198,4 +198,6 @@ FIELD(CSR_DBG, ECODE, 16, 6) #define LOONGARCH_CSR_DERA 0x501 /* Debug era */ #define LOONGARCH_CSR_DSAVE 0x502 /* Debug save */ +int cpu_csr_offset(unsigned csr_num); + #endif /* LOONGARCH_CPU_CSR_H */ diff --git a/target/loongarch/csr_helper.c b/target/loongarch/csr_helper.c new file mode 100644 index 0000000000..0884e85cb0 --- /dev/null +++ b/target/loongarch/csr_helper.c @@ -0,0 +1,186 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * LoongArch emulation helpers for CSRs + * + * Copyright (c) 2021 Loongson Technology Corporation Limited + */ + +#include "qemu/osdep.h" +#include "qemu/main-loop.h" +#include "cpu.h" +#include "internals.h" +#include "qemu/host-utils.h" +#include "exec/helper-proto.h" +#include "exec/exec-all.h" +#include "exec/cpu_ldst.h" +#include "hw/irq.h" +#include "cpu-csr.h" +#include "hw/loongarch/loongarch.h" +#include "tcg/tcg-ldst.h" + +#define CSR_OFF(X) \ + [LOONGARCH_CSR_##X] = offsetof(CPULoongArchState, CSR_##X) +#define CSR_OFF_ARRAY(X, N) \ + [LOONGARCH_CSR_##X(N)] = offsetof(CPULoongArchState, CSR_##X[N]) + +static const uint64_t csr_offsets[] = { + CSR_OFF(CRMD), + CSR_OFF(PRMD), + CSR_OFF(EUEN), + CSR_OFF(MISC), + CSR_OFF(ECFG), + CSR_OFF(ESTAT), + CSR_OFF(ERA), + CSR_OFF(BADV), + CSR_OFF(BADI), + CSR_OFF(EENTRY), + CSR_OFF(TLBIDX), + CSR_OFF(TLBEHI), + CSR_OFF(TLBELO0), + CSR_OFF(TLBELO1), + CSR_OFF(ASID), + CSR_OFF(PGDL), + CSR_OFF(PGDH), + CSR_OFF(PGD), + CSR_OFF(PWCL), + CSR_OFF(PWCH), + CSR_OFF(STLBPS), + CSR_OFF(RVACFG), + [LOONGARCH_CSR_CPUID] = offsetof(CPUState, cpu_index) + - offsetof(ArchCPU, env), + CSR_OFF(PRCFG1), + CSR_OFF(PRCFG2), + CSR_OFF(PRCFG3), + CSR_OFF_ARRAY(SAVE, 0), + CSR_OFF_ARRAY(SAVE, 1), + CSR_OFF_ARRAY(SAVE, 2), + CSR_OFF_ARRAY(SAVE, 3), + CSR_OFF_ARRAY(SAVE, 4), + CSR_OFF_ARRAY(SAVE, 5), + CSR_OFF_ARRAY(SAVE, 6), + CSR_OFF_ARRAY(SAVE, 7), + CSR_OFF_ARRAY(SAVE, 8), + CSR_OFF_ARRAY(SAVE, 9), + CSR_OFF_ARRAY(SAVE, 10), + CSR_OFF_ARRAY(SAVE, 11), + CSR_OFF_ARRAY(SAVE, 12), + CSR_OFF_ARRAY(SAVE, 13), + CSR_OFF_ARRAY(SAVE, 14), + CSR_OFF_ARRAY(SAVE, 15), + CSR_OFF(TID), + CSR_OFF(TCFG), + CSR_OFF(TVAL), + CSR_OFF(CNTC), + CSR_OFF(TICLR), + CSR_OFF(LLBCTL), + CSR_OFF(IMPCTL1), + CSR_OFF(IMPCTL2), + CSR_OFF(TLBRENTRY), + CSR_OFF(TLBRBADV), + CSR_OFF(TLBRERA), + CSR_OFF(TLBRSAVE), + CSR_OFF(TLBRELO0), + CSR_OFF(TLBRELO1), + CSR_OFF(TLBREHI), + CSR_OFF(TLBRPRMD), + CSR_OFF(MERRCTL), + CSR_OFF(MERRINFO1), + CSR_OFF(MERRINFO2), + CSR_OFF(MERRENTRY), + CSR_OFF(MERRERA), + CSR_OFF(MERRSAVE), + CSR_OFF(CTAG), + CSR_OFF_ARRAY(DMW, 0), + CSR_OFF_ARRAY(DMW, 1), + CSR_OFF_ARRAY(DMW, 2), + CSR_OFF_ARRAY(DMW, 3), + CSR_OFF(DBG), + CSR_OFF(DERA), + CSR_OFF(DSAVE), +}; + +int cpu_csr_offset(unsigned csr_num) +{ + if (csr_num < ARRAY_SIZE(csr_offsets)) { + return csr_offsets[csr_num]; + } + return 0; +} + +target_ulong helper_csrrd_pgd(CPULoongArchState *env) +{ + int64_t v; + + if (env->CSR_TLBRERA & 0x1) { + v = env->CSR_TLBRBADV; + } else { + v = env->CSR_BADV; + } + + if ((v >> 63) & 0x1) { + v = env->CSR_PGDH; + } else { + v = env->CSR_PGDL; + } + + return v; +} + +target_ulong helper_csrrd_tval(CPULoongArchState *env) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(env_cpu(env)); + + return cpu_loongarch_get_constant_timer_ticks(cpu); +} + +target_ulong helper_csrwr_estat(CPULoongArchState *env, target_ulong val) +{ + int64_t old_v = env->CSR_ESTAT; + + /* Only IS[1:0] can be written */ + env->CSR_ESTAT = FIELD_DP64(env->CSR_ESTAT, CSR_ESTAT, IS, val & 0x3); + + return old_v; +} + +target_ulong helper_csrwr_asid(CPULoongArchState *env, target_ulong val) +{ + int64_t old_v = env->CSR_ASID; + + /* Only ASID filed of CSR_ASID can be written */ + env->CSR_ASID = FIELD_DP64(env->CSR_ASID, CSR_ASID, ASID, + val & R_CSR_ASID_ASID_MASK); + if (old_v != val) { + tlb_flush(env_cpu(env)); + } + return old_v; +} + +target_ulong helper_csrwr_tcfg(CPULoongArchState *env, target_ulong val) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(env_cpu(env)); + int64_t old_v = env->CSR_TCFG; + + cpu_loongarch_store_constant_timer_config(cpu, val); + + return old_v; +} + +target_ulong helper_csrwr_ticlr(CPULoongArchState *env, target_ulong val) +{ + LoongArchCPU *cpu = LOONGARCH_CPU(env_cpu(env)); + int64_t old_v = 0; + + if (val & 0x1) { + loongarch_cpu_set_irq(cpu, IRQ_TIMER, 0); + } + return old_v; +} + +void helper_csr_update(CPULoongArchState *env, target_ulong new_val, + target_ulong csr_offset) +{ + uint64_t *csr = (void *)env + csr_offset; + + *csr = new_val; +} diff --git a/target/loongarch/disas.c b/target/loongarch/disas.c index 9454ebb8e9..db0e0c73fe 100644 --- a/target/loongarch/disas.c +++ b/target/loongarch/disas.c @@ -205,6 +205,18 @@ static void output_rr_offs(DisasContext *ctx, arg_rr_offs *a, a->rd, a->offs, ctx->pc + a->offs); } +static void output_r_csr(DisasContext *ctx, arg_r_csr *a, + const char *mnemonic) +{ + output(ctx, mnemonic, "r%d, %d", a->rd, a->csr); +} + +static void output_rr_csr(DisasContext *ctx, arg_rr_csr *a, + const char *mnemonic) +{ + output(ctx, mnemonic, "r%d, r%d, %d", a->rd, a->rj, a->csr); +} + #define INSN(insn, type) \ static bool trans_##insn(DisasContext *ctx, arg_##type * a) \ { \ @@ -514,6 +526,9 @@ INSN(blt, rr_offs) INSN(bge, rr_offs) INSN(bltu, rr_offs) INSN(bgeu, rr_offs) +INSN(csrrd, r_csr) +INSN(csrwr, r_csr) +INSN(csrxchg, rr_csr) #define output_fcmp(C, PREFIX, SUFFIX) \ { \ diff --git a/target/loongarch/helper.h b/target/loongarch/helper.h index da1a2bced7..bd2cb3a9c5 100644 --- a/target/loongarch/helper.h +++ b/target/loongarch/helper.h @@ -92,3 +92,12 @@ DEF_HELPER_2(frint_s, i64, env, i64) DEF_HELPER_2(frint_d, i64, env, i64) DEF_HELPER_FLAGS_2(set_rounding_mode, TCG_CALL_NO_RWG, void, env, i32) + +/* CSRs helper */ +DEF_HELPER_1(csrrd_pgd, i64, env) +DEF_HELPER_1(csrrd_tval, i64, env) +DEF_HELPER_2(csrwr_estat, i64, env, tl) +DEF_HELPER_2(csrwr_asid, i64, env, tl) +DEF_HELPER_2(csrwr_tcfg, i64, env, tl) +DEF_HELPER_2(csrwr_ticlr, i64, env, tl) +DEF_HELPER_3(csr_update, void, env, tl, i64) diff --git a/target/loongarch/insn_trans/trans_privileged.c.inc b/target/loongarch/insn_trans/trans_privileged.c.inc new file mode 100644 index 0000000000..ba111779c2 --- /dev/null +++ b/target/loongarch/insn_trans/trans_privileged.c.inc @@ -0,0 +1,177 @@ +/* SPDX-License-Identifier: GPL-2.0-or-later */ +/* + * Copyright (c) 2021 Loongson Technology Corporation Limited + * + * LoongArch translation routines for the privileged instructions. + */ + +#include "cpu-csr.h" + +static void gen_disas_exit(DisasContext *ctx) +{ + tcg_gen_movi_tl(cpu_pc, ctx->base.pc_next + 4); + ctx->base.is_jmp = DISAS_EXIT; +} + +static bool check_plv(DisasContext *ctx) +{ + if (ctx->base.tb->flags == MMU_USER_IDX) { + generate_exception(ctx, EXCCODE_IPE); + return true; + } + return false; +} + +static bool ro_csr(int csr_num) +{ + /* + * For now qemu does not support any features of the MISC + * bits yet treat as a RO CSR. + */ + if ((csr_num == LOONGARCH_CSR_BADI) || + (csr_num == LOONGARCH_CSR_CPUID) || + (csr_num == LOONGARCH_CSR_PRCFG1) || + (csr_num == LOONGARCH_CSR_PRCFG2) || + (csr_num == LOONGARCH_CSR_PRCFG3) || + (csr_num == LOONGARCH_CSR_PGD) || + (csr_num == LOONGARCH_CSR_TVAL) || + (csr_num == LOONGARCH_CSR_MISC)) { + return true; + } + + return false; +} + +static bool trans_csrrd(DisasContext *ctx, arg_csrrd *a) +{ + TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE); + + if (check_plv(ctx)) { + return false; + } + + unsigned csr_offset = cpu_csr_offset(a->csr); + if (csr_offset == 0) { + /* CSR is undefined: read as 0 */ + dest = tcg_constant_tl(0); + return true; + } + + switch (a->csr) { + case LOONGARCH_CSR_PGD: + gen_helper_csrrd_pgd(dest, cpu_env); + break; + case LOONGARCH_CSR_TVAL: + gen_helper_csrrd_tval(dest, cpu_env); + break; + default: + tcg_gen_ld_tl(dest, cpu_env, csr_offset); + } + return true; +} + +static bool trans_csrwr(DisasContext *ctx, arg_csrwr *a) +{ + TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE); + TCGv src1 = gpr_src(ctx, a->rd, EXT_NONE); + + if (check_plv(ctx) || ro_csr(a->csr)) { + return false; + } + + unsigned csr_offset = cpu_csr_offset(a->csr); + if (csr_offset == 0) { + /* CSR is undefined: write ignored. */ + return true; + } + + switch (a->csr) { + case LOONGARCH_CSR_ESTAT: + gen_helper_csrwr_estat(dest, cpu_env, src1); + break; + case LOONGARCH_CSR_ASID: + gen_helper_csrwr_asid(dest, cpu_env, src1); + gen_disas_exit(ctx); + break; + case LOONGARCH_CSR_TCFG: + gen_helper_csrwr_tcfg(dest, cpu_env, src1); + break; + case LOONGARCH_CSR_TICLR: + gen_helper_csrwr_ticlr(dest, cpu_env, src1); + break; + default: + { + TCGv temp = tcg_temp_new(); + tcg_gen_ld_tl(temp, cpu_env, csr_offset); + tcg_gen_st_tl(src1, cpu_env, csr_offset); + tcg_gen_mov_tl(dest, temp); + tcg_temp_free(temp); + + /* Cpu state may be changed, need exit */ + if ((a->csr == LOONGARCH_CSR_CRMD) || + (a->csr == LOONGARCH_CSR_EUEN)) { + gen_disas_exit(ctx); + } + } + } + return true; +} + +static bool trans_csrxchg(DisasContext *ctx, arg_csrxchg *a) +{ + TCGv dest = gpr_dst(ctx, a->rd, EXT_NONE); + TCGv src1 = gpr_src(ctx, a->rd, EXT_NONE); + TCGv mask = gpr_src(ctx, a->rj, EXT_NONE); + + if (check_plv(ctx) || ro_csr(a->csr)) { + return false; + } + + unsigned csr_offset = cpu_csr_offset(a->csr); + if (csr_offset == 0) { + /* CSR is undefined: write ignored */ + return true; + } + + TCGv old_val = tcg_temp_new(); + TCGv new_val = tcg_temp_new(); + TCGv t0 = tcg_temp_new(); + TCGv t1 = tcg_temp_new(); + + tcg_gen_ld_tl(old_val, cpu_env, csr_offset); + tcg_gen_and_tl(t0, src1, mask); + tcg_gen_not_tl(t1, mask); + tcg_gen_and_tl(t1, old_val, t1); + tcg_gen_or_tl(new_val, t1, t0); + + switch (a->csr) { + case LOONGARCH_CSR_ESTAT: + gen_helper_csrwr_estat(dest, cpu_env, new_val); + break; + case LOONGARCH_CSR_ASID: + gen_helper_csrwr_asid(dest, cpu_env, new_val); + break; + case LOONGARCH_CSR_TCFG: + gen_helper_csrwr_tcfg(dest, cpu_env, new_val); + break; + case LOONGARCH_CSR_TICLR: + gen_helper_csrwr_ticlr(dest, cpu_env, new_val); + break; + default: + tcg_gen_mov_tl(dest, old_val); + } + + gen_helper_csr_update(cpu_env, new_val, tcg_constant_tl(csr_offset)); + + if ((a->csr == LOONGARCH_CSR_ASID) || (a->csr == LOONGARCH_CSR_CRMD) || + (a->csr == LOONGARCH_CSR_EUEN)) { + gen_disas_exit(ctx); + } + + tcg_temp_free(t1); + tcg_temp_free(t0); + tcg_temp_free(new_val); + tcg_temp_free(old_val); + + return true; +} diff --git a/target/loongarch/insns.decode b/target/loongarch/insns.decode index 9b293dfdf9..43005ca283 100644 --- a/target/loongarch/insns.decode +++ b/target/loongarch/insns.decode @@ -45,6 +45,8 @@ &c_offs cj offs &offs offs &rr_offs rj rd offs +&r_csr rd csr +&rr_csr rd rj csr # # Formats @@ -85,6 +87,8 @@ @c_offs21 .... .. ................ .. cj:3 ..... &c_offs offs=%offs21 @offs26 .... .. .......................... &offs offs=%offs26 @rr_offs16 .... .. ................ rj:5 rd:5 &rr_offs offs=%offs16 +@r_csr .... .... csr:14 ..... rd:5 &r_csr +@rr_csr .... .... csr:14 rj:5 rd:5 &rr_csr # # Fixed point arithmetic operation instruction @@ -437,3 +441,12 @@ blt 0110 00 ................ ..... ..... @rr_offs16 bge 0110 01 ................ ..... ..... @rr_offs16 bltu 0110 10 ................ ..... ..... @rr_offs16 bgeu 0110 11 ................ ..... ..... @rr_offs16 + +# +# Core instructions +# +{ + csrrd 0000 0100 .............. 00000 ..... @r_csr + csrwr 0000 0100 .............. 00001 ..... @r_csr + csrxchg 0000 0100 .............. ..... ..... @rr_csr +} diff --git a/target/loongarch/meson.build b/target/loongarch/meson.build index 04e15ba1e3..d11829a6cc 100644 --- a/target/loongarch/meson.build +++ b/target/loongarch/meson.build @@ -19,6 +19,7 @@ loongarch_softmmu_ss.add(files( 'machine.c', 'tlb_helper.c', 'constant_timer.c', + 'csr_helper.c', )) loongarch_ss.add_all(when: 'CONFIG_TCG', if_true: [loongarch_tcg_ss]) diff --git a/target/loongarch/translate.c b/target/loongarch/translate.c index f39ebe7967..c1cac2f006 100644 --- a/target/loongarch/translate.c +++ b/target/loongarch/translate.c @@ -26,6 +26,7 @@ TCGv_i32 cpu_fcsr0; TCGv_i64 cpu_fpr[32]; #define DISAS_STOP DISAS_TARGET_0 +#define DISAS_EXIT DISAS_TARGET_1 static inline int plus_1(DisasContext *ctx, int x) { @@ -172,6 +173,7 @@ static void gen_set_gpr(int reg_num, TCGv t, DisasExtend dst_ext) #include "insn_trans/trans_fmov.c.inc" #include "insn_trans/trans_fmemory.c.inc" #include "insn_trans/trans_branch.c.inc" +#include "insn_trans/trans_privileged.c.inc" static void loongarch_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) { @@ -209,6 +211,9 @@ static void loongarch_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs) break; case DISAS_NORETURN: break; + case DISAS_EXIT: + tcg_gen_exit_tb(NULL, 0); + break; default: g_assert_not_reached(); }