129 lines
3.1 KiB
C
129 lines
3.1 KiB
C
/*
|
|
* 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>
|
|
|
|
/* These stubs aren't provided by the mocking framework, and I can't
|
|
* find a proper place to put them as mocking seems not to have a
|
|
* proper "arch" layer.
|
|
*/
|
|
#ifdef ZTEST_UNITTEST
|
|
static inline int z_arch_irq_lock(void)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
static inline void z_arch_irq_unlock(int key)
|
|
{
|
|
ARG_UNUSED(key);
|
|
}
|
|
#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. (Note
|
|
* we're using the kconfig value here. This isn't defined for every
|
|
* board, but the default of zero works well as an "infinity"
|
|
* fallback. There is a DT_FLASH_SIZE parameter too, but that seems
|
|
* even more poorly supported.
|
|
*/
|
|
#if (CONFIG_FLASH_SIZE == 0) || (CONFIG_FLASH_SIZE > 32)
|
|
#if defined(CONFIG_ASSERT) && (CONFIG_MP_NUM_CPUS < 4)
|
|
#include <sys/__assert.h>
|
|
#include <stdbool.h>
|
|
struct k_spinlock;
|
|
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);
|
|
#define SPIN_VALIDATE
|
|
#endif
|
|
#endif
|
|
|
|
struct k_spinlock_key {
|
|
int key;
|
|
};
|
|
|
|
typedef struct k_spinlock_key k_spinlock_key_t;
|
|
|
|
struct k_spinlock {
|
|
#ifdef CONFIG_SMP
|
|
atomic_t locked;
|
|
#endif
|
|
|
|
#ifdef SPIN_VALIDATE
|
|
/* Stores the thread that holds the lock with the locking CPU
|
|
* ID in the bottom two bits.
|
|
*/
|
|
uintptr_t thread_cpu;
|
|
#endif
|
|
};
|
|
|
|
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 = z_arch_irq_lock();
|
|
|
|
#ifdef SPIN_VALIDATE
|
|
__ASSERT(z_spin_lock_valid(l), "Recursive spinlock");
|
|
#endif
|
|
|
|
#ifdef CONFIG_SMP
|
|
while (!atomic_cas(&l->locked, 0, 1)) {
|
|
}
|
|
#endif
|
|
|
|
#ifdef SPIN_VALIDATE
|
|
z_spin_lock_set_owner(l);
|
|
#endif
|
|
return k;
|
|
}
|
|
|
|
static ALWAYS_INLINE void k_spin_unlock(struct k_spinlock *l,
|
|
k_spinlock_key_t key)
|
|
{
|
|
ARG_UNUSED(l);
|
|
#ifdef SPIN_VALIDATE
|
|
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock!");
|
|
#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
|
|
z_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 SPIN_VALIDATE
|
|
__ASSERT(z_spin_unlock_valid(l), "Not my spinlock!");
|
|
#endif
|
|
#ifdef CONFIG_SMP
|
|
atomic_clear(&l->locked);
|
|
#endif
|
|
}
|
|
|
|
|
|
#endif /* ZEPHYR_INCLUDE_SPINLOCK_H_ */
|