You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
206 lines
6.1 KiB
206 lines
6.1 KiB
/* |
|
* 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_ */
|
|
|