1 /* SPDX-License-Identifier: GPL-2.0-only */ 2 /* 3 * Copyright (C) 2004, 2007-2010, 2011-2012 Synopsys, Inc. (www.synopsys.com) 4 */ 5 6 #ifndef _ASM_ARC_ATOMIC_H 7 #define _ASM_ARC_ATOMIC_H 8 9 #ifndef __ASSEMBLY__ 10 11 #include <linux/types.h> 12 #include <linux/compiler.h> 13 #include <asm/cmpxchg.h> 14 #include <asm/barrier.h> 15 #include <asm/smp.h> 16 17 #define atomic_read(v) READ_ONCE((v)->counter) 18 19 #ifdef CONFIG_ARC_HAS_LLSC 20 21 #define atomic_set(v, i) WRITE_ONCE(((v)->counter), (i)) 22 23 #define ATOMIC_OP(op, c_op, asm_op) \ 24 static inline void atomic_##op(int i, atomic_t *v) \ 25 { \ 26 unsigned int val; \ 27 \ 28 __asm__ __volatile__( \ 29 "1: llock %[val], [%[ctr]] \n" \ 30 " " #asm_op " %[val], %[val], %[i] \n" \ 31 " scond %[val], [%[ctr]] \n" \ 32 " bnz 1b \n" \ 33 : [val] "=&r" (val) /* Early clobber to prevent reg reuse */ \ 34 : [ctr] "r" (&v->counter), /* Not "m": llock only supports reg direct addr mode */ \ 35 [i] "ir" (i) \ 36 : "cc"); \ 37 } \ 38 39 #define ATOMIC_OP_RETURN(op, c_op, asm_op) \ 40 static inline int atomic_##op##_return(int i, atomic_t *v) \ 41 { \ 42 unsigned int val; \ 43 \ 44 /* \ 45 * Explicit full memory barrier needed before/after as \ 46 * LLOCK/SCOND themselves don't provide any such semantics \ 47 */ \ 48 smp_mb(); \ 49 \ 50 __asm__ __volatile__( \ 51 "1: llock %[val], [%[ctr]] \n" \ 52 " " #asm_op " %[val], %[val], %[i] \n" \ 53 " scond %[val], [%[ctr]] \n" \ 54 " bnz 1b \n" \ 55 : [val] "=&r" (val) \ 56 : [ctr] "r" (&v->counter), \ 57 [i] "ir" (i) \ 58 : "cc"); \ 59 \ 60 smp_mb(); \ 61 \ 62 return val; \ 63 } 64 65 #define ATOMIC_FETCH_OP(op, c_op, asm_op) \ 66 static inline int atomic_fetch_##op(int i, atomic_t *v) \ 67 { \ 68 unsigned int val, orig; \ 69 \ 70 /* \ 71 * Explicit full memory barrier needed before/after as \ 72 * LLOCK/SCOND themselves don't provide any such semantics \ 73 */ \ 74 smp_mb(); \ 75 \ 76 __asm__ __volatile__( \ 77 "1: llock %[orig], [%[ctr]] \n" \ 78 " " #asm_op " %[val], %[orig], %[i] \n" \ 79 " scond %[val], [%[ctr]] \n" \ 80 " bnz 1b \n" \ 81 : [val] "=&r" (val), \ 82 [orig] "=&r" (orig) \ 83 : [ctr] "r" (&v->counter), \ 84 [i] "ir" (i) \ 85 : "cc"); \ 86 \ 87 smp_mb(); \ 88 \ 89 return orig; \ 90 } 91 92 #else /* !CONFIG_ARC_HAS_LLSC */ 93 94 #ifndef CONFIG_SMP 95 96 /* violating atomic_xxx API locking protocol in UP for optimization sake */ 97 #define atomic_set(v, i) WRITE_ONCE(((v)->counter), (i)) 98 99 #else 100 101 static inline void atomic_set(atomic_t *v, int i) 102 { 103 /* 104 * Independent of hardware support, all of the atomic_xxx() APIs need 105 * to follow the same locking rules to make sure that a "hardware" 106 * atomic insn (e.g. LD) doesn't clobber an "emulated" atomic insn 107 * sequence 108 * 109 * Thus atomic_set() despite being 1 insn (and seemingly atomic) 110 * requires the locking. 111 */ 112 unsigned long flags; 113 114 atomic_ops_lock(flags); 115 WRITE_ONCE(v->counter, i); 116 atomic_ops_unlock(flags); 117 } 118 119 #define atomic_set_release(v, i) atomic_set((v), (i)) 120 121 #endif 122 123 /* 124 * Non hardware assisted Atomic-R-M-W 125 * Locking would change to irq-disabling only (UP) and spinlocks (SMP) 126 */ 127 128 #define ATOMIC_OP(op, c_op, asm_op) \ 129 static inline void atomic_##op(int i, atomic_t *v) \ 130 { \ 131 unsigned long flags; \ 132 \ 133 atomic_ops_lock(flags); \ 134 v->counter c_op i; \ 135 atomic_ops_unlock(flags); \ 136 } 137 138 #define ATOMIC_OP_RETURN(op, c_op, asm_op) \ 139 static inline int atomic_##op##_return(int i, atomic_t *v) \ 140 { \ 141 unsigned long flags; \ 142 unsigned long temp; \ 143 \ 144 /* \ 145 * spin lock/unlock provides the needed smp_mb() before/after \ 146 */ \ 147 atomic_ops_lock(flags); \ 148 temp = v->counter; \ 149 temp c_op i; \ 150 v->counter = temp; \ 151 atomic_ops_unlock(flags); \ 152 \ 153 return temp; \ 154 } 155 156 #define ATOMIC_FETCH_OP(op, c_op, asm_op) \ 157 static inline int atomic_fetch_##op(int i, atomic_t *v) \ 158 { \ 159 unsigned long flags; \ 160 unsigned long orig; \ 161 \ 162 /* \ 163 * spin lock/unlock provides the needed smp_mb() before/after \ 164 */ \ 165 atomic_ops_lock(flags); \ 166 orig = v->counter; \ 167 v->counter c_op i; \ 168 atomic_ops_unlock(flags); \ 169 \ 170 return orig; \ 171 } 172 173 #endif /* !CONFIG_ARC_HAS_LLSC */ 174 175 #define ATOMIC_OPS(op, c_op, asm_op) \ 176 ATOMIC_OP(op, c_op, asm_op) \ 177 ATOMIC_OP_RETURN(op, c_op, asm_op) \ 178 ATOMIC_FETCH_OP(op, c_op, asm_op) 179 180 ATOMIC_OPS(add, +=, add) 181 ATOMIC_OPS(sub, -=, sub) 182 183 #define atomic_andnot atomic_andnot 184 #define atomic_fetch_andnot atomic_fetch_andnot 185 186 #undef ATOMIC_OPS 187 #define ATOMIC_OPS(op, c_op, asm_op) \ 188 ATOMIC_OP(op, c_op, asm_op) \ 189 ATOMIC_FETCH_OP(op, c_op, asm_op) 190 191 ATOMIC_OPS(and, &=, and) 192 ATOMIC_OPS(andnot, &= ~, bic) 193 ATOMIC_OPS(or, |=, or) 194 ATOMIC_OPS(xor, ^=, xor) 195 196 #undef ATOMIC_OPS 197 #undef ATOMIC_FETCH_OP 198 #undef ATOMIC_OP_RETURN 199 #undef ATOMIC_OP 200 201 #ifdef CONFIG_GENERIC_ATOMIC64 202 203 #include <asm-generic/atomic64.h> 204 205 #else /* Kconfig ensures this is only enabled with needed h/w assist */ 206 207 /* 208 * ARCv2 supports 64-bit exclusive load (LLOCKD) / store (SCONDD) 209 * - The address HAS to be 64-bit aligned 210 * - There are 2 semantics involved here: 211 * = exclusive implies no interim update between load/store to same addr 212 * = both words are observed/updated together: this is guaranteed even 213 * for regular 64-bit load (LDD) / store (STD). Thus atomic64_set() 214 * is NOT required to use LLOCKD+SCONDD, STD suffices 215 */ 216 217 typedef struct { 218 s64 __aligned(8) counter; 219 } atomic64_t; 220 221 #define ATOMIC64_INIT(a) { (a) } 222 223 static inline s64 atomic64_read(const atomic64_t *v) 224 { 225 s64 val; 226 227 __asm__ __volatile__( 228 " ldd %0, [%1] \n" 229 : "=r"(val) 230 : "r"(&v->counter)); 231 232 return val; 233 } 234 235 static inline void atomic64_set(atomic64_t *v, s64 a) 236 { 237 /* 238 * This could have been a simple assignment in "C" but would need 239 * explicit volatile. Otherwise gcc optimizers could elide the store 240 * which borked atomic64 self-test 241 * In the inline asm version, memory clobber needed for exact same 242 * reason, to tell gcc about the store. 243 * 244 * This however is not needed for sibling atomic64_add() etc since both 245 * load/store are explicitly done in inline asm. As long as API is used 246 * for each access, gcc has no way to optimize away any load/store 247 */ 248 __asm__ __volatile__( 249 " std %0, [%1] \n" 250 : 251 : "r"(a), "r"(&v->counter) 252 : "memory"); 253 } 254 255 #define ATOMIC64_OP(op, op1, op2) \ 256 static inline void atomic64_##op(s64 a, atomic64_t *v) \ 257 { \ 258 s64 val; \ 259 \ 260 __asm__ __volatile__( \ 261 "1: \n" \ 262 " llockd %0, [%1] \n" \ 263 " " #op1 " %L0, %L0, %L2 \n" \ 264 " " #op2 " %H0, %H0, %H2 \n" \ 265 " scondd %0, [%1] \n" \ 266 " bnz 1b \n" \ 267 : "=&r"(val) \ 268 : "r"(&v->counter), "ir"(a) \ 269 : "cc"); \ 270 } \ 271 272 #define ATOMIC64_OP_RETURN(op, op1, op2) \ 273 static inline s64 atomic64_##op##_return(s64 a, atomic64_t *v) \ 274 { \ 275 s64 val; \ 276 \ 277 smp_mb(); \ 278 \ 279 __asm__ __volatile__( \ 280 "1: \n" \ 281 " llockd %0, [%1] \n" \ 282 " " #op1 " %L0, %L0, %L2 \n" \ 283 " " #op2 " %H0, %H0, %H2 \n" \ 284 " scondd %0, [%1] \n" \ 285 " bnz 1b \n" \ 286 : [val] "=&r"(val) \ 287 : "r"(&v->counter), "ir"(a) \ 288 : "cc"); /* memory clobber comes from smp_mb() */ \ 289 \ 290 smp_mb(); \ 291 \ 292 return val; \ 293 } 294 295 #define ATOMIC64_FETCH_OP(op, op1, op2) \ 296 static inline s64 atomic64_fetch_##op(s64 a, atomic64_t *v) \ 297 { \ 298 s64 val, orig; \ 299 \ 300 smp_mb(); \ 301 \ 302 __asm__ __volatile__( \ 303 "1: \n" \ 304 " llockd %0, [%2] \n" \ 305 " " #op1 " %L1, %L0, %L3 \n" \ 306 " " #op2 " %H1, %H0, %H3 \n" \ 307 " scondd %1, [%2] \n" \ 308 " bnz 1b \n" \ 309 : "=&r"(orig), "=&r"(val) \ 310 : "r"(&v->counter), "ir"(a) \ 311 : "cc"); /* memory clobber comes from smp_mb() */ \ 312 \ 313 smp_mb(); \ 314 \ 315 return orig; \ 316 } 317 318 #define ATOMIC64_OPS(op, op1, op2) \ 319 ATOMIC64_OP(op, op1, op2) \ 320 ATOMIC64_OP_RETURN(op, op1, op2) \ 321 ATOMIC64_FETCH_OP(op, op1, op2) 322 323 #define atomic64_andnot atomic64_andnot 324 #define atomic64_fetch_andnot atomic64_fetch_andnot 325 326 ATOMIC64_OPS(add, add.f, adc) 327 ATOMIC64_OPS(sub, sub.f, sbc) 328 ATOMIC64_OPS(and, and, and) 329 ATOMIC64_OPS(andnot, bic, bic) 330 ATOMIC64_OPS(or, or, or) 331 ATOMIC64_OPS(xor, xor, xor) 332 333 #undef ATOMIC64_OPS 334 #undef ATOMIC64_FETCH_OP 335 #undef ATOMIC64_OP_RETURN 336 #undef ATOMIC64_OP 337 338 static inline s64 339 atomic64_cmpxchg(atomic64_t *ptr, s64 expected, s64 new) 340 { 341 s64 prev; 342 343 smp_mb(); 344 345 __asm__ __volatile__( 346 "1: llockd %0, [%1] \n" 347 " brne %L0, %L2, 2f \n" 348 " brne %H0, %H2, 2f \n" 349 " scondd %3, [%1] \n" 350 " bnz 1b \n" 351 "2: \n" 352 : "=&r"(prev) 353 : "r"(ptr), "ir"(expected), "r"(new) 354 : "cc"); /* memory clobber comes from smp_mb() */ 355 356 smp_mb(); 357 358 return prev; 359 } 360 361 static inline s64 atomic64_xchg(atomic64_t *ptr, s64 new) 362 { 363 s64 prev; 364 365 smp_mb(); 366 367 __asm__ __volatile__( 368 "1: llockd %0, [%1] \n" 369 " scondd %2, [%1] \n" 370 " bnz 1b \n" 371 "2: \n" 372 : "=&r"(prev) 373 : "r"(ptr), "r"(new) 374 : "cc"); /* memory clobber comes from smp_mb() */ 375 376 smp_mb(); 377 378 return prev; 379 } 380 381 /** 382 * atomic64_dec_if_positive - decrement by 1 if old value positive 383 * @v: pointer of type atomic64_t 384 * 385 * The function returns the old value of *v minus 1, even if 386 * the atomic variable, v, was not decremented. 387 */ 388 389 static inline s64 atomic64_dec_if_positive(atomic64_t *v) 390 { 391 s64 val; 392 393 smp_mb(); 394 395 __asm__ __volatile__( 396 "1: llockd %0, [%1] \n" 397 " sub.f %L0, %L0, 1 # w0 - 1, set C on borrow\n" 398 " sub.c %H0, %H0, 1 # if C set, w1 - 1\n" 399 " brlt %H0, 0, 2f \n" 400 " scondd %0, [%1] \n" 401 " bnz 1b \n" 402 "2: \n" 403 : "=&r"(val) 404 : "r"(&v->counter) 405 : "cc"); /* memory clobber comes from smp_mb() */ 406 407 smp_mb(); 408 409 return val; 410 } 411 #define atomic64_dec_if_positive atomic64_dec_if_positive 412 413 /** 414 * atomic64_fetch_add_unless - add unless the number is a given value 415 * @v: pointer of type atomic64_t 416 * @a: the amount to add to v... 417 * @u: ...unless v is equal to u. 418 * 419 * Atomically adds @a to @v, if it was not @u. 420 * Returns the old value of @v 421 */ 422 static inline s64 atomic64_fetch_add_unless(atomic64_t *v, s64 a, s64 u) 423 { 424 s64 old, temp; 425 426 smp_mb(); 427 428 __asm__ __volatile__( 429 "1: llockd %0, [%2] \n" 430 " brne %L0, %L4, 2f # continue to add since v != u \n" 431 " breq.d %H0, %H4, 3f # return since v == u \n" 432 "2: \n" 433 " add.f %L1, %L0, %L3 \n" 434 " adc %H1, %H0, %H3 \n" 435 " scondd %1, [%2] \n" 436 " bnz 1b \n" 437 "3: \n" 438 : "=&r"(old), "=&r" (temp) 439 : "r"(&v->counter), "r"(a), "r"(u) 440 : "cc"); /* memory clobber comes from smp_mb() */ 441 442 smp_mb(); 443 444 return old; 445 } 446 #define atomic64_fetch_add_unless atomic64_fetch_add_unless 447 448 #endif /* !CONFIG_GENERIC_ATOMIC64 */ 449 450 #endif /* !__ASSEMBLY__ */ 451 452 #endif 453