| .. | .. |
|---|
| 11 | 11 | #include <linux/bug.h> |
|---|
| 12 | 12 | #include <linux/irqflags.h> |
|---|
| 13 | 13 | #include <asm/compiler.h> |
|---|
| 14 | +#include <asm/llsc.h> |
|---|
| 15 | +#include <asm/sync.h> |
|---|
| 14 | 16 | #include <asm/war.h> |
|---|
| 15 | | - |
|---|
| 16 | | -/* |
|---|
| 17 | | - * Using a branch-likely instruction to check the result of an sc instruction |
|---|
| 18 | | - * works around a bug present in R10000 CPUs prior to revision 3.0 that could |
|---|
| 19 | | - * cause ll-sc sequences to execute non-atomically. |
|---|
| 20 | | - */ |
|---|
| 21 | | -#if R10000_LLSC_WAR |
|---|
| 22 | | -# define __scbeqz "beqzl" |
|---|
| 23 | | -#else |
|---|
| 24 | | -# define __scbeqz "beqz" |
|---|
| 25 | | -#endif |
|---|
| 26 | 17 | |
|---|
| 27 | 18 | /* |
|---|
| 28 | 19 | * These functions doesn't exist, so if they are called you'll either: |
|---|
| .. | .. |
|---|
| 36 | 27 | */ |
|---|
| 37 | 28 | extern unsigned long __cmpxchg_called_with_bad_pointer(void) |
|---|
| 38 | 29 | __compiletime_error("Bad argument size for cmpxchg"); |
|---|
| 30 | +extern unsigned long __cmpxchg64_unsupported(void) |
|---|
| 31 | + __compiletime_error("cmpxchg64 not available; cpu_has_64bits may be false"); |
|---|
| 39 | 32 | extern unsigned long __xchg_called_with_bad_pointer(void) |
|---|
| 40 | 33 | __compiletime_error("Bad argument size for xchg"); |
|---|
| 41 | 34 | |
|---|
| .. | .. |
|---|
| 47 | 40 | __asm__ __volatile__( \ |
|---|
| 48 | 41 | " .set push \n" \ |
|---|
| 49 | 42 | " .set noat \n" \ |
|---|
| 43 | + " .set push \n" \ |
|---|
| 50 | 44 | " .set " MIPS_ISA_ARCH_LEVEL " \n" \ |
|---|
| 45 | + " " __SYNC(full, loongson3_war) " \n" \ |
|---|
| 51 | 46 | "1: " ld " %0, %2 # __xchg_asm \n" \ |
|---|
| 52 | | - " .set mips0 \n" \ |
|---|
| 47 | + " .set pop \n" \ |
|---|
| 53 | 48 | " move $1, %z3 \n" \ |
|---|
| 54 | 49 | " .set " MIPS_ISA_ARCH_LEVEL " \n" \ |
|---|
| 55 | 50 | " " st " $1, %1 \n" \ |
|---|
| 56 | | - "\t" __scbeqz " $1, 1b \n" \ |
|---|
| 51 | + "\t" __SC_BEQZ "$1, 1b \n" \ |
|---|
| 57 | 52 | " .set pop \n" \ |
|---|
| 58 | 53 | : "=&r" (__ret), "=" GCC_OFF_SMALL_ASM() (*m) \ |
|---|
| 59 | 54 | : GCC_OFF_SMALL_ASM() (*m), "Jr" (val) \ |
|---|
| 60 | | - : "memory"); \ |
|---|
| 55 | + : __LLSC_CLOBBER); \ |
|---|
| 61 | 56 | } else { \ |
|---|
| 62 | 57 | unsigned long __flags; \ |
|---|
| 63 | 58 | \ |
|---|
| .. | .. |
|---|
| 99 | 94 | ({ \ |
|---|
| 100 | 95 | __typeof__(*(ptr)) __res; \ |
|---|
| 101 | 96 | \ |
|---|
| 102 | | - smp_mb__before_llsc(); \ |
|---|
| 97 | + /* \ |
|---|
| 98 | + * In the Loongson3 workaround case __xchg_asm() already \ |
|---|
| 99 | + * contains a completion barrier prior to the LL, so we don't \ |
|---|
| 100 | + * need to emit an extra one here. \ |
|---|
| 101 | + */ \ |
|---|
| 102 | + if (__SYNC_loongson3_war == 0) \ |
|---|
| 103 | + smp_mb__before_llsc(); \ |
|---|
| 103 | 104 | \ |
|---|
| 104 | 105 | __res = (__typeof__(*(ptr))) \ |
|---|
| 105 | 106 | __xchg((ptr), (unsigned long)(x), sizeof(*(ptr))); \ |
|---|
| .. | .. |
|---|
| 117 | 118 | __asm__ __volatile__( \ |
|---|
| 118 | 119 | " .set push \n" \ |
|---|
| 119 | 120 | " .set noat \n" \ |
|---|
| 121 | + " .set push \n" \ |
|---|
| 120 | 122 | " .set "MIPS_ISA_ARCH_LEVEL" \n" \ |
|---|
| 123 | + " " __SYNC(full, loongson3_war) " \n" \ |
|---|
| 121 | 124 | "1: " ld " %0, %2 # __cmpxchg_asm \n" \ |
|---|
| 122 | 125 | " bne %0, %z3, 2f \n" \ |
|---|
| 123 | | - " .set mips0 \n" \ |
|---|
| 126 | + " .set pop \n" \ |
|---|
| 124 | 127 | " move $1, %z4 \n" \ |
|---|
| 125 | 128 | " .set "MIPS_ISA_ARCH_LEVEL" \n" \ |
|---|
| 126 | 129 | " " st " $1, %1 \n" \ |
|---|
| 127 | | - "\t" __scbeqz " $1, 1b \n" \ |
|---|
| 130 | + "\t" __SC_BEQZ "$1, 1b \n" \ |
|---|
| 128 | 131 | " .set pop \n" \ |
|---|
| 129 | | - "2: \n" \ |
|---|
| 132 | + "2: " __SYNC(full, loongson3_war) " \n" \ |
|---|
| 130 | 133 | : "=&r" (__ret), "=" GCC_OFF_SMALL_ASM() (*m) \ |
|---|
| 131 | | - : GCC_OFF_SMALL_ASM() (*m), "Jr" (old), "Jr" (new) \ |
|---|
| 132 | | - : "memory"); \ |
|---|
| 134 | + : GCC_OFF_SMALL_ASM() (*m), "Jr" (old), "Jr" (new) \ |
|---|
| 135 | + : __LLSC_CLOBBER); \ |
|---|
| 133 | 136 | } else { \ |
|---|
| 134 | 137 | unsigned long __flags; \ |
|---|
| 135 | 138 | \ |
|---|
| .. | .. |
|---|
| 183 | 186 | ({ \ |
|---|
| 184 | 187 | __typeof__(*(ptr)) __res; \ |
|---|
| 185 | 188 | \ |
|---|
| 186 | | - smp_mb__before_llsc(); \ |
|---|
| 189 | + /* \ |
|---|
| 190 | + * In the Loongson3 workaround case __cmpxchg_asm() already \ |
|---|
| 191 | + * contains a completion barrier prior to the LL, so we don't \ |
|---|
| 192 | + * need to emit an extra one here. \ |
|---|
| 193 | + */ \ |
|---|
| 194 | + if (__SYNC_loongson3_war == 0) \ |
|---|
| 195 | + smp_mb__before_llsc(); \ |
|---|
| 196 | + \ |
|---|
| 187 | 197 | __res = cmpxchg_local((ptr), (old), (new)); \ |
|---|
| 188 | | - smp_llsc_mb(); \ |
|---|
| 198 | + \ |
|---|
| 199 | + /* \ |
|---|
| 200 | + * In the Loongson3 workaround case __cmpxchg_asm() already \ |
|---|
| 201 | + * contains a completion barrier after the SC, so we don't \ |
|---|
| 202 | + * need to emit an extra one here. \ |
|---|
| 203 | + */ \ |
|---|
| 204 | + if (__SYNC_loongson3_war == 0) \ |
|---|
| 205 | + smp_llsc_mb(); \ |
|---|
| 189 | 206 | \ |
|---|
| 190 | 207 | __res; \ |
|---|
| 191 | 208 | }) |
|---|
| .. | .. |
|---|
| 203 | 220 | cmpxchg((ptr), (o), (n)); \ |
|---|
| 204 | 221 | }) |
|---|
| 205 | 222 | #else |
|---|
| 206 | | -#include <asm-generic/cmpxchg-local.h> |
|---|
| 207 | | -#define cmpxchg64_local(ptr, o, n) __cmpxchg64_local_generic((ptr), (o), (n)) |
|---|
| 208 | | -#ifndef CONFIG_SMP |
|---|
| 209 | | -#define cmpxchg64(ptr, o, n) cmpxchg64_local((ptr), (o), (n)) |
|---|
| 210 | | -#endif |
|---|
| 211 | | -#endif |
|---|
| 212 | 223 | |
|---|
| 213 | | -#undef __scbeqz |
|---|
| 224 | +# include <asm-generic/cmpxchg-local.h> |
|---|
| 225 | +# define cmpxchg64_local(ptr, o, n) __cmpxchg64_local_generic((ptr), (o), (n)) |
|---|
| 226 | + |
|---|
| 227 | +# ifdef CONFIG_SMP |
|---|
| 228 | + |
|---|
| 229 | +static inline unsigned long __cmpxchg64(volatile void *ptr, |
|---|
| 230 | + unsigned long long old, |
|---|
| 231 | + unsigned long long new) |
|---|
| 232 | +{ |
|---|
| 233 | + unsigned long long tmp, ret; |
|---|
| 234 | + unsigned long flags; |
|---|
| 235 | + |
|---|
| 236 | + /* |
|---|
| 237 | + * The assembly below has to combine 32 bit values into a 64 bit |
|---|
| 238 | + * register, and split 64 bit values from one register into two. If we |
|---|
| 239 | + * were to take an interrupt in the middle of this we'd only save the |
|---|
| 240 | + * least significant 32 bits of each register & probably clobber the |
|---|
| 241 | + * most significant 32 bits of the 64 bit values we're using. In order |
|---|
| 242 | + * to avoid this we must disable interrupts. |
|---|
| 243 | + */ |
|---|
| 244 | + local_irq_save(flags); |
|---|
| 245 | + |
|---|
| 246 | + asm volatile( |
|---|
| 247 | + " .set push \n" |
|---|
| 248 | + " .set " MIPS_ISA_ARCH_LEVEL " \n" |
|---|
| 249 | + /* Load 64 bits from ptr */ |
|---|
| 250 | + " " __SYNC(full, loongson3_war) " \n" |
|---|
| 251 | + "1: lld %L0, %3 # __cmpxchg64 \n" |
|---|
| 252 | + " .set pop \n" |
|---|
| 253 | + /* |
|---|
| 254 | + * Split the 64 bit value we loaded into the 2 registers that hold the |
|---|
| 255 | + * ret variable. |
|---|
| 256 | + */ |
|---|
| 257 | + " dsra %M0, %L0, 32 \n" |
|---|
| 258 | + " sll %L0, %L0, 0 \n" |
|---|
| 259 | + /* |
|---|
| 260 | + * Compare ret against old, breaking out of the loop if they don't |
|---|
| 261 | + * match. |
|---|
| 262 | + */ |
|---|
| 263 | + " bne %M0, %M4, 2f \n" |
|---|
| 264 | + " bne %L0, %L4, 2f \n" |
|---|
| 265 | + /* |
|---|
| 266 | + * Combine the 32 bit halves from the 2 registers that hold the new |
|---|
| 267 | + * variable into a single 64 bit register. |
|---|
| 268 | + */ |
|---|
| 269 | +# if MIPS_ISA_REV >= 2 |
|---|
| 270 | + " move %L1, %L5 \n" |
|---|
| 271 | + " dins %L1, %M5, 32, 32 \n" |
|---|
| 272 | +# else |
|---|
| 273 | + " dsll %L1, %L5, 32 \n" |
|---|
| 274 | + " dsrl %L1, %L1, 32 \n" |
|---|
| 275 | + " .set noat \n" |
|---|
| 276 | + " dsll $at, %M5, 32 \n" |
|---|
| 277 | + " or %L1, %L1, $at \n" |
|---|
| 278 | + " .set at \n" |
|---|
| 279 | +# endif |
|---|
| 280 | + " .set push \n" |
|---|
| 281 | + " .set " MIPS_ISA_ARCH_LEVEL " \n" |
|---|
| 282 | + /* Attempt to store new at ptr */ |
|---|
| 283 | + " scd %L1, %2 \n" |
|---|
| 284 | + /* If we failed, loop! */ |
|---|
| 285 | + "\t" __SC_BEQZ "%L1, 1b \n" |
|---|
| 286 | + "2: " __SYNC(full, loongson3_war) " \n" |
|---|
| 287 | + " .set pop \n" |
|---|
| 288 | + : "=&r"(ret), |
|---|
| 289 | + "=&r"(tmp), |
|---|
| 290 | + "=" GCC_OFF_SMALL_ASM() (*(unsigned long long *)ptr) |
|---|
| 291 | + : GCC_OFF_SMALL_ASM() (*(unsigned long long *)ptr), |
|---|
| 292 | + "r" (old), |
|---|
| 293 | + "r" (new) |
|---|
| 294 | + : "memory"); |
|---|
| 295 | + |
|---|
| 296 | + local_irq_restore(flags); |
|---|
| 297 | + return ret; |
|---|
| 298 | +} |
|---|
| 299 | + |
|---|
| 300 | +# define cmpxchg64(ptr, o, n) ({ \ |
|---|
| 301 | + unsigned long long __old = (__typeof__(*(ptr)))(o); \ |
|---|
| 302 | + unsigned long long __new = (__typeof__(*(ptr)))(n); \ |
|---|
| 303 | + __typeof__(*(ptr)) __res; \ |
|---|
| 304 | + \ |
|---|
| 305 | + /* \ |
|---|
| 306 | + * We can only use cmpxchg64 if we know that the CPU supports \ |
|---|
| 307 | + * 64-bits, ie. lld & scd. Our call to __cmpxchg64_unsupported \ |
|---|
| 308 | + * will cause a build error unless cpu_has_64bits is a \ |
|---|
| 309 | + * compile-time constant 1. \ |
|---|
| 310 | + */ \ |
|---|
| 311 | + if (cpu_has_64bits && kernel_uses_llsc) { \ |
|---|
| 312 | + smp_mb__before_llsc(); \ |
|---|
| 313 | + __res = __cmpxchg64((ptr), __old, __new); \ |
|---|
| 314 | + smp_llsc_mb(); \ |
|---|
| 315 | + } else { \ |
|---|
| 316 | + __res = __cmpxchg64_unsupported(); \ |
|---|
| 317 | + } \ |
|---|
| 318 | + \ |
|---|
| 319 | + __res; \ |
|---|
| 320 | +}) |
|---|
| 321 | + |
|---|
| 322 | +# else /* !CONFIG_SMP */ |
|---|
| 323 | +# define cmpxchg64(ptr, o, n) cmpxchg64_local((ptr), (o), (n)) |
|---|
| 324 | +# endif /* !CONFIG_SMP */ |
|---|
| 325 | +#endif /* !CONFIG_64BIT */ |
|---|
| 214 | 326 | |
|---|
| 215 | 327 | #endif /* __ASM_CMPXCHG_H */ |
|---|