| 1 | /* |
| 2 | * Copyright (c) 2007 by NEC LE-IT: All rights reserved. |
| 3 | * A transcription of ARMv6 atomic operations for the ARM Realview Toolchain. |
| 4 | * This code works with armcc from RVDS 3.1 |
| 5 | * This is based on work in gcc/arm.h by |
| 6 | * Copyright (c) 1991-1994 by Xerox Corporation. All rights reserved. |
| 7 | * Copyright (c) 1996-1999 by Silicon Graphics. All rights reserved. |
| 8 | * Copyright (c) 1999-2003 by Hewlett-Packard Company. All rights reserved. |
| 9 | * |
| 10 | * |
| 11 | * |
| 12 | * THIS MATERIAL IS PROVIDED AS IS, WITH ABSOLUTELY NO WARRANTY EXPRESSED |
| 13 | * OR IMPLIED. ANY USE IS AT YOUR OWN RISK. |
| 14 | * |
| 15 | * Permission is hereby granted to use or copy this program |
| 16 | * for any purpose, provided the above notices are retained on all copies. |
| 17 | * Permission to modify the code and to distribute modified code is granted, |
| 18 | * provided the above notices are retained, and a notice that the code was |
| 19 | * modified is included with the above copyright notice. |
| 20 | * |
| 21 | */ |
| 22 | |
| 23 | #include "../test_and_set_t_is_ao_t.h" /* Probably suboptimal */ |
| 24 | |
| 25 | #if __TARGET_ARCH_ARM < 6 |
| 26 | # if !defined(CPPCHECK) |
| 27 | # error Do not use with ARM instruction sets lower than v6 |
| 28 | # endif |
| 29 | #else |
| 30 | |
| 31 | #define AO_ACCESS_CHECK_ALIGNED |
| 32 | #define AO_ACCESS_short_CHECK_ALIGNED |
| 33 | #define AO_ACCESS_int_CHECK_ALIGNED |
| 34 | #include "../all_atomic_only_load.h" |
| 35 | |
| 36 | #include "../standard_ao_double_t.h" |
| 37 | |
| 38 | /* NEC LE-IT: ARMv6 is the first architecture providing support for simple LL/SC |
| 39 | * A data memory barrier must be raised via CP15 command (see documentation). |
| 40 | * |
| 41 | * ARMv7 is compatible to ARMv6 but has a simpler command for issuing a |
| 42 | * memory barrier (DMB). Raising it via CP15 should still work as told me by the |
| 43 | * support engineers. If it turns out to be much quicker than we should implement |
| 44 | * custom code for ARMv7 using the asm { dmb } command. |
| 45 | * |
| 46 | * If only a single processor is used, we can define AO_UNIPROCESSOR |
| 47 | * and do not need to access CP15 for ensuring a DMB at all. |
| 48 | */ |
| 49 | |
| 50 | AO_INLINE void |
| 51 | AO_nop_full(void) |
| 52 | { |
| 53 | # ifndef AO_UNIPROCESSOR |
| 54 | unsigned int dest=0; |
| 55 | /* Issue a data memory barrier (keeps ordering of memory transactions */ |
| 56 | /* before and after this operation). */ |
| 57 | __asm { |
| 58 | mcr p15,0,dest,c7,c10,5 |
| 59 | }; |
| 60 | # else |
| 61 | AO_compiler_barrier(); |
| 62 | # endif |
| 63 | } |
| 64 | #define AO_HAVE_nop_full |
| 65 | |
| 66 | /* NEC LE-IT: atomic "store" - according to ARM documentation this is |
| 67 | * the only safe way to set variables also used in LL/SC environment. |
| 68 | * A direct write won't be recognized by the LL/SC construct in other CPUs. |
| 69 | * |
| 70 | * HB: Based on subsequent discussion, I think it would be OK to use an |
| 71 | * ordinary store here if we knew that interrupt handlers always cleared |
| 72 | * the reservation. They should, but there is some doubt that this is |
| 73 | * currently always the case for e.g. Linux. |
| 74 | */ |
| 75 | AO_INLINE void AO_store(volatile AO_t *addr, AO_t value) |
| 76 | { |
| 77 | unsigned long tmp; |
| 78 | |
| 79 | retry: |
| 80 | __asm { |
| 81 | ldrex tmp, [addr] |
| 82 | strex tmp, value, [addr] |
| 83 | teq tmp, #0 |
| 84 | bne retry |
| 85 | }; |
| 86 | } |
| 87 | #define AO_HAVE_store |
| 88 | |
| 89 | /* NEC LE-IT: replace the SWAP as recommended by ARM: |
| 90 | |
| 91 | "Applies to: ARM11 Cores |
| 92 | Though the SWP instruction will still work with ARM V6 cores, it is recommended |
| 93 | to use the new V6 synchronization instructions. The SWP instruction produces |
| 94 | locked read and write accesses which are atomic, i.e. another operation cannot |
| 95 | be done between these locked accesses which ties up external bus (AHB,AXI) |
| 96 | bandwidth and can increase worst case interrupt latencies. LDREX,STREX are |
| 97 | more flexible, other instructions can be done between the LDREX and STREX accesses. |
| 98 | " |
| 99 | */ |
| 100 | #ifndef AO_PREFER_GENERALIZED |
| 101 | AO_INLINE AO_TS_VAL_t |
| 102 | AO_test_and_set(volatile AO_TS_t *addr) { |
| 103 | AO_TS_VAL_t oldval; |
| 104 | unsigned long tmp; |
| 105 | unsigned long one = 1; |
| 106 | retry: |
| 107 | __asm { |
| 108 | ldrex oldval, [addr] |
| 109 | strex tmp, one, [addr] |
| 110 | teq tmp, #0 |
| 111 | bne retry |
| 112 | } |
| 113 | |
| 114 | return oldval; |
| 115 | } |
| 116 | #define AO_HAVE_test_and_set |
| 117 | |
| 118 | AO_INLINE AO_t |
| 119 | AO_fetch_and_add(volatile AO_t *p, AO_t incr) |
| 120 | { |
| 121 | unsigned long tmp,tmp2; |
| 122 | AO_t result; |
| 123 | |
| 124 | retry: |
| 125 | __asm { |
| 126 | ldrex result, [p] |
| 127 | add tmp, incr, result |
| 128 | strex tmp2, tmp, [p] |
| 129 | teq tmp2, #0 |
| 130 | bne retry |
| 131 | } |
| 132 | |
| 133 | return result; |
| 134 | } |
| 135 | #define AO_HAVE_fetch_and_add |
| 136 | |
| 137 | AO_INLINE AO_t |
| 138 | AO_fetch_and_add1(volatile AO_t *p) |
| 139 | { |
| 140 | unsigned long tmp,tmp2; |
| 141 | AO_t result; |
| 142 | |
| 143 | retry: |
| 144 | __asm { |
| 145 | ldrex result, [p] |
| 146 | add tmp, result, #1 |
| 147 | strex tmp2, tmp, [p] |
| 148 | teq tmp2, #0 |
| 149 | bne retry |
| 150 | } |
| 151 | |
| 152 | return result; |
| 153 | } |
| 154 | #define AO_HAVE_fetch_and_add1 |
| 155 | |
| 156 | AO_INLINE AO_t |
| 157 | AO_fetch_and_sub1(volatile AO_t *p) |
| 158 | { |
| 159 | unsigned long tmp,tmp2; |
| 160 | AO_t result; |
| 161 | |
| 162 | retry: |
| 163 | __asm { |
| 164 | ldrex result, [p] |
| 165 | sub tmp, result, #1 |
| 166 | strex tmp2, tmp, [p] |
| 167 | teq tmp2, #0 |
| 168 | bne retry |
| 169 | } |
| 170 | |
| 171 | return result; |
| 172 | } |
| 173 | #define AO_HAVE_fetch_and_sub1 |
| 174 | #endif /* !AO_PREFER_GENERALIZED */ |
| 175 | |
| 176 | #ifndef AO_GENERALIZE_ASM_BOOL_CAS |
| 177 | /* Returns nonzero if the comparison succeeded. */ |
| 178 | AO_INLINE int |
| 179 | AO_compare_and_swap(volatile AO_t *addr, AO_t old_val, AO_t new_val) |
| 180 | { |
| 181 | AO_t result, tmp; |
| 182 | |
| 183 | retry: |
| 184 | __asm__ { |
| 185 | mov result, #2 |
| 186 | ldrex tmp, [addr] |
| 187 | teq tmp, old_val |
| 188 | # ifdef __thumb__ |
| 189 | it eq |
| 190 | # endif |
| 191 | strexeq result, new_val, [addr] |
| 192 | teq result, #1 |
| 193 | beq retry |
| 194 | } |
| 195 | return !(result&2); |
| 196 | } |
| 197 | # define AO_HAVE_compare_and_swap |
| 198 | #endif /* !AO_GENERALIZE_ASM_BOOL_CAS */ |
| 199 | |
| 200 | AO_INLINE AO_t |
| 201 | AO_fetch_compare_and_swap(volatile AO_t *addr, AO_t old_val, AO_t new_val) |
| 202 | { |
| 203 | AO_t fetched_val, tmp; |
| 204 | |
| 205 | retry: |
| 206 | __asm__ { |
| 207 | mov tmp, #2 |
| 208 | ldrex fetched_val, [addr] |
| 209 | teq fetched_val, old_val |
| 210 | # ifdef __thumb__ |
| 211 | it eq |
| 212 | # endif |
| 213 | strexeq tmp, new_val, [addr] |
| 214 | teq tmp, #1 |
| 215 | beq retry |
| 216 | } |
| 217 | return fetched_val; |
| 218 | } |
| 219 | #define AO_HAVE_fetch_compare_and_swap |
| 220 | |
| 221 | /* helper functions for the Realview compiler: LDREXD is not usable |
| 222 | * with inline assembler, so use the "embedded" assembler as |
| 223 | * suggested by ARM Dev. support (June 2008). */ |
| 224 | __asm inline double_ptr_storage AO_load_ex(const volatile AO_double_t *addr) { |
| 225 | LDREXD r0,r1,[r0] |
| 226 | } |
| 227 | |
| 228 | __asm inline int AO_store_ex(AO_t val1, AO_t val2, volatile AO_double_t *addr) { |
| 229 | STREXD r3,r0,r1,[r2] |
| 230 | MOV r0,r3 |
| 231 | } |
| 232 | |
| 233 | AO_INLINE AO_double_t |
| 234 | AO_double_load(const volatile AO_double_t *addr) |
| 235 | { |
| 236 | AO_double_t result; |
| 237 | |
| 238 | result.AO_whole = AO_load_ex(addr); |
| 239 | return result; |
| 240 | } |
| 241 | #define AO_HAVE_double_load |
| 242 | |
| 243 | AO_INLINE int |
| 244 | AO_compare_double_and_swap_double(volatile AO_double_t *addr, |
| 245 | AO_t old_val1, AO_t old_val2, |
| 246 | AO_t new_val1, AO_t new_val2) |
| 247 | { |
| 248 | double_ptr_storage old_val = |
| 249 | ((double_ptr_storage)old_val2 << 32) | old_val1; |
| 250 | double_ptr_storage tmp; |
| 251 | int result; |
| 252 | |
| 253 | while(1) { |
| 254 | tmp = AO_load_ex(addr); |
| 255 | if(tmp != old_val) return 0; |
| 256 | result = AO_store_ex(new_val1, new_val2, addr); |
| 257 | if(!result) return 1; |
| 258 | } |
| 259 | } |
| 260 | #define AO_HAVE_compare_double_and_swap_double |
| 261 | |
| 262 | #endif /* __TARGET_ARCH_ARM >= 6 */ |
| 263 | |
| 264 | #define AO_T_IS_INT |
| 265 | |