#include "armdefs.h"
#include "arm2x86_self.h"

extern mem_bank_t	*bank_ptr(ARMword addr);

/*ywc 2005-04-22, in armmem.c*/
extern mem_bank_t	*insn_bank_ptr(ARMword addr);

static __inline__ int
translate_word(ARMul_State *state, ARMword insn, uint8_t *tbp)
{
	int		toplen = 0, len = 0;
	ARMword		val, op1, shift, rm, rs, rn, rd, sh;
	uint8_t		*begin = tbp;

	//init
	begin = tbp;
	state->trap = 0;

	//return if debug || irq || fiq || condition
	op1 = (insn >> 28) & 0xff;
	if (op1 == AL || op1 == NV) {
		GEN_OP(tbp, len, op_begin);
		if (op1 == NV)
			goto translate_word_out;
	}
	else {
		gen_op_movl_Tx_im(state, &tbp, &len, 0, op1);
		GEN_OP(tbp, len, op_begin_test_T0);
		toplen = len;
	}

	if (((insn & 0x0e000000) == 0 && (insn & 0x00000090) != 0x90) || ((insn & 0x0e000000) == (1 << 25))) {
		ARMword		set_cc, logic_cc, shiftop;

		op1 = (insn >> 21) & 0xf;
		set_cc = (insn >> 20) & 1;

		if ((op1 == 0x8 || op1 == 0xa) && !set_cc) {
			//mrs
			gen_op_mrs(state, &tbp, &len, insn);
			goto translate_word_end;
		}

		logic_cc = table_logic_cc[op1] & set_cc;
		//in arm_arm A 5.1
		if (insn & (1 << 25)) {
			//immediate operand
			val = insn & 0xff;
			shift = (uint8_t)((insn >> 8) & 0xf) * 2;
			//ror
			if (shift)
				val = (val >> shift) | (val << (32 - shift));
			//op=set val to t1
			gen_op_movl_Tx_im(state, &tbp, &len, 1, val);

			if (logic_cc && shift) {
				val = ((insn & 0xff) >> (shift - 1)) & 1;
				//op=set val to C
				if (val) {
					GEN_OP(tbp, len, op_logic_1_sc);
				}
				else {
					GEN_OP(tbp, len, op_logic_0_sc);
				}
			}
		}
		else {
			//register
			rm = (insn) & 0xf;
			//op=set rm(0-15) to t1
			gen_op_movl_Tx_reg(state, &tbp, &len, 1, rm);
			shiftop = (insn >> 5) & 3;
			if (!(insn & (1 << 4))) {
				shift = (uint8_t)(insn >> 7) & 0x1f;
				if (shift != 0) {
					//op=shift, & set CF if logic_cc
					if (logic_cc) {
						gen_op_shift_T1_im_sc(state, &tbp, &len, shiftop, shift);
					}
					gen_op_shift_T1_im(state, &tbp, &len, shiftop, shift);
				}
			}
			else {
				rs = (insn >> 8) & 0xf;
				//op=set rs(0-15) to t0
				gen_op_movl_Tx_reg(state, &tbp, &len, 0, rs);
				//op=shift, & set CF if logic_cc
				if (logic_cc) {
					//op=shift & set CF
					gen_op_shift_T1_T0_sc(state, &tbp, &len, shiftop);
				}
				else {
					//op=shift
					gen_op_shift_T1_T0(state, &tbp, &len, shiftop);
				}
			}
		}

		if ((op1 == 0x9 || op1 == 0xb) && !set_cc) {
			//msr	T1, psr
			gen_op_msr(state, &tbp, &len, insn);
			goto translate_word_end;
		}

		//data processing instruction
		if (op1 != 0x0f && op1 != 0x0d) {
			rn = (insn >> 16) & 0xf;
			//op=set rn(0-15) to t0
			gen_op_movl_Tx_reg(state, &tbp, &len, 0, rn);
		}
		rd = (insn >> 12) & 0xf;
		arm2x86_get_dp_op[op1](state, &tbp, &len, set_cc, rd);
		arm2x86_get_dp_op_setcpsr[op1](state, &tbp, &len, set_cc, rd);
	}
	else {
		//other instructions
		op1 = (insn >> 24) & 0xf;
		sh = (insn >> 5) & 3;
		if (sh != 0 && (op1 == 0 || op1 == 1)) {
			//ldrh strh ldrsh ldrsb
			gen_op_ldrhstrh(state, &tbp, &len, insn, sh);
		}
		else {
			arm2x86_get_other_op[op1](state, insn, &tbp, &len);
		}
	}

translate_word_end:
	if (state->trap) {
		GEN_OP(tbp, len, op_return);
	}
	if (toplen && begin) {
		//set jmp length of condition code
		//begin[toplen-1] = (uint8_t)(len - toplen);
		int	*p_tmp = (int *)(begin + (toplen - sizeof(int)));
		*p_tmp = len - toplen;
	}

translate_word_out:
	//r15 += 4
	if (!state->trap || toplen) {
		GEN_OP(tbp, len, op_addpc);
	}
	TEA_OUT(GEN_OP(tbp, len, op_return));
	if (len > TB_INSN_LEN_MAX) {
		fprintf(stderr, "SKYEYE: TB_INSN_LEN_MAX: insn %x len %d > TB_INSN_LEN_MAX %d.\n", insn, len, TB_INSN_LEN_MAX);
		exit(-1);
	}
#ifdef TEA_DEBUG
	{
		static int	insn_max = 0;
		if (len > insn_max) {
			insn_max = len;
			fprintf(stderr, "\nSKYEYE: insn_max = %d.\n", insn_max);
		}
	}
#endif	//TEA_DEBUG

	return(len);
}

