/*
 *  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.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/>.
 */

#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

void helper_raise_exception(CPUCRISState *env, uint32_t index)
{
    CPUState *cs = env_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)
    CPUState *cs = env_cpu(env);

    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)
{
    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(env_cpu(env), 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_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 arithmetic, 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;
    }
}
