diff options
author | Paul Brook <paul@codesourcery.com> | 2009-11-22 21:35:13 +0000 |
---|---|---|
committer | Paul Brook <paul@codesourcery.com> | 2009-11-22 21:35:13 +0000 |
commit | 426f5abcaa15d0e59475fdb33b2a4a5fdf492d05 (patch) | |
tree | ef9eb63ac19b7ba19fe533d514d3571f21de2b3d /target-arm/translate.c | |
parent | eee4850422d1d18276c1f4dbb6153b568efcb8b9 (diff) |
ARM atomic ops rewrite
Implement ARMv6 atomic ops (ldrex/strex) using the same trick as PPC.
Signed-off-by: Paul Brook <paul@codesourcery.com>
Diffstat (limited to 'target-arm/translate.c')
-rw-r--r-- | target-arm/translate.c | 250 |
1 files changed, 165 insertions, 85 deletions
diff --git a/target-arm/translate.c b/target-arm/translate.c index a002f7e029..45bf77256f 100644 --- a/target-arm/translate.c +++ b/target-arm/translate.c @@ -76,6 +76,13 @@ static TCGv_ptr cpu_env; /* We reuse the same 64-bit temporaries for efficiency. */ static TCGv_i64 cpu_V0, cpu_V1, cpu_M0; static TCGv_i32 cpu_R[16]; +static TCGv_i32 cpu_exclusive_addr; +static TCGv_i32 cpu_exclusive_val; +static TCGv_i32 cpu_exclusive_high; +#ifdef CONFIG_USER_ONLY +static TCGv_i32 cpu_exclusive_test; +static TCGv_i32 cpu_exclusive_info; +#endif /* FIXME: These should be removed. */ static TCGv cpu_F0s, cpu_F1s; @@ -99,6 +106,18 @@ void arm_translate_init(void) offsetof(CPUState, regs[i]), regnames[i]); } + cpu_exclusive_addr = tcg_global_mem_new_i32(TCG_AREG0, + offsetof(CPUState, exclusive_addr), "exclusive_addr"); + cpu_exclusive_val = tcg_global_mem_new_i32(TCG_AREG0, + offsetof(CPUState, exclusive_val), "exclusive_val"); + cpu_exclusive_high = tcg_global_mem_new_i32(TCG_AREG0, + offsetof(CPUState, exclusive_high), "exclusive_high"); +#ifdef CONFIG_USER_ONLY + cpu_exclusive_test = tcg_global_mem_new_i32(TCG_AREG0, + offsetof(CPUState, exclusive_test), "exclusive_test"); + cpu_exclusive_info = tcg_global_mem_new_i32(TCG_AREG0, + offsetof(CPUState, exclusive_info), "exclusive_info"); +#endif #define GEN_HELPER 2 #include "helpers.h" @@ -5819,6 +5838,132 @@ static void gen_logicq_cc(TCGv_i64 val) dead_tmp(tmp); } +/* Load/Store exclusive instructions are implemented by remembering + the value/address loaded, and seeing if these are the same + when the store is performed. This should be is sufficient to implement + the architecturally mandated semantics, and avoids having to monitor + regular stores. + + In system emulation mode only one CPU will be running at once, so + this sequence is effectively atomic. In user emulation mode we + throw an exception and handle the atomic operation elsewhere. */ +static void gen_load_exclusive(DisasContext *s, int rt, int rt2, + TCGv addr, int size) +{ + TCGv tmp; + + switch (size) { + case 0: + tmp = gen_ld8u(addr, IS_USER(s)); + break; + case 1: + tmp = gen_ld16u(addr, IS_USER(s)); + break; + case 2: + case 3: + tmp = gen_ld32(addr, IS_USER(s)); + break; + default: + abort(); + } + tcg_gen_mov_i32(cpu_exclusive_val, tmp); + store_reg(s, rt, tmp); + if (size == 3) { + tcg_gen_addi_i32(addr, addr, 4); + tmp = gen_ld32(addr, IS_USER(s)); + tcg_gen_mov_i32(cpu_exclusive_high, tmp); + store_reg(s, rt2, tmp); + } + tcg_gen_mov_i32(cpu_exclusive_addr, addr); +} + +static void gen_clrex(DisasContext *s) +{ + tcg_gen_movi_i32(cpu_exclusive_addr, -1); +} + +#ifdef CONFIG_USER_ONLY +static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2, + TCGv addr, int size) +{ + tcg_gen_mov_i32(cpu_exclusive_test, addr); + tcg_gen_movi_i32(cpu_exclusive_info, + size | (rd << 4) | (rt << 8) | (rt2 << 12)); + gen_set_condexec(s); + gen_set_pc_im(s->pc - 4); + gen_exception(EXCP_STREX); + s->is_jmp = DISAS_JUMP; +} +#else +static void gen_store_exclusive(DisasContext *s, int rd, int rt, int rt2, + TCGv addr, int size) +{ + TCGv tmp; + int done_label; + int fail_label; + + /* if (env->exclusive_addr == addr && env->exclusive_val == [addr]) { + [addr] = {Rt}; + {Rd} = 0; + } else { + {Rd} = 1; + } */ + fail_label = gen_new_label(); + done_label = gen_new_label(); + tcg_gen_brcond_i32(TCG_COND_NE, addr, cpu_exclusive_addr, fail_label); + switch (size) { + case 0: + tmp = gen_ld8u(addr, IS_USER(s)); + break; + case 1: + tmp = gen_ld16u(addr, IS_USER(s)); + break; + case 2: + case 3: + tmp = gen_ld32(addr, IS_USER(s)); + break; + default: + abort(); + } + tcg_gen_brcond_i32(TCG_COND_NE, tmp, cpu_exclusive_val, fail_label); + dead_tmp(tmp); + if (size == 3) { + TCGv tmp2 = new_tmp(); + tcg_gen_addi_i32(tmp2, addr, 4); + tmp = gen_ld32(addr, IS_USER(s)); + dead_tmp(tmp2); + tcg_gen_brcond_i32(TCG_COND_NE, tmp, cpu_exclusive_high, fail_label); + dead_tmp(tmp); + } + tmp = load_reg(s, rt); + switch (size) { + case 0: + gen_st8(tmp, addr, IS_USER(s)); + break; + case 1: + gen_st16(tmp, addr, IS_USER(s)); + break; + case 2: + case 3: + gen_st32(tmp, addr, IS_USER(s)); + break; + default: + abort(); + } + if (size == 3) { + tcg_gen_addi_i32(addr, addr, 4); + tmp = load_reg(s, rt2); + gen_st32(tmp, addr, IS_USER(s)); + } + tcg_gen_movi_i32(cpu_R[rd], 0); + tcg_gen_br(done_label); + gen_set_label(fail_label); + tcg_gen_movi_i32(cpu_R[rd], 1); + gen_set_label(done_label); + tcg_gen_movi_i32(cpu_exclusive_addr, -1); +} +#endif + static void disas_arm_insn(CPUState * env, DisasContext *s) { unsigned int cond, insn, val, op1, i, shift, rm, rs, rn, rd, sh; @@ -5869,7 +6014,7 @@ static void disas_arm_insn(CPUState * env, DisasContext *s) switch ((insn >> 4) & 0xf) { case 1: /* clrex */ ARCH(6K); - gen_helper_clrex(cpu_env); + gen_clrex(s); return; case 4: /* dsb */ case 5: /* dmb */ @@ -6454,57 +6599,40 @@ static void disas_arm_insn(CPUState * env, DisasContext *s) addr = tcg_temp_local_new_i32(); load_reg_var(s, addr, rn); if (insn & (1 << 20)) { - gen_helper_mark_exclusive(cpu_env, addr); switch (op1) { case 0: /* ldrex */ - tmp = gen_ld32(addr, IS_USER(s)); + gen_load_exclusive(s, rd, 15, addr, 2); break; case 1: /* ldrexd */ - tmp = gen_ld32(addr, IS_USER(s)); - store_reg(s, rd, tmp); - tcg_gen_addi_i32(addr, addr, 4); - tmp = gen_ld32(addr, IS_USER(s)); - rd++; + gen_load_exclusive(s, rd, rd + 1, addr, 3); break; case 2: /* ldrexb */ - tmp = gen_ld8u(addr, IS_USER(s)); + gen_load_exclusive(s, rd, 15, addr, 0); break; case 3: /* ldrexh */ - tmp = gen_ld16u(addr, IS_USER(s)); + gen_load_exclusive(s, rd, 15, addr, 1); break; default: abort(); } - store_reg(s, rd, tmp); } else { - int label = gen_new_label(); rm = insn & 0xf; - tmp2 = tcg_temp_local_new_i32(); - gen_helper_test_exclusive(tmp2, cpu_env, addr); - tcg_gen_brcondi_i32(TCG_COND_NE, tmp2, 0, label); - tmp = load_reg(s,rm); switch (op1) { case 0: /* strex */ - gen_st32(tmp, addr, IS_USER(s)); + gen_store_exclusive(s, rd, rm, 15, addr, 2); break; case 1: /* strexd */ - gen_st32(tmp, addr, IS_USER(s)); - tcg_gen_addi_i32(addr, addr, 4); - tmp = load_reg(s, rm + 1); - gen_st32(tmp, addr, IS_USER(s)); + gen_store_exclusive(s, rd, rm, rm + 1, addr, 2); break; case 2: /* strexb */ - gen_st8(tmp, addr, IS_USER(s)); + gen_store_exclusive(s, rd, rm, 15, addr, 0); break; case 3: /* strexh */ - gen_st16(tmp, addr, IS_USER(s)); + gen_store_exclusive(s, rd, rm, 15, addr, 1); break; default: abort(); } - gen_set_label(label); - tcg_gen_mov_i32(cpu_R[rd], tmp2); - tcg_temp_free(tmp2); } tcg_temp_free(addr); } else { @@ -7259,20 +7387,11 @@ static int disas_thumb2_insn(CPUState *env, DisasContext *s, uint16_t insn_hw1) /* Load/store exclusive word. */ addr = tcg_temp_local_new(); load_reg_var(s, addr, rn); + tcg_gen_addi_i32(addr, addr, (insn & 0xff) << 2); if (insn & (1 << 20)) { - gen_helper_mark_exclusive(cpu_env, addr); - tmp = gen_ld32(addr, IS_USER(s)); - store_reg(s, rd, tmp); + gen_load_exclusive(s, rs, 15, addr, 2); } else { - int label = gen_new_label(); - tmp2 = tcg_temp_local_new(); - gen_helper_test_exclusive(tmp2, cpu_env, addr); - tcg_gen_brcondi_i32(TCG_COND_NE, tmp2, 0, label); - tmp = load_reg(s, rs); - gen_st32(tmp, addr, IS_USER(s)); - gen_set_label(label); - tcg_gen_mov_i32(cpu_R[rd], tmp2); - tcg_temp_free(tmp2); + gen_store_exclusive(s, rd, rs, 15, addr, 2); } tcg_temp_free(addr); } else if ((insn & (1 << 6)) == 0) { @@ -7300,56 +7419,17 @@ static int disas_thumb2_insn(CPUState *env, DisasContext *s, uint16_t insn_hw1) store_reg(s, 15, tmp); } else { /* Load/store exclusive byte/halfword/doubleword. */ - /* ??? These are not really atomic. However we know - we never have multiple CPUs running in parallel, - so it is good enough. */ + ARCH(7); op = (insn >> 4) & 0x3; + if (op == 2) { + goto illegal_op; + } addr = tcg_temp_local_new(); load_reg_var(s, addr, rn); if (insn & (1 << 20)) { - gen_helper_mark_exclusive(cpu_env, addr); - switch (op) { - case 0: - tmp = gen_ld8u(addr, IS_USER(s)); - break; - case 1: - tmp = gen_ld16u(addr, IS_USER(s)); - break; - case 3: - tmp = gen_ld32(addr, IS_USER(s)); - tcg_gen_addi_i32(addr, addr, 4); - tmp2 = gen_ld32(addr, IS_USER(s)); - store_reg(s, rd, tmp2); - break; - default: - goto illegal_op; - } - store_reg(s, rs, tmp); + gen_load_exclusive(s, rs, rd, addr, op); } else { - int label = gen_new_label(); - tmp2 = tcg_temp_local_new(); - gen_helper_test_exclusive(tmp2, cpu_env, addr); - tcg_gen_brcondi_i32(TCG_COND_NE, tmp2, 0, label); - tmp = load_reg(s, rs); - switch (op) { - case 0: - gen_st8(tmp, addr, IS_USER(s)); - break; - case 1: - gen_st16(tmp, addr, IS_USER(s)); - break; - case 3: - gen_st32(tmp, addr, IS_USER(s)); - tcg_gen_addi_i32(addr, addr, 4); - tmp = load_reg(s, rd); - gen_st32(tmp, addr, IS_USER(s)); - break; - default: - goto illegal_op; - } - gen_set_label(label); - tcg_gen_mov_i32(cpu_R[rm], tmp2); - tcg_temp_free(tmp2); + gen_store_exclusive(s, rm, rs, rd, addr, op); } tcg_temp_free(addr); } @@ -7845,16 +7925,16 @@ static int disas_thumb2_insn(CPUState *env, DisasContext *s, uint16_t insn_hw1) } break; case 3: /* Special control operations. */ + ARCH(7); op = (insn >> 4) & 0xf; switch (op) { case 2: /* clrex */ - gen_helper_clrex(cpu_env); + gen_clrex(s); break; case 4: /* dsb */ case 5: /* dmb */ case 6: /* isb */ /* These execute as NOPs. */ - ARCH(7); break; default: goto illegal_op; |