commit 426f5abcaa15d0e59475fdb33b2a4a5fdf492d05
Author: Paul Brook <paul@codesourcery.com>
Date:   Sun Nov 22 21:35:13 2009 +0000

    ARM atomic ops rewrite
    
    Implement ARMv6 atomic ops (ldrex/strex) using the same trick as PPC.
    
    Signed-off-by: Paul Brook <paul@codesourcery.com>

diff --git a/target-arm/translate.c b/target-arm/translate.c
index a002f7e..45bf772 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;
