Zephyr API Documentation 4.4.99
A Scalable Open Source RTOS
Loading...
Searching...
No Matches
spinlock.h
Go to the documentation of this file.
1/*
2 * Copyright (c) 2018 Intel Corporation.
3 *
4 * SPDX-License-Identifier: Apache-2.0
5 */
6
11
12#ifndef ZEPHYR_INCLUDE_SPINLOCK_H_
13#define ZEPHYR_INCLUDE_SPINLOCK_H_
14
15#include <errno.h>
16#include <stdbool.h>
17
18#include <zephyr/arch/cpu.h>
19#include <zephyr/sys/atomic.h>
20#include <zephyr/sys/__assert.h>
22
23#ifdef __cplusplus
24extern "C" {
25#endif
26
33
34struct z_spinlock_key {
35 int key;
36};
37
45struct k_spinlock {
49#ifdef CONFIG_SMP
50#ifdef CONFIG_TICKET_SPINLOCKS
51 /*
52 * Ticket spinlocks are conceptually two atomic variables,
53 * one indicating the current FIFO head (spinlock owner),
54 * and the other indicating the current FIFO tail.
55 * Spinlock is acquired in the following manner:
56 * - current FIFO tail value is atomically incremented while it's
57 * original value is saved as a "ticket"
58 * - we spin until the FIFO head becomes equal to the ticket value
59 *
60 * Spinlock is released by atomic increment of the FIFO head
61 */
62 atomic_t owner;
63 atomic_t tail;
64#else
65 atomic_t locked;
66#endif /* CONFIG_TICKET_SPINLOCKS */
67#endif /* CONFIG_SMP */
68
69#ifdef CONFIG_SPIN_VALIDATE
70 /* Stores the thread that holds the lock with the locking CPU
71 * ID in the bottom bits (2 bits on 32-bit, 3 bits on 64-bit).
72 */
73 uintptr_t thread_cpu;
74#ifdef CONFIG_SPIN_LOCK_TIME_LIMIT
75 /* Stores the time (in cycles) when a lock was taken
76 */
77 uint32_t lock_time;
78#endif /* CONFIG_SPIN_LOCK_TIME_LIMIT */
79#endif /* CONFIG_SPIN_VALIDATE */
80
81#if defined(CONFIG_NONZERO_SPINLOCK_SIZE) && !defined(CONFIG_SMP) && !defined(CONFIG_SPIN_VALIDATE)
82 /* Add a dummy field to guarantee the spinlock has a non-zero
83 * size. If neither CONFIG_SMP nor CONFIG_SPIN_VALIDATE are
84 * defined then the k_spinlock struct would otherwise have no
85 * members and sizeof(k_spinlock) would be 0 in C and 1 in C++.
86 *
87 * That size difference causes problems when the k_spinlock
88 * is embedded into another struct like k_msgq, because C and
89 * C++ will have different ideas on the offsets of the members
90 * that come after the k_spinlock member.
91 */
92 char dummy;
93#endif
97};
98
99/* There's a spinlock validation framework available when asserts are
100 * enabled. It adds a relatively hefty overhead (about 3k or so) to
101 * kernel code size, don't use on platforms known to be small.
102 */
103#ifdef CONFIG_SPIN_VALIDATE
104bool z_spin_lock_valid(struct k_spinlock *l);
105bool z_spin_unlock_valid(struct k_spinlock *l);
106void z_spin_lock_set_owner(struct k_spinlock *l);
107void z_spin_lock_transfer_owner(struct k_spinlock *l);
108void z_assert_can_swap(unsigned int key, struct k_spinlock *swap_lock);
109void z_spin_validate_reset(bool lock_held);
110extern const uint8_t z_spinlock_abort_sentinel;
111#ifdef CONFIG_TEST
112struct k_spinlock *z_spin_get_held_lock(void);
113#endif /* CONFIG_TEST */
114#if defined(CONFIG_64BIT)
115BUILD_ASSERT(CONFIG_MP_MAX_NUM_CPUS <= 8, "Too many CPUs for mask");
116#else
117BUILD_ASSERT(CONFIG_MP_MAX_NUM_CPUS <= 4, "Too many CPUs for mask");
118#endif
119
120# ifdef CONFIG_KERNEL_COHERENCE
121bool z_spin_lock_mem_coherent(struct k_spinlock *l);
122# endif /* CONFIG_KERNEL_COHERENCE */
123
124#endif /* CONFIG_SPIN_VALIDATE */
125
137typedef struct z_spinlock_key k_spinlock_key_t;
138
139static ALWAYS_INLINE void z_spinlock_validate_pre(struct k_spinlock *l)
140{
141 ARG_UNUSED(l);
142#ifdef CONFIG_SPIN_VALIDATE
143 __ASSERT(z_spin_lock_valid(l), "Invalid spinlock %p", l);
144#ifdef CONFIG_KERNEL_COHERENCE
145 __ASSERT_NO_MSG(z_spin_lock_mem_coherent(l));
146#endif
147#endif
148}
149
150static ALWAYS_INLINE void z_spinlock_validate_post(struct k_spinlock *l)
151{
152 ARG_UNUSED(l);
153#ifdef CONFIG_SPIN_VALIDATE
154 z_spin_lock_set_owner(l);
155#if defined(CONFIG_SPIN_LOCK_TIME_LIMIT) && (CONFIG_SPIN_LOCK_TIME_LIMIT != 0)
156 l->lock_time = sys_clock_cycle_get_32();
157#endif /* CONFIG_SPIN_LOCK_TIME_LIMIT */
158#endif /* CONFIG_SPIN_VALIDATE */
159}
160
192static ALWAYS_INLINE k_spinlock_key_t k_spin_lock(struct k_spinlock *l)
193{
194 ARG_UNUSED(l);
196
197 /* Note that we need to use the underlying arch-specific lock
198 * implementation. The "irq_lock()" API in SMP context is
199 * actually a wrapper for a global spinlock!
200 */
201 k.key = arch_irq_lock();
202
203 z_spinlock_validate_pre(l);
204#ifdef CONFIG_SMP
205#ifdef CONFIG_TICKET_SPINLOCKS
206 /*
207 * Enqueue ourselves to the end of a spinlock waiters queue
208 * receiving a ticket
209 */
210 atomic_val_t ticket = atomic_inc(&l->tail);
211 /* Spin until our ticket is served */
212 while (atomic_get(&l->owner) != ticket) {
214 }
215#else
216 while (!atomic_cas(&l->locked, 0, 1)) {
217 do {
219 } while (atomic_get(&l->locked) != 0);
220 }
221#endif /* CONFIG_TICKET_SPINLOCKS */
222#endif /* CONFIG_SMP */
223 z_spinlock_validate_post(l);
224
225 return k;
226}
227
242static ALWAYS_INLINE int k_spin_trylock(struct k_spinlock *l, k_spinlock_key_t *k)
243{
244 int key = arch_irq_lock();
245
246 z_spinlock_validate_pre(l);
247#ifdef CONFIG_SMP
248#ifdef CONFIG_TICKET_SPINLOCKS
249 /*
250 * atomic_get and atomic_cas operations below are not executed
251 * simultaneously.
252 * So in theory k_spin_trylock can lock an already locked spinlock.
253 * To reproduce this the following conditions should be met after we
254 * executed atomic_get and before we executed atomic_cas:
255 *
256 * - spinlock needs to be taken 0xffff_..._ffff + 1 times
257 * (which requires 0xffff_..._ffff number of CPUs, as k_spin_lock call
258 * is blocking) or
259 * - spinlock needs to be taken and released 0xffff_..._ffff times and
260 * then taken again
261 *
262 * In real-life systems this is considered non-reproducible given that
263 * required actions need to be done during this tiny window of several
264 * CPU instructions (which execute with interrupt locked,
265 * so no preemption can happen here)
266 */
267 atomic_val_t ticket_val = atomic_get(&l->owner);
268
269 if (!atomic_cas(&l->tail, ticket_val, ticket_val + 1)) {
270 goto busy;
271 }
272#else
273 if (!atomic_cas(&l->locked, 0, 1)) {
274 goto busy;
275 }
276#endif /* CONFIG_TICKET_SPINLOCKS */
277#endif /* CONFIG_SMP */
278 z_spinlock_validate_post(l);
279
280 k->key = key;
281
282 return 0;
283
284#ifdef CONFIG_SMP
285busy:
286 arch_irq_unlock(key);
287 return -EBUSY;
288#endif /* CONFIG_SMP */
289}
290
312static ALWAYS_INLINE void k_spin_unlock(struct k_spinlock *l,
314{
315 ARG_UNUSED(l);
316#ifdef CONFIG_SPIN_VALIDATE
317 __ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
318
319#if defined(CONFIG_SPIN_LOCK_TIME_LIMIT) && (CONFIG_SPIN_LOCK_TIME_LIMIT != 0)
320 uint32_t delta = sys_clock_cycle_get_32() - l->lock_time;
321
322 __ASSERT(delta < CONFIG_SPIN_LOCK_TIME_LIMIT,
323 "Spin lock %p held %u cycles, longer than limit of %u cycles",
324 l, delta, CONFIG_SPIN_LOCK_TIME_LIMIT);
325#endif /* CONFIG_SPIN_LOCK_TIME_LIMIT */
326#endif /* CONFIG_SPIN_VALIDATE */
327
328#ifdef CONFIG_SMP
329#ifdef CONFIG_TICKET_SPINLOCKS
330 /* Give the spinlock to the next CPU in a FIFO */
331 (void)atomic_inc(&l->owner);
332#else
333 /* Strictly we don't need atomic_clear() here (which is an
334 * exchange operation that returns the old value). We are always
335 * setting a zero and (because we hold the lock) know the existing
336 * state won't change due to a race. But some architectures need
337 * a memory barrier when used like this, and we don't have a
338 * Zephyr framework for that.
339 */
340 (void)atomic_clear(&l->locked);
341#endif /* CONFIG_TICKET_SPINLOCKS */
342#endif /* CONFIG_SMP */
343 arch_irq_unlock(key.key);
344}
345
349
350#if defined(CONFIG_TEST) || defined(CONFIG_ASSERT)
351/*
352 * @brief Checks if spinlock is held by some CPU, including the local CPU.
353 * This should only be used in tests or assertions, not to make
354 * runtime control flow decisions.
355 *
356 * @param l A pointer to the spinlock
357 * @retval true - if spinlock is held by some CPU; false - otherwise
358 */
359static ALWAYS_INLINE bool z_spin_is_locked(struct k_spinlock *l)
360{
361#ifdef CONFIG_SMP
362#ifdef CONFIG_TICKET_SPINLOCKS
363 atomic_val_t ticket_val = atomic_get(&l->owner);
364
365 return !atomic_cas(&l->tail, ticket_val, ticket_val);
366#else
367 return l->locked;
368#endif /* CONFIG_TICKET_SPINLOCKS */
369#else
370 ARG_UNUSED(l);
371 /* In UP builds a spinlock reduces to an IRQ lock. */
373#endif /* CONFIG_SMP */
374}
375#endif /* defined(CONFIG_TEST) || defined(CONFIG_ASSERT) */
376
377/* Internal function: releases the lock, but leaves local interrupts disabled */
378static ALWAYS_INLINE void k_spin_release(struct k_spinlock *l)
379{
380 ARG_UNUSED(l);
381#ifdef CONFIG_SPIN_VALIDATE
382 __ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
383#endif
384#ifdef CONFIG_SMP
385#ifdef CONFIG_TICKET_SPINLOCKS
386 (void)atomic_inc(&l->owner);
387#else
388 (void)atomic_clear(&l->locked);
389#endif /* CONFIG_TICKET_SPINLOCKS */
390#endif /* CONFIG_SMP */
391}
392
393#if defined(CONFIG_SPIN_VALIDATE) && defined(__GNUC__)
394static ALWAYS_INLINE void z_spin_onexit(__maybe_unused k_spinlock_key_t *k)
395{
396 __ASSERT(k->key, "K_SPINLOCK exited with goto, break or return, "
397 "use K_SPINLOCK_BREAK instead.");
398}
399#define K_SPINLOCK_ONEXIT __attribute__((__cleanup__(z_spin_onexit)))
400#else
401#define K_SPINLOCK_ONEXIT
402#endif
403
407
414#define K_SPINLOCK_BREAK continue
415
457#define K_SPINLOCK(lck) \
458 for (k_spinlock_key_t __i K_SPINLOCK_ONEXIT = {}, __key = k_spin_lock(lck); !__i.key; \
459 k_spin_unlock((lck), __key), __i.key = 1)
460
462
463#ifdef __cplusplus
464}
465#endif
466
467#endif /* ZEPHYR_INCLUDE_SPINLOCK_H_ */
void arch_spin_relax(void)
Perform architecture specific processing within spin loops.
Header file for the Atomic operations API.
System error numbers.
static unsigned int arch_irq_lock(void)
Lock interrupts on the current CPU.
static void arch_irq_unlock(unsigned int key)
Unlock interrupts on the current CPU.
static bool arch_cpu_irqs_are_enabled(void)
Probe the current CPU overall interrupt controller lock state without modifying it.
long atomic_t
Atomic integer variable.
Definition atomic_types.h:31
atomic_t atomic_val_t
Value type for atomic integer variables.
Definition atomic_types.h:38
atomic_val_t atomic_get(const atomic_t *target)
Atomic get.
atomic_val_t atomic_clear(atomic_t *target)
Atomic clear.
atomic_val_t atomic_inc(atomic_t *target)
Atomic increment.
bool atomic_cas(atomic_t *target, atomic_val_t old_value, atomic_val_t new_value)
Atomic compare-and-set.
uint32_t sys_clock_cycle_get_32(void)
Hardware cycle counter.
static ALWAYS_INLINE int k_spin_trylock(struct k_spinlock *l, k_spinlock_key_t *k)
Attempt to lock a spinlock.
Definition spinlock.h:242
static ALWAYS_INLINE void k_spin_unlock(struct k_spinlock *l, k_spinlock_key_t key)
Unlock a spin lock.
Definition spinlock.h:312
static ALWAYS_INLINE k_spinlock_key_t k_spin_lock(struct k_spinlock *l)
Lock a spinlock.
Definition spinlock.h:192
struct z_spinlock_key k_spinlock_key_t
Spinlock key type.
Definition spinlock.h:137
#define EBUSY
Mount device busy.
Definition errno.h:54
__UINT32_TYPE__ uint32_t
Definition stdint.h:90
__UINT8_TYPE__ uint8_t
Definition stdint.h:88
__UINTPTR_TYPE__ uintptr_t
Definition stdint.h:105
Kernel Spin Lock.
Definition spinlock.h:45