static uint8_t *
tb_translate(ARMul_State * state, ARMword *addr, ARMword *tb_begin_addr, uint8_t *tbp)
{
	int		len;
	uint8_t		*ret = NULL;
	ARMword		*tb_end_addr = tb_begin_addr + TB_LEN  / sizeof(ARMword);

	for( ; tb_begin_addr < tb_end_addr; tb_begin_addr++) {
		if (addr == tb_begin_addr) {
			ret = tbp;
		}
		len = translate_word(state, *tb_begin_addr, tbp);
		tbp += len;
	}
	GEN_OP(tbp, len, op_return);

	return(ret);
}

static uint8_t *
tb_translate_find(ARMul_State * state, ARMword *addr, ARMword *tb_begin_addr, uint8_t *tbp)
{
	int		len;
	uint8_t		*ret = NULL;
	ARMword		*tb_end_addr = tb_begin_addr + TB_LEN  / sizeof(ARMword);

	for( ; tb_begin_addr < tb_end_addr; tb_begin_addr++) {
		if (addr == tb_begin_addr) {
			ret = tbp;
			break;
		}
		len = translate_word(state, *tb_begin_addr, NULL);
		tbp += len;
	}

	return(ret);
}

uint8_t *
tb_find(ARMul_State * state, ARMword addr)
{
	int		bank_num;
	tb_t		*tbt;
	uint8_t		*tbp, *ret = NULL;
	ARMword		*tb_begin_addr, *real_addr;
	ARMword		align_addr = ALIGN(addr, TB_LEN);
	mem_bank_t	*mbp = NULL;
	tb_cache_t	*e;

	//XXX teawater: can't use in the mach that have mmu. i want to add a function pointer in mmu_ops_s to do get the real addr.
	
	/*ywc 2005-04-22*/
	//mbp = bank_ptr(align_addr);
	mbp = insn_bank_ptr(align_addr);
	
	if(!mbp) {
		fprintf(stderr, "tb_find. No bank at address 0x%x", addr);
		goto out;
	}
	bank_num = mbp - skyeye_config.mem.mem_banks;
	if (!state->mem.tbt[bank_num]) {
		state->mem.tbp[bank_num] = mmap(NULL, state->mem.rom_size[bank_num] / sizeof(ARMword) * TB_INSN_LEN_MAX + state->mem.rom_size[bank_num] / TB_LEN * op_return.len, PROT_READ|PROT_WRITE|PROT_EXEC, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0);
		if (state->mem.tbp[bank_num] == MAP_FAILED) {
			fprintf(stderr, "SKYEYE: mem_reset: Error allocating mem for bank number %d.\n", bank_num);
			exit(-1);
		}
		state->mem.tbt[bank_num] = malloc(state->mem.rom_size[bank_num] / TB_LEN * sizeof(tb_t));
		if (!state->mem.tbt[bank_num]) {
			fprintf(stderr, "SKYEYE: mem_reset: Error allocating mem for bank number %d.\n", bank_num);
			exit(-1);
		}
		memset(state->mem.tbt[bank_num], 0, state->mem.rom_size[bank_num] / TB_LEN * sizeof(tb_t));
	}
	tbt = &(state->mem.tbt[bank_num][(align_addr - mbp->addr)/TB_LEN]);
	tbp = &(state->mem.tbp[bank_num][(align_addr - mbp->addr) / sizeof(ARMword) * TB_INSN_LEN_MAX + (align_addr - mbp->addr) / TB_LEN * op_return.len]);
	tb_begin_addr = &(state->mem.rom[bank_num][(align_addr - mbp->addr) / sizeof(ARMword)]);
	real_addr = tb_begin_addr + (addr - align_addr) / sizeof(ARMword);
	//already translated
	if (tbt->ted) {
		struct list_head	*list,*n;

		//addr is align
		if (addr == align_addr) {
			ret = tbp;
			goto out;
		}
		//find in cache
		list_for_each_safe(list, n, &tbt->cache) {
			e = list_entry(list, tb_cache_t, list);
			if (e->addr == addr) {
				ret = e->tp;
				goto out;
			}
		}
		//translate to find not align addr
		ret = tb_translate_find(state, real_addr, tb_begin_addr, tbp);
		if (!ret)
			goto out;
		//add to cache
		e = malloc(sizeof(tb_cache_t));
		if (!e) {
			fprintf(stderr, "SKYEYE: tb_find: Error allocating mem for cache.\n");
		}
		else {
			e->addr = addr;
			e->tp = ret;
			list_add_tail(&e->list, &tbt->cache);
		}
		goto out;
	}
	//not translated
	INIT_LIST_HEAD(&tbt->cache);	//cache is already clear when set tbt->ted to 0
	ret = tb_translate(state, real_addr, tb_begin_addr, tbp);
	if (!ret)
		goto out;
	tbt->ted = 1;
	//add to cache
	if (addr == align_addr) {
		e = malloc(sizeof(tb_cache_t));
		if (!e) {
			fprintf(stderr, "SKYEYE: tb_find: Error allocating mem for cache.\n");
		}
		else {
			e->addr = addr;
			e->tp = ret;
			list_add_tail(&e->list, &tbt->cache);
		}
	}

out:
	return(ret);
}

int
tb_setdirty(ARMul_State * state, ARMword addr, mem_bank_t *mbp)
{
	int			bank_num;
	tb_t			*tbt;
	tb_cache_t		*e;
	struct list_head	*list,*n;
	ARMword			align_addr = ALIGN(addr, TB_LEN);

	if (!mbp) {
		/*ywc 2005-04-22*/
		//mbp = bank_ptr(align_addr);
		mbp = insn_bank_ptr(align_addr);
		
		if(!mbp) {
			fprintf(stderr, "tb_setdirty. No bank at address 0x%x", addr);
			return(-1);
		}
	}
	bank_num = mbp - skyeye_config.mem.mem_banks;
	if (!state->mem.tbt[bank_num]) {
		return(0);
	}
	tbt = &(state->mem.tbt[bank_num][(align_addr - mbp->addr)/TB_LEN]);
	if (tbt->ted) {
		list_for_each_safe(list, n, &tbt->cache) {
			e = list_entry(list, tb_cache_t, list);
			list_del_init(&e->list);
			free(e);
		}
		tbt->ted = 0;
	}

	return(0);
}
