1 /*
2 * Copyright (c) 2016 Intel Corporation
3 * Copyright (c) 2011-2014 Wind River Systems, Inc.
4 *
5 * SPDX-License-Identifier: Apache-2.0
6 */
7
8 /**
9 * @file Atomic ops in pure C
10 *
11 * This module provides the atomic operators for processors
12 * which do not support native atomic operations.
13 *
14 * The atomic operations are guaranteed to be atomic with respect
15 * to interrupt service routines, and to operations performed by peer
16 * processors.
17 *
18 * (originally from x86's atomic.c)
19 */
20
21 #include <zephyr/toolchain.h>
22 #include <zephyr/arch/cpu.h>
23 #include <zephyr/spinlock.h>
24 #include <zephyr/sys/atomic.h>
25 #include <zephyr/kernel_structs.h>
26
27 /* Single global spinlock for atomic operations. This is fallback
28 * code, not performance sensitive. At least by not using irq_lock()
29 * in SMP contexts we won't content with legitimate users of the
30 * global lock.
31 */
32 static struct k_spinlock lock;
33
34 /* For those rare CPUs which support user mode, but not native atomic
35 * operations, the best we can do for them is implement the atomic
36 * functions as system calls, since in user mode locking a spinlock is
37 * forbidden.
38 */
39 #ifdef CONFIG_USERSPACE
40 #include <zephyr/syscall_handler.h>
41
42 #define ATOMIC_SYSCALL_HANDLER_TARGET(name) \
43 static inline atomic_val_t z_vrfy_##name(atomic_t *target) \
44 { \
45 Z_OOPS(Z_SYSCALL_MEMORY_WRITE(target, sizeof(atomic_t))); \
46 return z_impl_##name((atomic_t *)target); \
47 }
48
49 #define ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(name) \
50 static inline atomic_val_t z_vrfy_##name(atomic_t *target, \
51 atomic_val_t value) \
52 { \
53 Z_OOPS(Z_SYSCALL_MEMORY_WRITE(target, sizeof(atomic_t))); \
54 return z_impl_##name((atomic_t *)target, value); \
55 }
56 #else
57 #define ATOMIC_SYSCALL_HANDLER_TARGET(name)
58 #define ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(name)
59 #endif
60
61 /**
62 *
63 * @brief Atomic compare-and-set primitive
64 *
65 * This routine provides the compare-and-set operator. If the original value at
66 * <target> equals <oldValue>, then <newValue> is stored at <target> and the
67 * function returns true.
68 *
69 * If the original value at <target> does not equal <oldValue>, then the store
70 * is not done and the function returns false.
71 *
72 * The reading of the original value at <target>, the comparison,
73 * and the write of the new value (if it occurs) all happen atomically with
74 * respect to both interrupts and accesses of other processors to <target>.
75 *
76 * @param target address to be tested
77 * @param old_value value to compare against
78 * @param new_value value to compare against
79 * @return Returns true if <new_value> is written, false otherwise.
80 */
z_impl_atomic_cas(atomic_t * target,atomic_val_t old_value,atomic_val_t new_value)81 bool z_impl_atomic_cas(atomic_t *target, atomic_val_t old_value,
82 atomic_val_t new_value)
83 {
84 k_spinlock_key_t key;
85 int ret = false;
86
87 /*
88 * On SMP the k_spin_lock() definition calls atomic_cas().
89 * Using k_spin_lock() here would create an infinite loop and
90 * massive stack overflow. Consider CONFIG_ATOMIC_OPERATIONS_ARCH
91 * or CONFIG_ATOMIC_OPERATIONS_BUILTIN instead.
92 */
93 BUILD_ASSERT(!IS_ENABLED(CONFIG_SMP));
94
95 key = k_spin_lock(&lock);
96
97 if (*target == old_value) {
98 *target = new_value;
99 ret = true;
100 }
101
102 k_spin_unlock(&lock, key);
103
104 return ret;
105 }
106
107 #ifdef CONFIG_USERSPACE
z_vrfy_atomic_cas(atomic_t * target,atomic_val_t old_value,atomic_val_t new_value)108 bool z_vrfy_atomic_cas(atomic_t *target, atomic_val_t old_value,
109 atomic_val_t new_value)
110 {
111 Z_OOPS(Z_SYSCALL_MEMORY_WRITE(target, sizeof(atomic_t)));
112
113 return z_impl_atomic_cas((atomic_t *)target, old_value, new_value);
114 }
115 #include <syscalls/atomic_cas_mrsh.c>
116 #endif /* CONFIG_USERSPACE */
117
z_impl_atomic_ptr_cas(atomic_ptr_t * target,atomic_ptr_val_t old_value,atomic_ptr_val_t new_value)118 bool z_impl_atomic_ptr_cas(atomic_ptr_t *target, atomic_ptr_val_t old_value,
119 atomic_ptr_val_t new_value)
120 {
121 k_spinlock_key_t key;
122 int ret = false;
123
124 key = k_spin_lock(&lock);
125
126 if (*target == old_value) {
127 *target = new_value;
128 ret = true;
129 }
130
131 k_spin_unlock(&lock, key);
132
133 return ret;
134 }
135
136 #ifdef CONFIG_USERSPACE
z_vrfy_atomic_ptr_cas(atomic_ptr_t * target,atomic_ptr_val_t old_value,atomic_ptr_val_t new_value)137 static inline bool z_vrfy_atomic_ptr_cas(atomic_ptr_t *target,
138 atomic_ptr_val_t old_value,
139 atomic_ptr_val_t new_value)
140 {
141 Z_OOPS(Z_SYSCALL_MEMORY_WRITE(target, sizeof(atomic_ptr_t)));
142
143 return z_impl_atomic_ptr_cas(target, old_value, new_value);
144 }
145 #include <syscalls/atomic_ptr_cas_mrsh.c>
146 #endif /* CONFIG_USERSPACE */
147
148 /**
149 *
150 * @brief Atomic addition primitive
151 *
152 * This routine provides the atomic addition operator. The <value> is
153 * atomically added to the value at <target>, placing the result at <target>,
154 * and the old value from <target> is returned.
155 *
156 * @param target memory location to add to
157 * @param value the value to add
158 *
159 * @return The previous value from <target>
160 */
z_impl_atomic_add(atomic_t * target,atomic_val_t value)161 atomic_val_t z_impl_atomic_add(atomic_t *target, atomic_val_t value)
162 {
163 k_spinlock_key_t key;
164 atomic_val_t ret;
165
166 key = k_spin_lock(&lock);
167
168 ret = *target;
169 *target += value;
170
171 k_spin_unlock(&lock, key);
172
173 return ret;
174 }
175
176 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_add);
177
178 /**
179 *
180 * @brief Atomic subtraction primitive
181 *
182 * This routine provides the atomic subtraction operator. The <value> is
183 * atomically subtracted from the value at <target>, placing the result at
184 * <target>, and the old value from <target> is returned.
185 *
186 * @param target the memory location to subtract from
187 * @param value the value to subtract
188 *
189 * @return The previous value from <target>
190 */
z_impl_atomic_sub(atomic_t * target,atomic_val_t value)191 atomic_val_t z_impl_atomic_sub(atomic_t *target, atomic_val_t value)
192 {
193 k_spinlock_key_t key;
194 atomic_val_t ret;
195
196 key = k_spin_lock(&lock);
197
198 ret = *target;
199 *target -= value;
200
201 k_spin_unlock(&lock, key);
202
203 return ret;
204 }
205
206 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_sub);
207
208 /**
209 *
210 * @brief Atomic get primitive
211 *
212 * @param target memory location to read from
213 *
214 * This routine provides the atomic get primitive to atomically read
215 * a value from <target>. It simply does an ordinary load. Note that <target>
216 * is expected to be aligned to a 4-byte boundary.
217 *
218 * @return The value read from <target>
219 */
atomic_get(const atomic_t * target)220 atomic_val_t atomic_get(const atomic_t *target)
221 {
222 return *target;
223 }
224
atomic_ptr_get(const atomic_ptr_t * target)225 atomic_ptr_val_t atomic_ptr_get(const atomic_ptr_t *target)
226 {
227 return *target;
228 }
229
230 /**
231 *
232 * @brief Atomic get-and-set primitive
233 *
234 * This routine provides the atomic set operator. The <value> is atomically
235 * written at <target> and the previous value at <target> is returned.
236 *
237 * @param target the memory location to write to
238 * @param value the value to write
239 *
240 * @return The previous value from <target>
241 */
z_impl_atomic_set(atomic_t * target,atomic_val_t value)242 atomic_val_t z_impl_atomic_set(atomic_t *target, atomic_val_t value)
243 {
244 k_spinlock_key_t key;
245 atomic_val_t ret;
246
247 key = k_spin_lock(&lock);
248
249 ret = *target;
250 *target = value;
251
252 k_spin_unlock(&lock, key);
253
254 return ret;
255 }
256
257 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_set);
258
z_impl_atomic_ptr_set(atomic_ptr_t * target,atomic_ptr_val_t value)259 atomic_ptr_val_t z_impl_atomic_ptr_set(atomic_ptr_t *target,
260 atomic_ptr_val_t value)
261 {
262 k_spinlock_key_t key;
263 atomic_ptr_val_t ret;
264
265 key = k_spin_lock(&lock);
266
267 ret = *target;
268 *target = value;
269
270 k_spin_unlock(&lock, key);
271
272 return ret;
273 }
274
275 #ifdef CONFIG_USERSPACE
z_vrfy_atomic_ptr_set(atomic_ptr_t * target,atomic_ptr_val_t value)276 static inline atomic_ptr_val_t z_vrfy_atomic_ptr_set(atomic_ptr_t *target,
277 atomic_ptr_val_t value)
278 {
279 Z_OOPS(Z_SYSCALL_MEMORY_WRITE(target, sizeof(atomic_ptr_t)));
280
281 return z_impl_atomic_ptr_set(target, value);
282 }
283 #include <syscalls/atomic_ptr_set_mrsh.c>
284 #endif /* CONFIG_USERSPACE */
285
286 /**
287 *
288 * @brief Atomic bitwise inclusive OR primitive
289 *
290 * This routine provides the atomic bitwise inclusive OR operator. The <value>
291 * is atomically bitwise OR'ed with the value at <target>, placing the result
292 * at <target>, and the previous value at <target> is returned.
293 *
294 * @param target the memory location to be modified
295 * @param value the value to OR
296 *
297 * @return The previous value from <target>
298 */
z_impl_atomic_or(atomic_t * target,atomic_val_t value)299 atomic_val_t z_impl_atomic_or(atomic_t *target, atomic_val_t value)
300 {
301 k_spinlock_key_t key;
302 atomic_val_t ret;
303
304 key = k_spin_lock(&lock);
305
306 ret = *target;
307 *target |= value;
308
309 k_spin_unlock(&lock, key);
310
311 return ret;
312 }
313
314 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_or);
315
316 /**
317 *
318 * @brief Atomic bitwise exclusive OR (XOR) primitive
319 *
320 * This routine provides the atomic bitwise exclusive OR operator. The <value>
321 * is atomically bitwise XOR'ed with the value at <target>, placing the result
322 * at <target>, and the previous value at <target> is returned.
323 *
324 * @param target the memory location to be modified
325 * @param value the value to XOR
326 *
327 * @return The previous value from <target>
328 */
z_impl_atomic_xor(atomic_t * target,atomic_val_t value)329 atomic_val_t z_impl_atomic_xor(atomic_t *target, atomic_val_t value)
330 {
331 k_spinlock_key_t key;
332 atomic_val_t ret;
333
334 key = k_spin_lock(&lock);
335
336 ret = *target;
337 *target ^= value;
338
339 k_spin_unlock(&lock, key);
340
341 return ret;
342 }
343
344 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_xor);
345
346 /**
347 *
348 * @brief Atomic bitwise AND primitive
349 *
350 * This routine provides the atomic bitwise AND operator. The <value> is
351 * atomically bitwise AND'ed with the value at <target>, placing the result
352 * at <target>, and the previous value at <target> is returned.
353 *
354 * @param target the memory location to be modified
355 * @param value the value to AND
356 *
357 * @return The previous value from <target>
358 */
z_impl_atomic_and(atomic_t * target,atomic_val_t value)359 atomic_val_t z_impl_atomic_and(atomic_t *target, atomic_val_t value)
360 {
361 k_spinlock_key_t key;
362 atomic_val_t ret;
363
364 key = k_spin_lock(&lock);
365
366 ret = *target;
367 *target &= value;
368
369 k_spin_unlock(&lock, key);
370
371 return ret;
372 }
373
374 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_and);
375
376 /**
377 *
378 * @brief Atomic bitwise NAND primitive
379 *
380 * This routine provides the atomic bitwise NAND operator. The <value> is
381 * atomically bitwise NAND'ed with the value at <target>, placing the result
382 * at <target>, and the previous value at <target> is returned.
383 *
384 * @param target the memory location to be modified
385 * @param value the value to NAND
386 *
387 * @return The previous value from <target>
388 */
z_impl_atomic_nand(atomic_t * target,atomic_val_t value)389 atomic_val_t z_impl_atomic_nand(atomic_t *target, atomic_val_t value)
390 {
391 k_spinlock_key_t key;
392 atomic_val_t ret;
393
394 key = k_spin_lock(&lock);
395
396 ret = *target;
397 *target = ~(*target & value);
398
399 k_spin_unlock(&lock, key);
400
401 return ret;
402 }
403
404 ATOMIC_SYSCALL_HANDLER_TARGET_VALUE(atomic_nand);
405
406 #ifdef CONFIG_USERSPACE
407 #include <syscalls/atomic_add_mrsh.c>
408 #include <syscalls/atomic_sub_mrsh.c>
409 #include <syscalls/atomic_set_mrsh.c>
410 #include <syscalls/atomic_or_mrsh.c>
411 #include <syscalls/atomic_xor_mrsh.c>
412 #include <syscalls/atomic_and_mrsh.c>
413 #include <syscalls/atomic_nand_mrsh.c>
414 #endif
415