qemu/target/nios2/translate.c
Richard Henderson e84f176844 target/nios2: Advance pc when raising exceptions
The exception return address for nios2 is the instruction
after the one that was executing at the time of the exception.

We have so far implemented this by advancing the pc during the
process of raising the exception.  It is perhaps a little less
confusing to do this advance in the translator (and helpers)
when raising the exception in the first place, so that we may
more closely match kernel sources.

Signed-off-by: Richard Henderson <richard.henderson@linaro.org>
Reviewed-by: Peter Maydell <peter.maydell@linaro.org>
Message-Id: <20220421151735.31996-58-richard.henderson@linaro.org>
2022-04-26 08:17:05 -07:00

1117 lines
34 KiB
C

/*
* 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/cpu_ldst.h"
#include "exec/translator.h"
#include "qemu/qemu-print.h"
#include "exec/gen-icount.h"
#include "semihosting/semihost.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_temp_free(test);
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);
tcg_gen_qemu_ld_tl(data, addr, dc->mem_idx, flags);
tcg_temp_free(addr);
}
/* 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);
tcg_gen_qemu_st_tl(val, addr, dc->mem_idx, flags);
tcg_temp_free(addr);
}
/* 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_UW), /* ldhu */
INSTRUCTION(andi), /* andi */
INSTRUCTION_FLG(gen_stx, MO_UW), /* sth */
INSTRUCTION_FLG(gen_bxx, TCG_COND_GE), /* bge */
INSTRUCTION_FLG(gen_ldx, MO_SW), /* ldh */
INSTRUCTION_FLG(gen_cmpxxsi, TCG_COND_LT), /* cmplti */
INSTRUCTION_ILLEGAL(),
INSTRUCTION_ILLEGAL(),
INSTRUCTION_NOP(), /* initda */
INSTRUCTION(ori), /* ori */
INSTRUCTION_FLG(gen_stx, MO_UL), /* stw */
INSTRUCTION_FLG(gen_bxx, TCG_COND_LT), /* blt */
INSTRUCTION_FLG(gen_ldx, MO_UL), /* 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_UW), /* ldhuio */
INSTRUCTION(andhi), /* andhi */
INSTRUCTION_FLG(gen_stx, MO_UW), /* sthio */
INSTRUCTION_FLG(gen_bxx, TCG_COND_GEU), /* bgeu */
INSTRUCTION_FLG(gen_ldx, MO_SW), /* 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_SL), /* stwio */
INSTRUCTION_FLG(gen_bxx, TCG_COND_LTU), /* bltu */
INSTRUCTION_FLG(gen_ldx, MO_UL), /* 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));
tcg_temp_free(tmp);
} 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));
tcg_temp_free(tmp);
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);
tcg_temp_free(t1);
tcg_temp_free(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_temp_free(o);
}
tcg_gen_st_tl(n, cpu_env, ofs);
tcg_temp_free(n);
}
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));
tcg_temp_free(discard);
}
#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);
tcg_temp_free(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". */
R_TYPE(instr, code);
if (semihosting_enabled() && 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
#include "exec/gen-icount.h"
/* 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);
if (dc->sink) {
tcg_temp_free(dc->sink);
}
}
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)
{
DisasContext dc;
translator_loop(&nios2_tr_ops, &dc.base, cs, tb, max_insns);
}
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");
}
void restore_state_to_opc(CPUNios2State *env, TranslationBlock *tb,
target_ulong *data)
{
env->pc = data[0];
}