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