| /* |
| * Altera Nios II emulation for qemu: main translation routines. |
| * |
| * Copyright (C) 2016 Marek Vasut <marex@denx.de> |
| * Copyright (C) 2012 Chris Wulff <crwulff@gmail.com> |
| * Copyright (C) 2010 Tobias Klauser <tklauser@distanz.ch> |
| * (Portions of this file that were originally from nios2sim-ng.) |
| * |
| * This library is free software; you can redistribute it and/or |
| * modify it under the terms of the GNU Lesser General Public |
| * License as published by the Free Software Foundation; either |
| * version 2.1 of the License, or (at your option) any later version. |
| * |
| * This library is distributed in the hope that it will be useful, |
| * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| * Lesser General Public License for more details. |
| * |
| * You should have received a copy of the GNU Lesser General Public |
| * License along with this library; if not, see |
| * <http://www.gnu.org/licenses/lgpl-2.1.html> |
| */ |
| |
| #include "qemu/osdep.h" |
| #include "cpu.h" |
| #include "tcg/tcg-op.h" |
| #include "exec/exec-all.h" |
| #include "disas/disas.h" |
| #include "exec/helper-proto.h" |
| #include "exec/helper-gen.h" |
| #include "exec/log.h" |
| #include "exec/translator.h" |
| #include "qemu/qemu-print.h" |
| #include "semihosting/semihost.h" |
| |
| #define HELPER_H "helper.h" |
| #include "exec/helper-info.c.inc" |
| #undef HELPER_H |
| |
| |
| /* is_jmp field values */ |
| #define DISAS_UPDATE DISAS_TARGET_1 /* cpu state was modified dynamically */ |
| |
| #define INSTRUCTION_FLG(func, flags) { (func), (flags) } |
| #define INSTRUCTION(func) \ |
| INSTRUCTION_FLG(func, 0) |
| #define INSTRUCTION_NOP() \ |
| INSTRUCTION_FLG(nop, 0) |
| #define INSTRUCTION_UNIMPLEMENTED() \ |
| INSTRUCTION_FLG(gen_excp, EXCP_UNIMPL) |
| #define INSTRUCTION_ILLEGAL() \ |
| INSTRUCTION_FLG(gen_excp, EXCP_ILLEGAL) |
| |
| /* Special R-Type instruction opcode */ |
| #define INSN_R_TYPE 0x3A |
| |
| /* I-Type instruction parsing */ |
| typedef struct { |
| uint8_t op; |
| union { |
| uint16_t u; |
| int16_t s; |
| } imm16; |
| uint8_t b; |
| uint8_t a; |
| } InstrIType; |
| |
| #define I_TYPE(instr, code) \ |
| InstrIType (instr) = { \ |
| .op = extract32((code), 0, 6), \ |
| .imm16.u = extract32((code), 6, 16), \ |
| .b = extract32((code), 22, 5), \ |
| .a = extract32((code), 27, 5), \ |
| } |
| |
| typedef target_ulong ImmFromIType(const InstrIType *); |
| |
| static target_ulong imm_unsigned(const InstrIType *i) |
| { |
| return i->imm16.u; |
| } |
| |
| static target_ulong imm_signed(const InstrIType *i) |
| { |
| return i->imm16.s; |
| } |
| |
| static target_ulong imm_shifted(const InstrIType *i) |
| { |
| return i->imm16.u << 16; |
| } |
| |
| /* R-Type instruction parsing */ |
| typedef struct { |
| uint8_t op; |
| uint8_t imm5; |
| uint8_t opx; |
| uint8_t c; |
| uint8_t b; |
| uint8_t a; |
| } InstrRType; |
| |
| #define R_TYPE(instr, code) \ |
| InstrRType (instr) = { \ |
| .op = extract32((code), 0, 6), \ |
| .imm5 = extract32((code), 6, 5), \ |
| .opx = extract32((code), 11, 6), \ |
| .c = extract32((code), 17, 5), \ |
| .b = extract32((code), 22, 5), \ |
| .a = extract32((code), 27, 5), \ |
| } |
| |
| /* J-Type instruction parsing */ |
| typedef struct { |
| uint8_t op; |
| uint32_t imm26; |
| } InstrJType; |
| |
| #define J_TYPE(instr, code) \ |
| InstrJType (instr) = { \ |
| .op = extract32((code), 0, 6), \ |
| .imm26 = extract32((code), 6, 26), \ |
| } |
| |
| typedef void GenFn2i(TCGv, TCGv, target_long); |
| typedef void GenFn3(TCGv, TCGv, TCGv); |
| typedef void GenFn4(TCGv, TCGv, TCGv, TCGv); |
| |
| typedef struct DisasContext { |
| DisasContextBase base; |
| target_ulong pc; |
| int mem_idx; |
| uint32_t tb_flags; |
| TCGv sink; |
| const ControlRegState *cr_state; |
| bool eic_present; |
| } DisasContext; |
| |
| static TCGv cpu_R[NUM_GP_REGS]; |
| static TCGv cpu_pc; |
| #ifndef CONFIG_USER_ONLY |
| static TCGv cpu_crs_R[NUM_GP_REGS]; |
| #endif |
| |
| typedef struct Nios2Instruction { |
| void (*handler)(DisasContext *dc, uint32_t code, uint32_t flags); |
| uint32_t flags; |
| } Nios2Instruction; |
| |
| static uint8_t get_opcode(uint32_t code) |
| { |
| I_TYPE(instr, code); |
| return instr.op; |
| } |
| |
| static uint8_t get_opxcode(uint32_t code) |
| { |
| R_TYPE(instr, code); |
| return instr.opx; |
| } |
| |
| static TCGv load_gpr(DisasContext *dc, unsigned reg) |
| { |
| assert(reg < NUM_GP_REGS); |
| |
| /* |
| * With shadow register sets, register r0 does not necessarily contain 0, |
| * but it is overwhelmingly likely that it does -- software is supposed |
| * to have set r0 to 0 in every shadow register set before use. |
| */ |
| if (unlikely(reg == R_ZERO) && FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0)) { |
| return tcg_constant_tl(0); |
| } |
| if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) { |
| return cpu_R[reg]; |
| } |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| return cpu_crs_R[reg]; |
| #endif |
| } |
| |
| static TCGv dest_gpr(DisasContext *dc, unsigned reg) |
| { |
| assert(reg < NUM_GP_REGS); |
| |
| /* |
| * The spec for shadow register sets isn't clear, but we assume that |
| * writes to r0 are discarded regardless of CRS. |
| */ |
| if (unlikely(reg == R_ZERO)) { |
| if (dc->sink == NULL) { |
| dc->sink = tcg_temp_new(); |
| } |
| return dc->sink; |
| } |
| if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) { |
| return cpu_R[reg]; |
| } |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| return cpu_crs_R[reg]; |
| #endif |
| } |
| |
| static void t_gen_helper_raise_exception(DisasContext *dc, uint32_t index) |
| { |
| /* Note that PC is advanced for all hardware exceptions. */ |
| tcg_gen_movi_tl(cpu_pc, dc->base.pc_next); |
| gen_helper_raise_exception(cpu_env, tcg_constant_i32(index)); |
| dc->base.is_jmp = DISAS_NORETURN; |
| } |
| |
| static void gen_goto_tb(DisasContext *dc, int n, uint32_t dest) |
| { |
| const TranslationBlock *tb = dc->base.tb; |
| |
| if (translator_use_goto_tb(&dc->base, dest)) { |
| tcg_gen_goto_tb(n); |
| tcg_gen_movi_tl(cpu_pc, dest); |
| tcg_gen_exit_tb(tb, n); |
| } else { |
| tcg_gen_movi_tl(cpu_pc, dest); |
| tcg_gen_lookup_and_goto_ptr(); |
| } |
| dc->base.is_jmp = DISAS_NORETURN; |
| } |
| |
| static void gen_jumpr(DisasContext *dc, int regno, bool is_call) |
| { |
| TCGLabel *l = gen_new_label(); |
| TCGv test = tcg_temp_new(); |
| TCGv dest = load_gpr(dc, regno); |
| |
| tcg_gen_andi_tl(test, dest, 3); |
| tcg_gen_brcondi_tl(TCG_COND_NE, test, 0, l); |
| |
| tcg_gen_mov_tl(cpu_pc, dest); |
| if (is_call) { |
| tcg_gen_movi_tl(dest_gpr(dc, R_RA), dc->base.pc_next); |
| } |
| tcg_gen_lookup_and_goto_ptr(); |
| |
| gen_set_label(l); |
| tcg_gen_st_tl(dest, cpu_env, offsetof(CPUNios2State, ctrl[CR_BADADDR])); |
| t_gen_helper_raise_exception(dc, EXCP_UNALIGND); |
| |
| dc->base.is_jmp = DISAS_NORETURN; |
| } |
| |
| static void gen_excp(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| t_gen_helper_raise_exception(dc, flags); |
| } |
| |
| static bool gen_check_supervisor(DisasContext *dc) |
| { |
| if (FIELD_EX32(dc->tb_flags, TBFLAGS, U)) { |
| /* CPU in user mode, privileged instruction called, stop. */ |
| t_gen_helper_raise_exception(dc, EXCP_SUPERI); |
| return false; |
| } |
| return true; |
| } |
| |
| /* |
| * Used as a placeholder for all instructions which do not have |
| * an effect on the simulator (e.g. flush, sync) |
| */ |
| static void nop(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| /* Nothing to do here */ |
| } |
| |
| /* |
| * J-Type instructions |
| */ |
| static void jmpi(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| J_TYPE(instr, code); |
| gen_goto_tb(dc, 0, (dc->pc & 0xF0000000) | (instr.imm26 << 2)); |
| } |
| |
| static void call(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| tcg_gen_movi_tl(dest_gpr(dc, R_RA), dc->base.pc_next); |
| jmpi(dc, code, flags); |
| } |
| |
| /* |
| * I-Type instructions |
| */ |
| /* Load instructions */ |
| static void gen_ldx(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| I_TYPE(instr, code); |
| |
| TCGv addr = tcg_temp_new(); |
| TCGv data = dest_gpr(dc, instr.b); |
| |
| tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s); |
| #ifdef CONFIG_USER_ONLY |
| flags |= MO_UNALN; |
| #else |
| flags |= MO_ALIGN; |
| #endif |
| tcg_gen_qemu_ld_tl(data, addr, dc->mem_idx, flags); |
| } |
| |
| /* Store instructions */ |
| static void gen_stx(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| I_TYPE(instr, code); |
| TCGv val = load_gpr(dc, instr.b); |
| |
| TCGv addr = tcg_temp_new(); |
| tcg_gen_addi_tl(addr, load_gpr(dc, instr.a), instr.imm16.s); |
| #ifdef CONFIG_USER_ONLY |
| flags |= MO_UNALN; |
| #else |
| flags |= MO_ALIGN; |
| #endif |
| tcg_gen_qemu_st_tl(val, addr, dc->mem_idx, flags); |
| } |
| |
| /* Branch instructions */ |
| static void br(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| I_TYPE(instr, code); |
| |
| gen_goto_tb(dc, 0, dc->base.pc_next + (instr.imm16.s & -4)); |
| } |
| |
| static void gen_bxx(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| I_TYPE(instr, code); |
| |
| TCGLabel *l1 = gen_new_label(); |
| tcg_gen_brcond_tl(flags, load_gpr(dc, instr.a), load_gpr(dc, instr.b), l1); |
| gen_goto_tb(dc, 0, dc->base.pc_next); |
| gen_set_label(l1); |
| gen_goto_tb(dc, 1, dc->base.pc_next + (instr.imm16.s & -4)); |
| } |
| |
| /* Comparison instructions */ |
| static void do_i_cmpxx(DisasContext *dc, uint32_t insn, |
| TCGCond cond, ImmFromIType *imm) |
| { |
| I_TYPE(instr, insn); |
| tcg_gen_setcondi_tl(cond, dest_gpr(dc, instr.b), |
| load_gpr(dc, instr.a), imm(&instr)); |
| } |
| |
| #define gen_i_cmpxx(fname, imm) \ |
| static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \ |
| { do_i_cmpxx(dc, code, flags, imm); } |
| |
| gen_i_cmpxx(gen_cmpxxsi, imm_signed) |
| gen_i_cmpxx(gen_cmpxxui, imm_unsigned) |
| |
| /* Math/logic instructions */ |
| static void do_i_math_logic(DisasContext *dc, uint32_t insn, |
| GenFn2i *fn, ImmFromIType *imm, |
| bool x_op_0_eq_x) |
| { |
| I_TYPE(instr, insn); |
| target_ulong val; |
| |
| if (unlikely(instr.b == R_ZERO)) { |
| /* Store to R_ZERO is ignored -- this catches the canonical NOP. */ |
| return; |
| } |
| |
| val = imm(&instr); |
| |
| if (instr.a == R_ZERO && FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0)) { |
| /* This catches the canonical expansions of movi and movhi. */ |
| tcg_gen_movi_tl(dest_gpr(dc, instr.b), x_op_0_eq_x ? val : 0); |
| } else { |
| fn(dest_gpr(dc, instr.b), load_gpr(dc, instr.a), val); |
| } |
| } |
| |
| #define gen_i_math_logic(fname, insn, x_op_0, imm) \ |
| static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \ |
| { do_i_math_logic(dc, code, tcg_gen_##insn##_tl, imm, x_op_0); } |
| |
| gen_i_math_logic(addi, addi, 1, imm_signed) |
| gen_i_math_logic(muli, muli, 0, imm_signed) |
| |
| gen_i_math_logic(andi, andi, 0, imm_unsigned) |
| gen_i_math_logic(ori, ori, 1, imm_unsigned) |
| gen_i_math_logic(xori, xori, 1, imm_unsigned) |
| |
| gen_i_math_logic(andhi, andi, 0, imm_shifted) |
| gen_i_math_logic(orhi , ori, 1, imm_shifted) |
| gen_i_math_logic(xorhi, xori, 1, imm_shifted) |
| |
| /* rB <- prs.rA + sigma(IMM16) */ |
| static void rdprs(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| if (!dc->eic_present) { |
| t_gen_helper_raise_exception(dc, EXCP_ILLEGAL); |
| return; |
| } |
| if (!gen_check_supervisor(dc)) { |
| return; |
| } |
| |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| I_TYPE(instr, code); |
| TCGv dest = dest_gpr(dc, instr.b); |
| gen_helper_rdprs(dest, cpu_env, tcg_constant_i32(instr.a)); |
| tcg_gen_addi_tl(dest, dest, instr.imm16.s); |
| #endif |
| } |
| |
| /* Prototype only, defined below */ |
| static void handle_r_type_instr(DisasContext *dc, uint32_t code, |
| uint32_t flags); |
| |
| static const Nios2Instruction i_type_instructions[] = { |
| INSTRUCTION(call), /* call */ |
| INSTRUCTION(jmpi), /* jmpi */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_ldx, MO_UB), /* ldbu */ |
| INSTRUCTION(addi), /* addi */ |
| INSTRUCTION_FLG(gen_stx, MO_UB), /* stb */ |
| INSTRUCTION(br), /* br */ |
| INSTRUCTION_FLG(gen_ldx, MO_SB), /* ldb */ |
| INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_GE), /* cmpgei */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_ldx, MO_TEUW), /* ldhu */ |
| INSTRUCTION(andi), /* andi */ |
| INSTRUCTION_FLG(gen_stx, MO_TEUW), /* sth */ |
| INSTRUCTION_FLG(gen_bxx, TCG_COND_GE), /* bge */ |
| INSTRUCTION_FLG(gen_ldx, MO_TESW), /* ldh */ |
| INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_LT), /* cmplti */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_NOP(), /* initda */ |
| INSTRUCTION(ori), /* ori */ |
| INSTRUCTION_FLG(gen_stx, MO_TEUL), /* stw */ |
| INSTRUCTION_FLG(gen_bxx, TCG_COND_LT), /* blt */ |
| INSTRUCTION_FLG(gen_ldx, MO_TEUL), /* ldw */ |
| INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_NE), /* cmpnei */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_NOP(), /* flushda */ |
| INSTRUCTION(xori), /* xori */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_bxx, TCG_COND_NE), /* bne */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_EQ), /* cmpeqi */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_ldx, MO_UB), /* ldbuio */ |
| INSTRUCTION(muli), /* muli */ |
| INSTRUCTION_FLG(gen_stx, MO_UB), /* stbio */ |
| INSTRUCTION_FLG(gen_bxx, TCG_COND_EQ), /* beq */ |
| INSTRUCTION_FLG(gen_ldx, MO_SB), /* ldbio */ |
| INSTRUCTION_FLG(gen_cmpxxui, TCG_COND_GEU), /* cmpgeui */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_ldx, MO_TEUW), /* ldhuio */ |
| INSTRUCTION(andhi), /* andhi */ |
| INSTRUCTION_FLG(gen_stx, MO_TEUW), /* sthio */ |
| INSTRUCTION_FLG(gen_bxx, TCG_COND_GEU), /* bgeu */ |
| INSTRUCTION_FLG(gen_ldx, MO_TESW), /* ldhio */ |
| INSTRUCTION_FLG(gen_cmpxxui, TCG_COND_LTU), /* cmpltui */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_UNIMPLEMENTED(), /* custom */ |
| INSTRUCTION_NOP(), /* initd */ |
| INSTRUCTION(orhi), /* orhi */ |
| INSTRUCTION_FLG(gen_stx, MO_TESL), /* stwio */ |
| INSTRUCTION_FLG(gen_bxx, TCG_COND_LTU), /* bltu */ |
| INSTRUCTION_FLG(gen_ldx, MO_TEUL), /* ldwio */ |
| INSTRUCTION(rdprs), /* rdprs */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(handle_r_type_instr, 0), /* R-Type */ |
| INSTRUCTION_NOP(), /* flushd */ |
| INSTRUCTION(xorhi), /* xorhi */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| }; |
| |
| /* |
| * R-Type instructions |
| */ |
| /* |
| * status <- estatus |
| * PC <- ea |
| */ |
| static void eret(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| if (!gen_check_supervisor(dc)) { |
| return; |
| } |
| |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| if (FIELD_EX32(dc->tb_flags, TBFLAGS, CRS0)) { |
| TCGv tmp = tcg_temp_new(); |
| tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPUNios2State, ctrl[CR_ESTATUS])); |
| gen_helper_eret(cpu_env, tmp, load_gpr(dc, R_EA)); |
| } else { |
| gen_helper_eret(cpu_env, load_gpr(dc, R_SSTATUS), load_gpr(dc, R_EA)); |
| } |
| dc->base.is_jmp = DISAS_NORETURN; |
| #endif |
| } |
| |
| /* PC <- ra */ |
| static void ret(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| gen_jumpr(dc, R_RA, false); |
| } |
| |
| /* |
| * status <- bstatus |
| * PC <- ba |
| */ |
| static void bret(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| if (!gen_check_supervisor(dc)) { |
| return; |
| } |
| |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| TCGv tmp = tcg_temp_new(); |
| tcg_gen_ld_tl(tmp, cpu_env, offsetof(CPUNios2State, ctrl[CR_BSTATUS])); |
| gen_helper_eret(cpu_env, tmp, load_gpr(dc, R_BA)); |
| |
| dc->base.is_jmp = DISAS_NORETURN; |
| #endif |
| } |
| |
| /* PC <- rA */ |
| static void jmp(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| R_TYPE(instr, code); |
| |
| gen_jumpr(dc, instr.a, false); |
| } |
| |
| /* rC <- PC + 4 */ |
| static void nextpc(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| R_TYPE(instr, code); |
| |
| tcg_gen_movi_tl(dest_gpr(dc, instr.c), dc->base.pc_next); |
| } |
| |
| /* |
| * ra <- PC + 4 |
| * PC <- rA |
| */ |
| static void callr(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| R_TYPE(instr, code); |
| |
| gen_jumpr(dc, instr.a, true); |
| } |
| |
| /* rC <- ctlN */ |
| static void rdctl(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| if (!gen_check_supervisor(dc)) { |
| return; |
| } |
| |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| R_TYPE(instr, code); |
| TCGv t1, t2, dest = dest_gpr(dc, instr.c); |
| |
| /* Reserved registers read as zero. */ |
| if (nios2_cr_reserved(&dc->cr_state[instr.imm5])) { |
| tcg_gen_movi_tl(dest, 0); |
| return; |
| } |
| |
| switch (instr.imm5) { |
| case CR_IPENDING: |
| /* |
| * The value of the ipending register is synthetic. |
| * In hw, this is the AND of a set of hardware irq lines |
| * with the ienable register. In qemu, we re-use the space |
| * of CR_IPENDING to store the set of irq lines, and so we |
| * must perform the AND here, and anywhere else we need the |
| * guest value of ipending. |
| */ |
| t1 = tcg_temp_new(); |
| t2 = tcg_temp_new(); |
| tcg_gen_ld_tl(t1, cpu_env, offsetof(CPUNios2State, ctrl[CR_IPENDING])); |
| tcg_gen_ld_tl(t2, cpu_env, offsetof(CPUNios2State, ctrl[CR_IENABLE])); |
| tcg_gen_and_tl(dest, t1, t2); |
| break; |
| default: |
| tcg_gen_ld_tl(dest, cpu_env, |
| offsetof(CPUNios2State, ctrl[instr.imm5])); |
| break; |
| } |
| #endif |
| } |
| |
| /* ctlN <- rA */ |
| static void wrctl(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| if (!gen_check_supervisor(dc)) { |
| return; |
| } |
| |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| R_TYPE(instr, code); |
| TCGv v = load_gpr(dc, instr.a); |
| uint32_t ofs = offsetof(CPUNios2State, ctrl[instr.imm5]); |
| uint32_t wr = dc->cr_state[instr.imm5].writable; |
| uint32_t ro = dc->cr_state[instr.imm5].readonly; |
| |
| /* Skip reserved or readonly registers. */ |
| if (wr == 0) { |
| return; |
| } |
| |
| switch (instr.imm5) { |
| case CR_PTEADDR: |
| gen_helper_mmu_write_pteaddr(cpu_env, v); |
| break; |
| case CR_TLBACC: |
| gen_helper_mmu_write_tlbacc(cpu_env, v); |
| break; |
| case CR_TLBMISC: |
| gen_helper_mmu_write_tlbmisc(cpu_env, v); |
| break; |
| case CR_STATUS: |
| case CR_IENABLE: |
| /* If interrupts were enabled using WRCTL, trigger them. */ |
| dc->base.is_jmp = DISAS_UPDATE; |
| /* fall through */ |
| default: |
| if (wr == -1) { |
| /* The register is entirely writable. */ |
| tcg_gen_st_tl(v, cpu_env, ofs); |
| } else { |
| /* |
| * The register is partially read-only or reserved: |
| * merge the value. |
| */ |
| TCGv n = tcg_temp_new(); |
| |
| tcg_gen_andi_tl(n, v, wr); |
| |
| if (ro != 0) { |
| TCGv o = tcg_temp_new(); |
| tcg_gen_ld_tl(o, cpu_env, ofs); |
| tcg_gen_andi_tl(o, o, ro); |
| tcg_gen_or_tl(n, n, o); |
| } |
| |
| tcg_gen_st_tl(n, cpu_env, ofs); |
| } |
| break; |
| } |
| #endif |
| } |
| |
| /* prs.rC <- rA */ |
| static void wrprs(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| if (!dc->eic_present) { |
| t_gen_helper_raise_exception(dc, EXCP_ILLEGAL); |
| return; |
| } |
| if (!gen_check_supervisor(dc)) { |
| return; |
| } |
| |
| #ifdef CONFIG_USER_ONLY |
| g_assert_not_reached(); |
| #else |
| R_TYPE(instr, code); |
| gen_helper_wrprs(cpu_env, tcg_constant_i32(instr.c), |
| load_gpr(dc, instr.a)); |
| /* |
| * The expected write to PRS[r0] is 0, from CRS[r0]. |
| * If not, and CRS == PRS (which we cannot tell from here), |
| * we may now have a non-zero value in our current r0. |
| * By ending the TB, we re-evaluate tb_flags and find out. |
| */ |
| if (instr.c == 0 |
| && (instr.a != 0 || !FIELD_EX32(dc->tb_flags, TBFLAGS, R0_0))) { |
| dc->base.is_jmp = DISAS_UPDATE; |
| } |
| #endif |
| } |
| |
| /* Comparison instructions */ |
| static void gen_cmpxx(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| R_TYPE(instr, code); |
| tcg_gen_setcond_tl(flags, dest_gpr(dc, instr.c), |
| load_gpr(dc, instr.a), load_gpr(dc, instr.b)); |
| } |
| |
| /* Math/logic instructions */ |
| static void do_ri_math_logic(DisasContext *dc, uint32_t insn, GenFn2i *fn) |
| { |
| R_TYPE(instr, insn); |
| fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), instr.imm5); |
| } |
| |
| static void do_rr_math_logic(DisasContext *dc, uint32_t insn, GenFn3 *fn) |
| { |
| R_TYPE(instr, insn); |
| fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), load_gpr(dc, instr.b)); |
| } |
| |
| #define gen_ri_math_logic(fname, insn) \ |
| static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \ |
| { do_ri_math_logic(dc, code, tcg_gen_##insn##_tl); } |
| |
| #define gen_rr_math_logic(fname, insn) \ |
| static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \ |
| { do_rr_math_logic(dc, code, tcg_gen_##insn##_tl); } |
| |
| gen_rr_math_logic(add, add) |
| gen_rr_math_logic(sub, sub) |
| gen_rr_math_logic(mul, mul) |
| |
| gen_rr_math_logic(and, and) |
| gen_rr_math_logic(or, or) |
| gen_rr_math_logic(xor, xor) |
| gen_rr_math_logic(nor, nor) |
| |
| gen_ri_math_logic(srai, sari) |
| gen_ri_math_logic(srli, shri) |
| gen_ri_math_logic(slli, shli) |
| gen_ri_math_logic(roli, rotli) |
| |
| static void do_rr_mul_high(DisasContext *dc, uint32_t insn, GenFn4 *fn) |
| { |
| R_TYPE(instr, insn); |
| TCGv discard = tcg_temp_new(); |
| |
| fn(discard, dest_gpr(dc, instr.c), |
| load_gpr(dc, instr.a), load_gpr(dc, instr.b)); |
| } |
| |
| #define gen_rr_mul_high(fname, insn) \ |
| static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \ |
| { do_rr_mul_high(dc, code, tcg_gen_##insn##_tl); } |
| |
| gen_rr_mul_high(mulxss, muls2) |
| gen_rr_mul_high(mulxuu, mulu2) |
| gen_rr_mul_high(mulxsu, mulsu2) |
| |
| static void do_rr_shift(DisasContext *dc, uint32_t insn, GenFn3 *fn) |
| { |
| R_TYPE(instr, insn); |
| TCGv sh = tcg_temp_new(); |
| |
| tcg_gen_andi_tl(sh, load_gpr(dc, instr.b), 31); |
| fn(dest_gpr(dc, instr.c), load_gpr(dc, instr.a), sh); |
| } |
| |
| #define gen_rr_shift(fname, insn) \ |
| static void (fname)(DisasContext *dc, uint32_t code, uint32_t flags) \ |
| { do_rr_shift(dc, code, tcg_gen_##insn##_tl); } |
| |
| gen_rr_shift(sra, sar) |
| gen_rr_shift(srl, shr) |
| gen_rr_shift(sll, shl) |
| gen_rr_shift(rol, rotl) |
| gen_rr_shift(ror, rotr) |
| |
| static void divs(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| R_TYPE(instr, (code)); |
| gen_helper_divs(dest_gpr(dc, instr.c), cpu_env, |
| load_gpr(dc, instr.a), load_gpr(dc, instr.b)); |
| } |
| |
| static void divu(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| R_TYPE(instr, (code)); |
| gen_helper_divu(dest_gpr(dc, instr.c), cpu_env, |
| load_gpr(dc, instr.a), load_gpr(dc, instr.b)); |
| } |
| |
| static void trap(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| #ifdef CONFIG_USER_ONLY |
| /* |
| * The imm5 field is not stored anywhere on real hw; the kernel |
| * has to load the insn and extract the field. But we can make |
| * things easier for cpu_loop if we pop this into env->error_code. |
| */ |
| R_TYPE(instr, code); |
| tcg_gen_st_i32(tcg_constant_i32(instr.imm5), cpu_env, |
| offsetof(CPUNios2State, error_code)); |
| #endif |
| t_gen_helper_raise_exception(dc, EXCP_TRAP); |
| } |
| |
| static void gen_break(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| #ifndef CONFIG_USER_ONLY |
| /* The semihosting instruction is "break 1". */ |
| bool is_user = FIELD_EX32(dc->tb_flags, TBFLAGS, U); |
| R_TYPE(instr, code); |
| if (semihosting_enabled(is_user) && instr.imm5 == 1) { |
| t_gen_helper_raise_exception(dc, EXCP_SEMIHOST); |
| return; |
| } |
| #endif |
| |
| t_gen_helper_raise_exception(dc, EXCP_BREAK); |
| } |
| |
| static const Nios2Instruction r_type_instructions[] = { |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(eret), /* eret */ |
| INSTRUCTION(roli), /* roli */ |
| INSTRUCTION(rol), /* rol */ |
| INSTRUCTION_NOP(), /* flushp */ |
| INSTRUCTION(ret), /* ret */ |
| INSTRUCTION(nor), /* nor */ |
| INSTRUCTION(mulxuu), /* mulxuu */ |
| INSTRUCTION_FLG(gen_cmpxx, TCG_COND_GE), /* cmpge */ |
| INSTRUCTION(bret), /* bret */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(ror), /* ror */ |
| INSTRUCTION_NOP(), /* flushi */ |
| INSTRUCTION(jmp), /* jmp */ |
| INSTRUCTION(and), /* and */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_cmpxx, TCG_COND_LT), /* cmplt */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(slli), /* slli */ |
| INSTRUCTION(sll), /* sll */ |
| INSTRUCTION(wrprs), /* wrprs */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(or), /* or */ |
| INSTRUCTION(mulxsu), /* mulxsu */ |
| INSTRUCTION_FLG(gen_cmpxx, TCG_COND_NE), /* cmpne */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(srli), /* srli */ |
| INSTRUCTION(srl), /* srl */ |
| INSTRUCTION(nextpc), /* nextpc */ |
| INSTRUCTION(callr), /* callr */ |
| INSTRUCTION(xor), /* xor */ |
| INSTRUCTION(mulxss), /* mulxss */ |
| INSTRUCTION_FLG(gen_cmpxx, TCG_COND_EQ), /* cmpeq */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(divu), /* divu */ |
| INSTRUCTION(divs), /* div */ |
| INSTRUCTION(rdctl), /* rdctl */ |
| INSTRUCTION(mul), /* mul */ |
| INSTRUCTION_FLG(gen_cmpxx, TCG_COND_GEU), /* cmpgeu */ |
| INSTRUCTION_NOP(), /* initi */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(trap), /* trap */ |
| INSTRUCTION(wrctl), /* wrctl */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_FLG(gen_cmpxx, TCG_COND_LTU), /* cmpltu */ |
| INSTRUCTION(add), /* add */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(gen_break), /* break */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(nop), /* nop */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION(sub), /* sub */ |
| INSTRUCTION(srai), /* srai */ |
| INSTRUCTION(sra), /* sra */ |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| INSTRUCTION_ILLEGAL(), |
| }; |
| |
| static void handle_r_type_instr(DisasContext *dc, uint32_t code, uint32_t flags) |
| { |
| uint8_t opx; |
| const Nios2Instruction *instr; |
| |
| opx = get_opxcode(code); |
| if (unlikely(opx >= ARRAY_SIZE(r_type_instructions))) { |
| goto illegal_op; |
| } |
| |
| instr = &r_type_instructions[opx]; |
| instr->handler(dc, code, instr->flags); |
| |
| return; |
| |
| illegal_op: |
| t_gen_helper_raise_exception(dc, EXCP_ILLEGAL); |
| } |
| |
| static const char * const gr_regnames[NUM_GP_REGS] = { |
| "zero", "at", "r2", "r3", |
| "r4", "r5", "r6", "r7", |
| "r8", "r9", "r10", "r11", |
| "r12", "r13", "r14", "r15", |
| "r16", "r17", "r18", "r19", |
| "r20", "r21", "r22", "r23", |
| "et", "bt", "gp", "sp", |
| "fp", "ea", "ba", "ra", |
| }; |
| |
| #ifndef CONFIG_USER_ONLY |
| static const char * const cr_regnames[NUM_CR_REGS] = { |
| "status", "estatus", "bstatus", "ienable", |
| "ipending", "cpuid", "res6", "exception", |
| "pteaddr", "tlbacc", "tlbmisc", "reserved1", |
| "badaddr", "config", "mpubase", "mpuacc", |
| "res16", "res17", "res18", "res19", |
| "res20", "res21", "res22", "res23", |
| "res24", "res25", "res26", "res27", |
| "res28", "res29", "res30", "res31", |
| }; |
| #endif |
| |
| /* generate intermediate code for basic block 'tb'. */ |
| static void nios2_tr_init_disas_context(DisasContextBase *dcbase, CPUState *cs) |
| { |
| DisasContext *dc = container_of(dcbase, DisasContext, base); |
| CPUNios2State *env = cs->env_ptr; |
| Nios2CPU *cpu = env_archcpu(env); |
| int page_insns; |
| |
| dc->mem_idx = cpu_mmu_index(env, false); |
| dc->cr_state = cpu->cr_state; |
| dc->tb_flags = dc->base.tb->flags; |
| dc->eic_present = cpu->eic_present; |
| |
| /* Bound the number of insns to execute to those left on the page. */ |
| page_insns = -(dc->base.pc_first | TARGET_PAGE_MASK) / 4; |
| dc->base.max_insns = MIN(page_insns, dc->base.max_insns); |
| } |
| |
| static void nios2_tr_tb_start(DisasContextBase *db, CPUState *cs) |
| { |
| } |
| |
| static void nios2_tr_insn_start(DisasContextBase *dcbase, CPUState *cs) |
| { |
| tcg_gen_insn_start(dcbase->pc_next); |
| } |
| |
| static void nios2_tr_translate_insn(DisasContextBase *dcbase, CPUState *cs) |
| { |
| DisasContext *dc = container_of(dcbase, DisasContext, base); |
| CPUNios2State *env = cs->env_ptr; |
| const Nios2Instruction *instr; |
| uint32_t code, pc; |
| uint8_t op; |
| |
| pc = dc->base.pc_next; |
| dc->pc = pc; |
| dc->base.pc_next = pc + 4; |
| |
| /* Decode an instruction */ |
| code = cpu_ldl_code(env, pc); |
| op = get_opcode(code); |
| |
| if (unlikely(op >= ARRAY_SIZE(i_type_instructions))) { |
| t_gen_helper_raise_exception(dc, EXCP_ILLEGAL); |
| return; |
| } |
| |
| dc->sink = NULL; |
| |
| instr = &i_type_instructions[op]; |
| instr->handler(dc, code, instr->flags); |
| } |
| |
| static void nios2_tr_tb_stop(DisasContextBase *dcbase, CPUState *cs) |
| { |
| DisasContext *dc = container_of(dcbase, DisasContext, base); |
| |
| /* Indicate where the next block should start */ |
| switch (dc->base.is_jmp) { |
| case DISAS_TOO_MANY: |
| gen_goto_tb(dc, 0, dc->base.pc_next); |
| break; |
| |
| case DISAS_UPDATE: |
| /* Save the current PC, and return to the main loop. */ |
| tcg_gen_movi_tl(cpu_pc, dc->base.pc_next); |
| tcg_gen_exit_tb(NULL, 0); |
| break; |
| |
| case DISAS_NORETURN: |
| /* nothing more to generate */ |
| break; |
| |
| default: |
| g_assert_not_reached(); |
| } |
| } |
| |
| static void nios2_tr_disas_log(const DisasContextBase *dcbase, |
| CPUState *cpu, FILE *logfile) |
| { |
| fprintf(logfile, "IN: %s\n", lookup_symbol(dcbase->pc_first)); |
| target_disas(logfile, cpu, dcbase->pc_first, dcbase->tb->size); |
| } |
| |
| static const TranslatorOps nios2_tr_ops = { |
| .init_disas_context = nios2_tr_init_disas_context, |
| .tb_start = nios2_tr_tb_start, |
| .insn_start = nios2_tr_insn_start, |
| .translate_insn = nios2_tr_translate_insn, |
| .tb_stop = nios2_tr_tb_stop, |
| .disas_log = nios2_tr_disas_log, |
| }; |
| |
| void gen_intermediate_code(CPUState *cs, TranslationBlock *tb, int *max_insns, |
| target_ulong pc, void *host_pc) |
| { |
| DisasContext dc; |
| translator_loop(cs, tb, max_insns, pc, host_pc, &nios2_tr_ops, &dc.base); |
| } |
| |
| void nios2_cpu_dump_state(CPUState *cs, FILE *f, int flags) |
| { |
| Nios2CPU *cpu = NIOS2_CPU(cs); |
| CPUNios2State *env = &cpu->env; |
| int i; |
| |
| qemu_fprintf(f, "IN: PC=%x %s\n", env->pc, lookup_symbol(env->pc)); |
| |
| for (i = 0; i < NUM_GP_REGS; i++) { |
| qemu_fprintf(f, "%9s=%8.8x ", gr_regnames[i], env->regs[i]); |
| if ((i + 1) % 4 == 0) { |
| qemu_fprintf(f, "\n"); |
| } |
| } |
| |
| #if !defined(CONFIG_USER_ONLY) |
| int j; |
| |
| for (i = j = 0; i < NUM_CR_REGS; i++) { |
| if (!nios2_cr_reserved(&cpu->cr_state[i])) { |
| qemu_fprintf(f, "%9s=%8.8x ", cr_regnames[i], env->ctrl[i]); |
| if (++j % 4 == 0) { |
| qemu_fprintf(f, "\n"); |
| } |
| } |
| } |
| if (j % 4 != 0) { |
| qemu_fprintf(f, "\n"); |
| } |
| if (cpu->mmu_present) { |
| qemu_fprintf(f, " mmu write: VPN=%05X PID %02X TLBACC %08X\n", |
| env->mmu.pteaddr_wr & R_CR_PTEADDR_VPN_MASK, |
| FIELD_EX32(env->mmu.tlbmisc_wr, CR_TLBMISC, PID), |
| env->mmu.tlbacc_wr); |
| } |
| #endif |
| qemu_fprintf(f, "\n\n"); |
| } |
| |
| void nios2_tcg_init(void) |
| { |
| #ifndef CONFIG_USER_ONLY |
| TCGv_ptr crs = tcg_global_mem_new_ptr(cpu_env, |
| offsetof(CPUNios2State, regs), "crs"); |
| |
| for (int i = 0; i < NUM_GP_REGS; i++) { |
| cpu_crs_R[i] = tcg_global_mem_new(crs, 4 * i, gr_regnames[i]); |
| } |
| |
| #define offsetof_regs0(N) offsetof(CPUNios2State, shadow_regs[0][N]) |
| #else |
| #define offsetof_regs0(N) offsetof(CPUNios2State, regs[N]) |
| #endif |
| |
| for (int i = 0; i < NUM_GP_REGS; i++) { |
| cpu_R[i] = tcg_global_mem_new(cpu_env, offsetof_regs0(i), |
| gr_regnames[i]); |
| } |
| |
| #undef offsetof_regs0 |
| |
| cpu_pc = tcg_global_mem_new(cpu_env, |
| offsetof(CPUNios2State, pc), "pc"); |
| } |