2018-01-25 23:04:32 +01:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2018 Intel Corporation.
|
|
|
|
*
|
|
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
|
|
*/
|
2022-02-01 21:30:19 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @file
|
|
|
|
* @brief Public interface for spinlocks
|
|
|
|
*/
|
|
|
|
|
2018-09-14 19:43:44 +02:00
|
|
|
#ifndef ZEPHYR_INCLUDE_SPINLOCK_H_
|
|
|
|
#define ZEPHYR_INCLUDE_SPINLOCK_H_
|
2018-01-25 23:04:32 +01:00
|
|
|
|
2019-06-25 18:25:32 +02:00
|
|
|
#include <sys/atomic.h>
|
2019-06-26 16:33:39 +02:00
|
|
|
#include <sys/__assert.h>
|
2019-03-14 19:41:21 +01:00
|
|
|
#include <stdbool.h>
|
2020-09-28 22:24:43 +02:00
|
|
|
#include <arch/cpu.h>
|
2019-01-30 21:27:43 +01:00
|
|
|
|
2021-01-28 08:45:50 +01:00
|
|
|
#ifdef __cplusplus
|
|
|
|
extern "C" {
|
|
|
|
#endif
|
|
|
|
|
2022-02-01 21:30:19 +01:00
|
|
|
/**
|
|
|
|
* @brief Spinlock APIs
|
|
|
|
* @defgroup spinlock_apis Spinlock APIs
|
|
|
|
* @ingroup kernel_apis
|
|
|
|
* @{
|
|
|
|
*/
|
|
|
|
|
2020-09-28 22:25:35 +02:00
|
|
|
struct z_spinlock_key {
|
2018-01-25 23:04:32 +01:00
|
|
|
int key;
|
|
|
|
};
|
|
|
|
|
2020-05-07 18:47:25 +02:00
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2020-09-28 22:24:43 +02:00
|
|
|
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);
|
2021-01-12 08:27:55 +01:00
|
|
|
BUILD_ASSERT(CONFIG_MP_NUM_CPUS <= 4, "Too many CPUs for mask");
|
2021-02-03 01:35:15 +01:00
|
|
|
|
|
|
|
# ifdef CONFIG_KERNEL_COHERENCE
|
|
|
|
bool z_spin_lock_mem_coherent(struct k_spinlock *l);
|
|
|
|
# endif /* CONFIG_KERNEL_COHERENCE */
|
|
|
|
|
2020-09-28 22:24:43 +02:00
|
|
|
#endif /* CONFIG_SPIN_VALIDATE */
|
2020-05-07 18:47:25 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2020-09-28 22:25:35 +02:00
|
|
|
typedef struct z_spinlock_key k_spinlock_key_t;
|
2018-01-25 23:04:32 +01:00
|
|
|
|
2020-05-07 18:47:25 +02:00
|
|
|
/**
|
|
|
|
* @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.
|
|
|
|
*/
|
2019-01-28 18:35:37 +01:00
|
|
|
static ALWAYS_INLINE k_spinlock_key_t k_spin_lock(struct k_spinlock *l)
|
2018-01-25 23:04:32 +01:00
|
|
|
{
|
2018-07-24 21:19:16 +02:00
|
|
|
ARG_UNUSED(l);
|
2018-01-25 23:04:32 +01:00
|
|
|
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!
|
|
|
|
*/
|
2019-11-07 21:43:29 +01:00
|
|
|
k.key = arch_irq_lock();
|
2018-01-25 23:04:32 +01:00
|
|
|
|
2019-12-13 11:24:56 +01:00
|
|
|
#ifdef CONFIG_SPIN_VALIDATE
|
2020-01-11 02:17:05 +01:00
|
|
|
__ASSERT(z_spin_lock_valid(l), "Recursive spinlock %p", l);
|
2020-12-07 19:15:42 +01:00
|
|
|
# ifdef CONFIG_KERNEL_COHERENCE
|
2021-02-03 01:35:15 +01:00
|
|
|
__ASSERT_NO_MSG(z_spin_lock_mem_coherent(l));
|
kernel: Add cache coherence management framework
Zephyr SMP kernels need to be able to run on architectures with
incoherent caches. Naive implementation of synchronization on such
architectures requires extensive cache flushing (e.g. flush+invalidate
everything on every spin lock operation, flush on every unlock!) and
is a performance problem.
Instead, many of these systems will have access to separate "coherent"
(usually uncached) and "incoherent" regions of memory. Where this is
available, place all writable data sections by default into the
coherent region. An "__incoherent" attribute flag is defined for data
regions that are known to be CPU-local and which should use the cache.
By default, this is used for stack memory.
Stack memory will be incoherent by default, as by definition it is
local to its current thread. This requires special cache management
on context switch, so an arch API has been added for that.
Also, when enabled, add assertions to strategic places to ensure that
shared kernel data is indeed coherent. We check thread objects, the
_kernel struct, waitq's, timeouts and spinlocks. In practice almost
all kernel synchronization is built on top of these structures, and
any shared data structs will contain at least one of them.
Signed-off-by: Andy Ross <andrew.j.ross@intel.com>
Signed-off-by: Anas Nashif <anas.nashif@intel.com>
2020-05-13 17:34:04 +02:00
|
|
|
# endif
|
2019-01-30 21:27:43 +01:00
|
|
|
#endif
|
|
|
|
|
2018-01-25 23:04:32 +01:00
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
while (!atomic_cas(&l->locked, 0, 1)) {
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2019-12-13 11:24:56 +01:00
|
|
|
#ifdef CONFIG_SPIN_VALIDATE
|
2019-02-20 19:11:24 +01:00
|
|
|
z_spin_lock_set_owner(l);
|
|
|
|
#endif
|
2018-01-25 23:04:32 +01:00
|
|
|
return k;
|
|
|
|
}
|
|
|
|
|
2020-05-07 18:47:25 +02:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
2019-01-28 18:35:37 +01:00
|
|
|
static ALWAYS_INLINE void k_spin_unlock(struct k_spinlock *l,
|
|
|
|
k_spinlock_key_t key)
|
2018-01-25 23:04:32 +01:00
|
|
|
{
|
2019-02-05 18:35:57 +01:00
|
|
|
ARG_UNUSED(l);
|
2019-12-13 11:24:56 +01:00
|
|
|
#ifdef CONFIG_SPIN_VALIDATE
|
2020-01-11 02:17:05 +01:00
|
|
|
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
|
2019-01-30 21:27:43 +01:00
|
|
|
#endif
|
|
|
|
|
2018-01-25 23:04:32 +01:00
|
|
|
#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
|
2019-11-07 21:43:29 +01:00
|
|
|
arch_irq_unlock(key.key);
|
2018-01-25 23:04:32 +01:00
|
|
|
}
|
|
|
|
|
2018-07-24 19:42:12 +02:00
|
|
|
/* Internal function: releases the lock, but leaves local interrupts
|
|
|
|
* disabled
|
|
|
|
*/
|
|
|
|
static ALWAYS_INLINE void k_spin_release(struct k_spinlock *l)
|
|
|
|
{
|
2019-02-05 18:35:57 +01:00
|
|
|
ARG_UNUSED(l);
|
2019-12-13 11:24:56 +01:00
|
|
|
#ifdef CONFIG_SPIN_VALIDATE
|
2020-01-11 02:17:05 +01:00
|
|
|
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock %p", l);
|
2018-07-24 19:42:12 +02:00
|
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
|
|
atomic_clear(&l->locked);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
|
2022-02-01 21:30:19 +01:00
|
|
|
/** @} */
|
|
|
|
|
2021-01-28 08:45:50 +01:00
|
|
|
#ifdef __cplusplus
|
|
|
|
}
|
|
|
|
#endif
|
2018-07-24 19:42:12 +02:00
|
|
|
|
2018-09-14 19:43:44 +02:00
|
|
|
#endif /* ZEPHYR_INCLUDE_SPINLOCK_H_ */
|