diff options
Diffstat (limited to 'target/cris/op_helper.c')
-rw-r--r-- | target/cris/op_helper.c | 639 |
1 files changed, 639 insertions, 0 deletions
diff --git a/target/cris/op_helper.c b/target/cris/op_helper.c new file mode 100644 index 0000000000..504303913c --- /dev/null +++ b/target/cris/op_helper.c @@ -0,0 +1,639 @@ +/* + * CRIS helper routines + * + * Copyright (c) 2007 AXIS Communications + * Written by Edgar E. Iglesias + * + * 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 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/>. + */ + +#include "qemu/osdep.h" +#include "cpu.h" +#include "mmu.h" +#include "exec/helper-proto.h" +#include "qemu/host-utils.h" +#include "exec/exec-all.h" +#include "exec/cpu_ldst.h" + +//#define CRIS_OP_HELPER_DEBUG + + +#ifdef CRIS_OP_HELPER_DEBUG +#define D(x) x +#define D_LOG(...) qemu_log(__VA_ARGS__) +#else +#define D(x) +#define D_LOG(...) do { } while (0) +#endif + +#if !defined(CONFIG_USER_ONLY) +/* Try to fill the TLB and return an exception if error. If retaddr is + NULL, it means that the function was called in C code (i.e. not + from generated code or from helper.c) */ +void tlb_fill(CPUState *cs, target_ulong addr, MMUAccessType access_type, + int mmu_idx, uintptr_t retaddr) +{ + CRISCPU *cpu = CRIS_CPU(cs); + CPUCRISState *env = &cpu->env; + int ret; + + D_LOG("%s pc=%x tpc=%x ra=%p\n", __func__, + env->pc, env->pregs[PR_EDA], (void *)retaddr); + ret = cris_cpu_handle_mmu_fault(cs, addr, access_type, mmu_idx); + if (unlikely(ret)) { + if (retaddr) { + /* now we have a real cpu fault */ + if (cpu_restore_state(cs, retaddr)) { + /* Evaluate flags after retranslation. */ + helper_top_evaluate_flags(env); + } + } + cpu_loop_exit(cs); + } +} + +#endif + +void helper_raise_exception(CPUCRISState *env, uint32_t index) +{ + CPUState *cs = CPU(cris_env_get_cpu(env)); + + cs->exception_index = index; + cpu_loop_exit(cs); +} + +void helper_tlb_flush_pid(CPUCRISState *env, uint32_t pid) +{ +#if !defined(CONFIG_USER_ONLY) + pid &= 0xff; + if (pid != (env->pregs[PR_PID] & 0xff)) + cris_mmu_flush_pid(env, env->pregs[PR_PID]); +#endif +} + +void helper_spc_write(CPUCRISState *env, uint32_t new_spc) +{ +#if !defined(CONFIG_USER_ONLY) + CRISCPU *cpu = cris_env_get_cpu(env); + CPUState *cs = CPU(cpu); + + tlb_flush_page(cs, env->pregs[PR_SPC]); + tlb_flush_page(cs, new_spc); +#endif +} + +/* Used by the tlb decoder. */ +#define EXTRACT_FIELD(src, start, end) \ + (((src) >> start) & ((1 << (end - start + 1)) - 1)) + +void helper_movl_sreg_reg(CPUCRISState *env, uint32_t sreg, uint32_t reg) +{ +#if !defined(CONFIG_USER_ONLY) + CRISCPU *cpu = cris_env_get_cpu(env); +#endif + uint32_t srs; + srs = env->pregs[PR_SRS]; + srs &= 3; + env->sregs[srs][sreg] = env->regs[reg]; + +#if !defined(CONFIG_USER_ONLY) + if (srs == 1 || srs == 2) { + if (sreg == 6) { + /* Writes to tlb-hi write to mm_cause as a side + effect. */ + env->sregs[SFR_RW_MM_TLB_HI] = env->regs[reg]; + env->sregs[SFR_R_MM_CAUSE] = env->regs[reg]; + } + else if (sreg == 5) { + uint32_t set; + uint32_t idx; + uint32_t lo, hi; + uint32_t vaddr; + int tlb_v; + + idx = set = env->sregs[SFR_RW_MM_TLB_SEL]; + set >>= 4; + set &= 3; + + idx &= 15; + /* We've just made a write to tlb_lo. */ + lo = env->sregs[SFR_RW_MM_TLB_LO]; + /* Writes are done via r_mm_cause. */ + hi = env->sregs[SFR_R_MM_CAUSE]; + + vaddr = EXTRACT_FIELD(env->tlbsets[srs-1][set][idx].hi, + 13, 31); + vaddr <<= TARGET_PAGE_BITS; + tlb_v = EXTRACT_FIELD(env->tlbsets[srs-1][set][idx].lo, + 3, 3); + env->tlbsets[srs - 1][set][idx].lo = lo; + env->tlbsets[srs - 1][set][idx].hi = hi; + + D_LOG("tlb flush vaddr=%x v=%d pc=%x\n", + vaddr, tlb_v, env->pc); + if (tlb_v) { + tlb_flush_page(CPU(cpu), vaddr); + } + } + } +#endif +} + +void helper_movl_reg_sreg(CPUCRISState *env, uint32_t reg, uint32_t sreg) +{ + uint32_t srs; + env->pregs[PR_SRS] &= 3; + srs = env->pregs[PR_SRS]; + +#if !defined(CONFIG_USER_ONLY) + if (srs == 1 || srs == 2) + { + uint32_t set; + uint32_t idx; + uint32_t lo, hi; + + idx = set = env->sregs[SFR_RW_MM_TLB_SEL]; + set >>= 4; + set &= 3; + idx &= 15; + + /* Update the mirror regs. */ + hi = env->tlbsets[srs - 1][set][idx].hi; + lo = env->tlbsets[srs - 1][set][idx].lo; + env->sregs[SFR_RW_MM_TLB_HI] = hi; + env->sregs[SFR_RW_MM_TLB_LO] = lo; + } +#endif + env->regs[reg] = env->sregs[srs][sreg]; +} + +static void cris_ccs_rshift(CPUCRISState *env) +{ + uint32_t ccs; + + /* Apply the ccs shift. */ + ccs = env->pregs[PR_CCS]; + ccs = (ccs & 0xc0000000) | ((ccs & 0x0fffffff) >> 10); + if (ccs & U_FLAG) + { + /* Enter user mode. */ + env->ksp = env->regs[R_SP]; + env->regs[R_SP] = env->pregs[PR_USP]; + } + + env->pregs[PR_CCS] = ccs; +} + +void helper_rfe(CPUCRISState *env) +{ + int rflag = env->pregs[PR_CCS] & R_FLAG; + + D_LOG("rfe: erp=%x pid=%x ccs=%x btarget=%x\n", + env->pregs[PR_ERP], env->pregs[PR_PID], + env->pregs[PR_CCS], + env->btarget); + + cris_ccs_rshift(env); + + /* RFE sets the P_FLAG only if the R_FLAG is not set. */ + if (!rflag) + env->pregs[PR_CCS] |= P_FLAG; +} + +void helper_rfn(CPUCRISState *env) +{ + int rflag = env->pregs[PR_CCS] & R_FLAG; + + D_LOG("rfn: erp=%x pid=%x ccs=%x btarget=%x\n", + env->pregs[PR_ERP], env->pregs[PR_PID], + env->pregs[PR_CCS], + env->btarget); + + cris_ccs_rshift(env); + + /* Set the P_FLAG only if the R_FLAG is not set. */ + if (!rflag) + env->pregs[PR_CCS] |= P_FLAG; + + /* Always set the M flag. */ + env->pregs[PR_CCS] |= M_FLAG_V32; +} + +uint32_t helper_lz(uint32_t t0) +{ + return clz32(t0); +} + +uint32_t helper_btst(CPUCRISState *env, uint32_t t0, uint32_t t1, uint32_t ccs) +{ + /* FIXME: clean this up. */ + + /* des ref: + The N flag is set according to the selected bit in the dest reg. + The Z flag is set if the selected bit and all bits to the right are + zero. + The X flag is cleared. + Other flags are left untouched. + The destination reg is not affected.*/ + unsigned int fz, sbit, bset, mask, masked_t0; + + sbit = t1 & 31; + bset = !!(t0 & (1 << sbit)); + mask = sbit == 31 ? -1 : (1 << (sbit + 1)) - 1; + masked_t0 = t0 & mask; + fz = !(masked_t0 | bset); + + /* Clear the X, N and Z flags. */ + ccs = ccs & ~(X_FLAG | N_FLAG | Z_FLAG); + if (env->pregs[PR_VR] < 32) + ccs &= ~(V_FLAG | C_FLAG); + /* Set the N and Z flags accordingly. */ + ccs |= (bset << 3) | (fz << 2); + return ccs; +} + +static inline uint32_t evaluate_flags_writeback(CPUCRISState *env, + uint32_t flags, uint32_t ccs) +{ + unsigned int x, z, mask; + + /* Extended arithmetics, leave the z flag alone. */ + x = env->cc_x; + mask = env->cc_mask | X_FLAG; + if (x) { + z = flags & Z_FLAG; + mask = mask & ~z; + } + flags &= mask; + + /* all insn clear the x-flag except setf or clrf. */ + ccs &= ~mask; + ccs |= flags; + return ccs; +} + +uint32_t helper_evaluate_flags_muls(CPUCRISState *env, + uint32_t ccs, uint32_t res, uint32_t mof) +{ + uint32_t flags = 0; + int64_t tmp; + int dneg; + + dneg = ((int32_t)res) < 0; + + tmp = mof; + tmp <<= 32; + tmp |= res; + if (tmp == 0) + flags |= Z_FLAG; + else if (tmp < 0) + flags |= N_FLAG; + if ((dneg && mof != -1) + || (!dneg && mof != 0)) + flags |= V_FLAG; + return evaluate_flags_writeback(env, flags, ccs); +} + +uint32_t helper_evaluate_flags_mulu(CPUCRISState *env, + uint32_t ccs, uint32_t res, uint32_t mof) +{ + uint32_t flags = 0; + uint64_t tmp; + + tmp = mof; + tmp <<= 32; + tmp |= res; + if (tmp == 0) + flags |= Z_FLAG; + else if (tmp >> 63) + flags |= N_FLAG; + if (mof) + flags |= V_FLAG; + + return evaluate_flags_writeback(env, flags, ccs); +} + +uint32_t helper_evaluate_flags_mcp(CPUCRISState *env, uint32_t ccs, + uint32_t src, uint32_t dst, uint32_t res) +{ + uint32_t flags = 0; + + src = src & 0x80000000; + dst = dst & 0x80000000; + + if ((res & 0x80000000L) != 0L) + { + flags |= N_FLAG; + if (!src && !dst) + flags |= V_FLAG; + else if (src & dst) + flags |= R_FLAG; + } + else + { + if (res == 0L) + flags |= Z_FLAG; + if (src & dst) + flags |= V_FLAG; + if (dst | src) + flags |= R_FLAG; + } + + return evaluate_flags_writeback(env, flags, ccs); +} + +uint32_t helper_evaluate_flags_alu_4(CPUCRISState *env, uint32_t ccs, + uint32_t src, uint32_t dst, uint32_t res) +{ + uint32_t flags = 0; + + src = src & 0x80000000; + dst = dst & 0x80000000; + + if ((res & 0x80000000L) != 0L) + { + flags |= N_FLAG; + if (!src && !dst) + flags |= V_FLAG; + else if (src & dst) + flags |= C_FLAG; + } + else + { + if (res == 0L) + flags |= Z_FLAG; + if (src & dst) + flags |= V_FLAG; + if (dst | src) + flags |= C_FLAG; + } + + return evaluate_flags_writeback(env, flags, ccs); +} + +uint32_t helper_evaluate_flags_sub_4(CPUCRISState *env, uint32_t ccs, + uint32_t src, uint32_t dst, uint32_t res) +{ + uint32_t flags = 0; + + src = (~src) & 0x80000000; + dst = dst & 0x80000000; + + if ((res & 0x80000000L) != 0L) + { + flags |= N_FLAG; + if (!src && !dst) + flags |= V_FLAG; + else if (src & dst) + flags |= C_FLAG; + } + else + { + if (res == 0L) + flags |= Z_FLAG; + if (src & dst) + flags |= V_FLAG; + if (dst | src) + flags |= C_FLAG; + } + + flags ^= C_FLAG; + return evaluate_flags_writeback(env, flags, ccs); +} + +uint32_t helper_evaluate_flags_move_4(CPUCRISState *env, + uint32_t ccs, uint32_t res) +{ + uint32_t flags = 0; + + if ((int32_t)res < 0) + flags |= N_FLAG; + else if (res == 0L) + flags |= Z_FLAG; + + return evaluate_flags_writeback(env, flags, ccs); +} +uint32_t helper_evaluate_flags_move_2(CPUCRISState *env, + uint32_t ccs, uint32_t res) +{ + uint32_t flags = 0; + + if ((int16_t)res < 0L) + flags |= N_FLAG; + else if (res == 0) + flags |= Z_FLAG; + + return evaluate_flags_writeback(env, flags, ccs); +} + +/* TODO: This is expensive. We could split things up and only evaluate part of + CCR on a need to know basis. For now, we simply re-evaluate everything. */ +void helper_evaluate_flags(CPUCRISState *env) +{ + uint32_t src, dst, res; + uint32_t flags = 0; + + src = env->cc_src; + dst = env->cc_dest; + res = env->cc_result; + + if (env->cc_op == CC_OP_SUB || env->cc_op == CC_OP_CMP) + src = ~src; + + /* Now, evaluate the flags. This stuff is based on + Per Zander's CRISv10 simulator. */ + switch (env->cc_size) + { + case 1: + if ((res & 0x80L) != 0L) + { + flags |= N_FLAG; + if (((src & 0x80L) == 0L) + && ((dst & 0x80L) == 0L)) + { + flags |= V_FLAG; + } + else if (((src & 0x80L) != 0L) + && ((dst & 0x80L) != 0L)) + { + flags |= C_FLAG; + } + } + else + { + if ((res & 0xFFL) == 0L) + { + flags |= Z_FLAG; + } + if (((src & 0x80L) != 0L) + && ((dst & 0x80L) != 0L)) + { + flags |= V_FLAG; + } + if ((dst & 0x80L) != 0L + || (src & 0x80L) != 0L) + { + flags |= C_FLAG; + } + } + break; + case 2: + if ((res & 0x8000L) != 0L) + { + flags |= N_FLAG; + if (((src & 0x8000L) == 0L) + && ((dst & 0x8000L) == 0L)) + { + flags |= V_FLAG; + } + else if (((src & 0x8000L) != 0L) + && ((dst & 0x8000L) != 0L)) + { + flags |= C_FLAG; + } + } + else + { + if ((res & 0xFFFFL) == 0L) + { + flags |= Z_FLAG; + } + if (((src & 0x8000L) != 0L) + && ((dst & 0x8000L) != 0L)) + { + flags |= V_FLAG; + } + if ((dst & 0x8000L) != 0L + || (src & 0x8000L) != 0L) + { + flags |= C_FLAG; + } + } + break; + case 4: + if ((res & 0x80000000L) != 0L) + { + flags |= N_FLAG; + if (((src & 0x80000000L) == 0L) + && ((dst & 0x80000000L) == 0L)) + { + flags |= V_FLAG; + } + else if (((src & 0x80000000L) != 0L) && + ((dst & 0x80000000L) != 0L)) + { + flags |= C_FLAG; + } + } + else + { + if (res == 0L) + flags |= Z_FLAG; + if (((src & 0x80000000L) != 0L) + && ((dst & 0x80000000L) != 0L)) + flags |= V_FLAG; + if ((dst & 0x80000000L) != 0L + || (src & 0x80000000L) != 0L) + flags |= C_FLAG; + } + break; + default: + break; + } + + if (env->cc_op == CC_OP_SUB || env->cc_op == CC_OP_CMP) + flags ^= C_FLAG; + + env->pregs[PR_CCS] = evaluate_flags_writeback(env, flags, + env->pregs[PR_CCS]); +} + +void helper_top_evaluate_flags(CPUCRISState *env) +{ + switch (env->cc_op) + { + case CC_OP_MCP: + env->pregs[PR_CCS] = helper_evaluate_flags_mcp(env, + env->pregs[PR_CCS], env->cc_src, + env->cc_dest, env->cc_result); + break; + case CC_OP_MULS: + env->pregs[PR_CCS] = helper_evaluate_flags_muls(env, + env->pregs[PR_CCS], env->cc_result, + env->pregs[PR_MOF]); + break; + case CC_OP_MULU: + env->pregs[PR_CCS] = helper_evaluate_flags_mulu(env, + env->pregs[PR_CCS], env->cc_result, + env->pregs[PR_MOF]); + break; + case CC_OP_MOVE: + case CC_OP_AND: + case CC_OP_OR: + case CC_OP_XOR: + case CC_OP_ASR: + case CC_OP_LSR: + case CC_OP_LSL: + switch (env->cc_size) + { + case 4: + env->pregs[PR_CCS] = + helper_evaluate_flags_move_4(env, + env->pregs[PR_CCS], + env->cc_result); + break; + case 2: + env->pregs[PR_CCS] = + helper_evaluate_flags_move_2(env, + env->pregs[PR_CCS], + env->cc_result); + break; + default: + helper_evaluate_flags(env); + break; + } + break; + case CC_OP_FLAGS: + /* live. */ + break; + case CC_OP_SUB: + case CC_OP_CMP: + if (env->cc_size == 4) + env->pregs[PR_CCS] = + helper_evaluate_flags_sub_4(env, + env->pregs[PR_CCS], + env->cc_src, env->cc_dest, + env->cc_result); + else + helper_evaluate_flags(env); + break; + default: + { + switch (env->cc_size) + { + case 4: + env->pregs[PR_CCS] = + helper_evaluate_flags_alu_4(env, + env->pregs[PR_CCS], + env->cc_src, env->cc_dest, + env->cc_result); + break; + default: + helper_evaluate_flags(env); + break; + } + } + break; + } +} |