Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 | /*
* Copyright (c) 2018 Intel Corporation.
*
* SPDX-License-Identifier: Apache-2.0
*/
#ifndef ZEPHYR_INCLUDE_SPINLOCK_H_
#define ZEPHYR_INCLUDE_SPINLOCK_H_
#include <sys/atomic.h>
#include <sys/__assert.h>
#include <stdbool.h>
#include <arch/cpu.h>
#ifdef __cplusplus
extern "C" {
#endif
struct z_spinlock_key {
int key;
};
/**
* @brief Kernel Spin Lock
*
* This struct defines a spin lock record on which CPUs can wait with
* k_spin_lock(). Any number of spinlocks may be defined in
* application code.
*/
struct k_spinlock {
#ifdef CONFIG_SMP
atomic_t locked;
#endif
#ifdef CONFIG_SPIN_VALIDATE
/* Stores the thread that holds the lock with the locking CPU
* ID in the bottom two bits.
*/
uintptr_t thread_cpu;
#endif
#if defined(CONFIG_CPLUSPLUS) && !defined(CONFIG_SMP) && \
!defined(CONFIG_SPIN_VALIDATE)
/* If CONFIG_SMP and CONFIG_SPIN_VALIDATE are both not defined
* the k_spinlock struct will have no members. The result
* is that in C sizeof(k_spinlock) is 0 and in C++ it is 1.
*
* This size difference causes problems when the k_spinlock
* is embedded into another struct like k_msgq, because C and
* C++ will have different ideas on the offsets of the members
* that come after the k_spinlock member.
*
* To prevent this we add a 1 byte dummy member to k_spinlock
* when the user selects C++ support and k_spinlock would
* otherwise be empty.
*/
char dummy;
#endif
};
/* There's a spinlock validation framework available when asserts are
* enabled. It adds a relatively hefty overhead (about 3k or so) to
* kernel code size, don't use on platforms known to be small.
*/
#ifdef CONFIG_SPIN_VALIDATE
bool z_spin_lock_valid(struct k_spinlock *l);
bool z_spin_unlock_valid(struct k_spinlock *l);
void z_spin_lock_set_owner(struct k_spinlock *l);
BUILD_ASSERT(CONFIG_MP_NUM_CPUS <= 4, "Too many CPUs for mask");
# ifdef CONFIG_KERNEL_COHERENCE
bool z_spin_lock_mem_coherent(struct k_spinlock *l);
# endif /* CONFIG_KERNEL_COHERENCE */
#endif /* CONFIG_SPIN_VALIDATE */
/**
* @brief Spinlock key type
*
* This type defines a "key" value used by a spinlock implementation
* to store the system interrupt state at the time of a call to
* k_spin_lock(). It is expected to be passed to a matching
* k_spin_unlock().
*
* This type is opaque and should not be inspected by application
* code.
*/
typedef struct z_spinlock_key k_spinlock_key_t;
/**
* @brief Lock a spinlock
*
* This routine locks the specified spinlock, returning a key handle
* representing interrupt state needed at unlock time. Upon
* returning, the calling thread is guaranteed not to be suspended or
* interrupted on its current CPU until it calls k_spin_unlock(). The
* implementation guarantees mutual exclusion: exactly one thread on
* one CPU will return from k_spin_lock() at a time. Other CPUs
* trying to acquire a lock already held by another CPU will enter an
* implementation-defined busy loop ("spinning") until the lock is
* released.
*
* Separate spin locks may be nested. It is legal to lock an
* (unlocked) spin lock while holding a different lock. Spin locks
* are not recursive, however: an attempt to acquire a spin lock that
* the CPU already holds will deadlock.
*
* In circumstances where only one CPU exists, the behavior of
* k_spin_lock() remains as specified above, though obviously no
* spinning will take place. Implementations may be free to optimize
* in uniprocessor contexts such that the locking reduces to an
* interrupt mask operation.
*
* @param l A pointer to the spinlock to lock
* @return A key value that must be passed to k_spin_unlock() when the
* lock is released.
*/
static ALWAYS_INLINE k_spinlock_key_t k_spin_lock(struct k_spinlock *l)
{
ARG_UNUSED(l);
k_spinlock_key_t k;
/* Note that we need to use the underlying arch-specific lock
* implementation. The "irq_lock()" API in SMP context is
* actually a wrapper for a global spinlock!
*/
k.key = arch_irq_lock();
#ifdef CONFIG_SPIN_VALIDATE
__ASSERT(z_spin_lock_valid(l), "Recursive spinlock %p", l);
# ifdef CONFIG_KERNEL_COHERENCE
__ASSERT_NO_MSG(z_spin_lock_mem_coherent(l));
# endif
#endif
#ifdef CONFIG_SMP
while (!atomic_cas(&l->locked, 0, 1)) {
}
#endif
#ifdef CONFIG_SPIN_VALIDATE
z_spin_lock_set_owner(l);
#endif
return k;
}
/**
* @brief Unlock a spin lock
*
* This releases a lock acquired by k_spin_lock(). After this
* function is called, any CPU will be able to acquire the lock. If
* other CPUs are currently spinning inside k_spin_lock() waiting for
* this lock, exactly one of them will return synchronously with the
* lock held.
*
* Spin locks must be properly nested. A call to k_spin_unlock() must
* be made on the lock object most recently locked using
* k_spin_lock(), using the key value that it returned. Attempts to
* unlock mis-nested locks, or to unlock locks that are not held, or
* to passing a key parameter other than the one returned from
* k_spin_lock(), are illegal. When CONFIG_SPIN_VALIDATE is set, some
* of these errors can be detected by the framework.
*
* @param l A pointer to the spinlock to release
* @param key The value returned from k_spin_lock() when this lock was
* acquired
*/
static ALWAYS_INLINE void k_spin_unlock(struct k_spinlock *l,
k_spinlock_key_t key)
{
ARG_UNUSED(l);
#ifdef CONFIG_SPIN_VALIDATE
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
#endif
#ifdef CONFIG_SMP
/* Strictly we don't need atomic_clear() here (which is an
* exchange operation that returns the old value). We are always
* setting a zero and (because we hold the lock) know the existing
* state won't change due to a race. But some architectures need
* a memory barrier when used like this, and we don't have a
* Zephyr framework for that.
*/
atomic_clear(&l->locked);
#endif
arch_irq_unlock(key.key);
}
/* Internal function: releases the lock, but leaves local interrupts
* disabled
*/
static ALWAYS_INLINE void k_spin_release(struct k_spinlock *l)
{
ARG_UNUSED(l);
#ifdef CONFIG_SPIN_VALIDATE
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
#endif
#ifdef CONFIG_SMP
atomic_clear(&l->locked);
#endif
}
#ifdef __cplusplus
}
#endif
#endif /* ZEPHYR_INCLUDE_SPINLOCK_H_ */
|