at v5.4-rc4 441 lines 12 kB view raw
1/* 2 * Atomic operations that C can't guarantee us. Useful for 3 * resource counting etc.. 4 * 5 * But use these as seldom as possible since they are much more slower 6 * than regular operations. 7 * 8 * This file is subject to the terms and conditions of the GNU General Public 9 * License. See the file "COPYING" in the main directory of this archive 10 * for more details. 11 * 12 * Copyright (C) 1996, 97, 99, 2000, 03, 04, 06 by Ralf Baechle 13 */ 14#ifndef _ASM_ATOMIC_H 15#define _ASM_ATOMIC_H 16 17#include <linux/irqflags.h> 18#include <linux/types.h> 19#include <asm/barrier.h> 20#include <asm/compiler.h> 21#include <asm/cpu-features.h> 22#include <asm/cmpxchg.h> 23#include <asm/war.h> 24 25/* 26 * Using a branch-likely instruction to check the result of an sc instruction 27 * works around a bug present in R10000 CPUs prior to revision 3.0 that could 28 * cause ll-sc sequences to execute non-atomically. 29 */ 30#if R10000_LLSC_WAR 31# define __scbeqz "beqzl" 32#else 33# define __scbeqz "beqz" 34#endif 35 36#define ATOMIC_INIT(i) { (i) } 37 38/* 39 * atomic_read - read atomic variable 40 * @v: pointer of type atomic_t 41 * 42 * Atomically reads the value of @v. 43 */ 44#define atomic_read(v) READ_ONCE((v)->counter) 45 46/* 47 * atomic_set - set atomic variable 48 * @v: pointer of type atomic_t 49 * @i: required value 50 * 51 * Atomically sets the value of @v to @i. 52 */ 53#define atomic_set(v, i) WRITE_ONCE((v)->counter, (i)) 54 55#define ATOMIC_OP(op, c_op, asm_op) \ 56static __inline__ void atomic_##op(int i, atomic_t * v) \ 57{ \ 58 if (kernel_uses_llsc) { \ 59 int temp; \ 60 \ 61 loongson_llsc_mb(); \ 62 __asm__ __volatile__( \ 63 " .set push \n" \ 64 " .set "MIPS_ISA_LEVEL" \n" \ 65 "1: ll %0, %1 # atomic_" #op " \n" \ 66 " " #asm_op " %0, %2 \n" \ 67 " sc %0, %1 \n" \ 68 "\t" __scbeqz " %0, 1b \n" \ 69 " .set pop \n" \ 70 : "=&r" (temp), "+" GCC_OFF_SMALL_ASM() (v->counter) \ 71 : "Ir" (i) : __LLSC_CLOBBER); \ 72 } else { \ 73 unsigned long flags; \ 74 \ 75 raw_local_irq_save(flags); \ 76 v->counter c_op i; \ 77 raw_local_irq_restore(flags); \ 78 } \ 79} 80 81#define ATOMIC_OP_RETURN(op, c_op, asm_op) \ 82static __inline__ int atomic_##op##_return_relaxed(int i, atomic_t * v) \ 83{ \ 84 int result; \ 85 \ 86 if (kernel_uses_llsc) { \ 87 int temp; \ 88 \ 89 loongson_llsc_mb(); \ 90 __asm__ __volatile__( \ 91 " .set push \n" \ 92 " .set "MIPS_ISA_LEVEL" \n" \ 93 "1: ll %1, %2 # atomic_" #op "_return \n" \ 94 " " #asm_op " %0, %1, %3 \n" \ 95 " sc %0, %2 \n" \ 96 "\t" __scbeqz " %0, 1b \n" \ 97 " " #asm_op " %0, %1, %3 \n" \ 98 " .set pop \n" \ 99 : "=&r" (result), "=&r" (temp), \ 100 "+" GCC_OFF_SMALL_ASM() (v->counter) \ 101 : "Ir" (i) : __LLSC_CLOBBER); \ 102 } else { \ 103 unsigned long flags; \ 104 \ 105 raw_local_irq_save(flags); \ 106 result = v->counter; \ 107 result c_op i; \ 108 v->counter = result; \ 109 raw_local_irq_restore(flags); \ 110 } \ 111 \ 112 return result; \ 113} 114 115#define ATOMIC_FETCH_OP(op, c_op, asm_op) \ 116static __inline__ int atomic_fetch_##op##_relaxed(int i, atomic_t * v) \ 117{ \ 118 int result; \ 119 \ 120 if (kernel_uses_llsc) { \ 121 int temp; \ 122 \ 123 loongson_llsc_mb(); \ 124 __asm__ __volatile__( \ 125 " .set push \n" \ 126 " .set "MIPS_ISA_LEVEL" \n" \ 127 "1: ll %1, %2 # atomic_fetch_" #op " \n" \ 128 " " #asm_op " %0, %1, %3 \n" \ 129 " sc %0, %2 \n" \ 130 "\t" __scbeqz " %0, 1b \n" \ 131 " .set pop \n" \ 132 " move %0, %1 \n" \ 133 : "=&r" (result), "=&r" (temp), \ 134 "+" GCC_OFF_SMALL_ASM() (v->counter) \ 135 : "Ir" (i) : __LLSC_CLOBBER); \ 136 } else { \ 137 unsigned long flags; \ 138 \ 139 raw_local_irq_save(flags); \ 140 result = v->counter; \ 141 v->counter c_op i; \ 142 raw_local_irq_restore(flags); \ 143 } \ 144 \ 145 return result; \ 146} 147 148#define ATOMIC_OPS(op, c_op, asm_op) \ 149 ATOMIC_OP(op, c_op, asm_op) \ 150 ATOMIC_OP_RETURN(op, c_op, asm_op) \ 151 ATOMIC_FETCH_OP(op, c_op, asm_op) 152 153ATOMIC_OPS(add, +=, addu) 154ATOMIC_OPS(sub, -=, subu) 155 156#define atomic_add_return_relaxed atomic_add_return_relaxed 157#define atomic_sub_return_relaxed atomic_sub_return_relaxed 158#define atomic_fetch_add_relaxed atomic_fetch_add_relaxed 159#define atomic_fetch_sub_relaxed atomic_fetch_sub_relaxed 160 161#undef ATOMIC_OPS 162#define ATOMIC_OPS(op, c_op, asm_op) \ 163 ATOMIC_OP(op, c_op, asm_op) \ 164 ATOMIC_FETCH_OP(op, c_op, asm_op) 165 166ATOMIC_OPS(and, &=, and) 167ATOMIC_OPS(or, |=, or) 168ATOMIC_OPS(xor, ^=, xor) 169 170#define atomic_fetch_and_relaxed atomic_fetch_and_relaxed 171#define atomic_fetch_or_relaxed atomic_fetch_or_relaxed 172#define atomic_fetch_xor_relaxed atomic_fetch_xor_relaxed 173 174#undef ATOMIC_OPS 175#undef ATOMIC_FETCH_OP 176#undef ATOMIC_OP_RETURN 177#undef ATOMIC_OP 178 179/* 180 * atomic_sub_if_positive - conditionally subtract integer from atomic variable 181 * @i: integer value to subtract 182 * @v: pointer of type atomic_t 183 * 184 * Atomically test @v and subtract @i if @v is greater or equal than @i. 185 * The function returns the old value of @v minus @i. 186 */ 187static __inline__ int atomic_sub_if_positive(int i, atomic_t * v) 188{ 189 int result; 190 191 smp_mb__before_llsc(); 192 193 if (kernel_uses_llsc) { 194 int temp; 195 196 loongson_llsc_mb(); 197 __asm__ __volatile__( 198 " .set push \n" 199 " .set "MIPS_ISA_LEVEL" \n" 200 "1: ll %1, %2 # atomic_sub_if_positive\n" 201 " .set pop \n" 202 " subu %0, %1, %3 \n" 203 " move %1, %0 \n" 204 " bltz %0, 2f \n" 205 " .set push \n" 206 " .set "MIPS_ISA_LEVEL" \n" 207 " sc %1, %2 \n" 208 "\t" __scbeqz " %1, 1b \n" 209 "2: \n" 210 " .set pop \n" 211 : "=&r" (result), "=&r" (temp), 212 "+" GCC_OFF_SMALL_ASM() (v->counter) 213 : "Ir" (i) : __LLSC_CLOBBER); 214 } else { 215 unsigned long flags; 216 217 raw_local_irq_save(flags); 218 result = v->counter; 219 result -= i; 220 if (result >= 0) 221 v->counter = result; 222 raw_local_irq_restore(flags); 223 } 224 225 smp_llsc_mb(); 226 227 return result; 228} 229 230#define atomic_cmpxchg(v, o, n) (cmpxchg(&((v)->counter), (o), (n))) 231#define atomic_xchg(v, new) (xchg(&((v)->counter), (new))) 232 233/* 234 * atomic_dec_if_positive - decrement by 1 if old value positive 235 * @v: pointer of type atomic_t 236 */ 237#define atomic_dec_if_positive(v) atomic_sub_if_positive(1, v) 238 239#ifdef CONFIG_64BIT 240 241#define ATOMIC64_INIT(i) { (i) } 242 243/* 244 * atomic64_read - read atomic variable 245 * @v: pointer of type atomic64_t 246 * 247 */ 248#define atomic64_read(v) READ_ONCE((v)->counter) 249 250/* 251 * atomic64_set - set atomic variable 252 * @v: pointer of type atomic64_t 253 * @i: required value 254 */ 255#define atomic64_set(v, i) WRITE_ONCE((v)->counter, (i)) 256 257#define ATOMIC64_OP(op, c_op, asm_op) \ 258static __inline__ void atomic64_##op(s64 i, atomic64_t * v) \ 259{ \ 260 if (kernel_uses_llsc) { \ 261 s64 temp; \ 262 \ 263 loongson_llsc_mb(); \ 264 __asm__ __volatile__( \ 265 " .set push \n" \ 266 " .set "MIPS_ISA_LEVEL" \n" \ 267 "1: lld %0, %1 # atomic64_" #op " \n" \ 268 " " #asm_op " %0, %2 \n" \ 269 " scd %0, %1 \n" \ 270 "\t" __scbeqz " %0, 1b \n" \ 271 " .set pop \n" \ 272 : "=&r" (temp), "+" GCC_OFF_SMALL_ASM() (v->counter) \ 273 : "Ir" (i) : __LLSC_CLOBBER); \ 274 } else { \ 275 unsigned long flags; \ 276 \ 277 raw_local_irq_save(flags); \ 278 v->counter c_op i; \ 279 raw_local_irq_restore(flags); \ 280 } \ 281} 282 283#define ATOMIC64_OP_RETURN(op, c_op, asm_op) \ 284static __inline__ s64 atomic64_##op##_return_relaxed(s64 i, atomic64_t * v) \ 285{ \ 286 s64 result; \ 287 \ 288 if (kernel_uses_llsc) { \ 289 s64 temp; \ 290 \ 291 loongson_llsc_mb(); \ 292 __asm__ __volatile__( \ 293 " .set push \n" \ 294 " .set "MIPS_ISA_LEVEL" \n" \ 295 "1: lld %1, %2 # atomic64_" #op "_return\n" \ 296 " " #asm_op " %0, %1, %3 \n" \ 297 " scd %0, %2 \n" \ 298 "\t" __scbeqz " %0, 1b \n" \ 299 " " #asm_op " %0, %1, %3 \n" \ 300 " .set pop \n" \ 301 : "=&r" (result), "=&r" (temp), \ 302 "+" GCC_OFF_SMALL_ASM() (v->counter) \ 303 : "Ir" (i) : __LLSC_CLOBBER); \ 304 } else { \ 305 unsigned long flags; \ 306 \ 307 raw_local_irq_save(flags); \ 308 result = v->counter; \ 309 result c_op i; \ 310 v->counter = result; \ 311 raw_local_irq_restore(flags); \ 312 } \ 313 \ 314 return result; \ 315} 316 317#define ATOMIC64_FETCH_OP(op, c_op, asm_op) \ 318static __inline__ s64 atomic64_fetch_##op##_relaxed(s64 i, atomic64_t * v) \ 319{ \ 320 s64 result; \ 321 \ 322 if (kernel_uses_llsc) { \ 323 s64 temp; \ 324 \ 325 loongson_llsc_mb(); \ 326 __asm__ __volatile__( \ 327 " .set push \n" \ 328 " .set "MIPS_ISA_LEVEL" \n" \ 329 "1: lld %1, %2 # atomic64_fetch_" #op "\n" \ 330 " " #asm_op " %0, %1, %3 \n" \ 331 " scd %0, %2 \n" \ 332 "\t" __scbeqz " %0, 1b \n" \ 333 " move %0, %1 \n" \ 334 " .set pop \n" \ 335 : "=&r" (result), "=&r" (temp), \ 336 "+" GCC_OFF_SMALL_ASM() (v->counter) \ 337 : "Ir" (i) : __LLSC_CLOBBER); \ 338 } else { \ 339 unsigned long flags; \ 340 \ 341 raw_local_irq_save(flags); \ 342 result = v->counter; \ 343 v->counter c_op i; \ 344 raw_local_irq_restore(flags); \ 345 } \ 346 \ 347 return result; \ 348} 349 350#define ATOMIC64_OPS(op, c_op, asm_op) \ 351 ATOMIC64_OP(op, c_op, asm_op) \ 352 ATOMIC64_OP_RETURN(op, c_op, asm_op) \ 353 ATOMIC64_FETCH_OP(op, c_op, asm_op) 354 355ATOMIC64_OPS(add, +=, daddu) 356ATOMIC64_OPS(sub, -=, dsubu) 357 358#define atomic64_add_return_relaxed atomic64_add_return_relaxed 359#define atomic64_sub_return_relaxed atomic64_sub_return_relaxed 360#define atomic64_fetch_add_relaxed atomic64_fetch_add_relaxed 361#define atomic64_fetch_sub_relaxed atomic64_fetch_sub_relaxed 362 363#undef ATOMIC64_OPS 364#define ATOMIC64_OPS(op, c_op, asm_op) \ 365 ATOMIC64_OP(op, c_op, asm_op) \ 366 ATOMIC64_FETCH_OP(op, c_op, asm_op) 367 368ATOMIC64_OPS(and, &=, and) 369ATOMIC64_OPS(or, |=, or) 370ATOMIC64_OPS(xor, ^=, xor) 371 372#define atomic64_fetch_and_relaxed atomic64_fetch_and_relaxed 373#define atomic64_fetch_or_relaxed atomic64_fetch_or_relaxed 374#define atomic64_fetch_xor_relaxed atomic64_fetch_xor_relaxed 375 376#undef ATOMIC64_OPS 377#undef ATOMIC64_FETCH_OP 378#undef ATOMIC64_OP_RETURN 379#undef ATOMIC64_OP 380 381/* 382 * atomic64_sub_if_positive - conditionally subtract integer from atomic 383 * variable 384 * @i: integer value to subtract 385 * @v: pointer of type atomic64_t 386 * 387 * Atomically test @v and subtract @i if @v is greater or equal than @i. 388 * The function returns the old value of @v minus @i. 389 */ 390static __inline__ s64 atomic64_sub_if_positive(s64 i, atomic64_t * v) 391{ 392 s64 result; 393 394 smp_mb__before_llsc(); 395 396 if (kernel_uses_llsc) { 397 s64 temp; 398 399 __asm__ __volatile__( 400 " .set push \n" 401 " .set "MIPS_ISA_LEVEL" \n" 402 "1: lld %1, %2 # atomic64_sub_if_positive\n" 403 " dsubu %0, %1, %3 \n" 404 " move %1, %0 \n" 405 " bltz %0, 1f \n" 406 " scd %1, %2 \n" 407 "\t" __scbeqz " %1, 1b \n" 408 "1: \n" 409 " .set pop \n" 410 : "=&r" (result), "=&r" (temp), 411 "+" GCC_OFF_SMALL_ASM() (v->counter) 412 : "Ir" (i)); 413 } else { 414 unsigned long flags; 415 416 raw_local_irq_save(flags); 417 result = v->counter; 418 result -= i; 419 if (result >= 0) 420 v->counter = result; 421 raw_local_irq_restore(flags); 422 } 423 424 smp_llsc_mb(); 425 426 return result; 427} 428 429#define atomic64_cmpxchg(v, o, n) \ 430 ((__typeof__((v)->counter))cmpxchg(&((v)->counter), (o), (n))) 431#define atomic64_xchg(v, new) (xchg(&((v)->counter), (new))) 432 433/* 434 * atomic64_dec_if_positive - decrement by 1 if old value positive 435 * @v: pointer of type atomic64_t 436 */ 437#define atomic64_dec_if_positive(v) atomic64_sub_if_positive(1, v) 438 439#endif /* CONFIG_64BIT */ 440 441#endif /* _ASM_ATOMIC_H */