blob: b4a343d29cc3dcf2c449727daff67af06682f614 [file] [edit]
/*
* Copyright (c) 2018,2024 Intel Corporation
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <kernel_internal.h>
#include <zephyr/spinlock.h>
#include <zephyr/llext/symbol.h>
#define SPIN_CPU_ID_MASK (sizeof(void *) - 1)
/* Outermost spinlock held on each CPU; set-if-NULL on lock,
* clear-if-matches on unlock.
*/
static struct k_spinlock *z_held_spinlock[CONFIG_MP_MAX_NUM_CPUS];
/* Total spinlock hold count per CPU, incremented on every lock and
* decremented on every unlock regardless of nesting depth.
*/
static int z_held_spinlock_count[CONFIG_MP_MAX_NUM_CPUS];
/* Sentinel: set in swap_data by the ztest harness before force-aborting a
* thread that holds a lock after an expected panic/assert.
*/
const uint8_t z_spinlock_abort_sentinel;
bool z_spin_lock_valid(struct k_spinlock *l)
{
uintptr_t thread_cpu = l->thread_cpu;
if (thread_cpu != 0U) {
if ((thread_cpu & SPIN_CPU_ID_MASK) == _current_cpu->id) {
return false;
}
}
return true;
}
EXPORT_SYMBOL(z_spin_lock_valid);
bool z_spin_unlock_valid(struct k_spinlock *l)
{
uint8_t cpu_id = _current_cpu->id;
uintptr_t tcpu = l->thread_cpu;
l->thread_cpu = 0;
if (z_held_spinlock[cpu_id] == l) {
z_held_spinlock[cpu_id] = NULL;
}
if (arch_is_in_isr() && _current->base.thread_state & _THREAD_DUMMY) {
/* Edge case where an ISR aborted _current */
z_held_spinlock_count[cpu_id]--;
__ASSERT(z_held_spinlock_count[cpu_id] >= 0,
"spinlock unlock with no matching lock");
return true;
}
if (tcpu != (cpu_id | (uintptr_t)_current)) {
return false;
}
z_held_spinlock_count[cpu_id]--;
__ASSERT(z_held_spinlock_count[cpu_id] >= 0,
"spinlock unlock with no matching lock");
return true;
}
EXPORT_SYMBOL(z_spin_unlock_valid);
void z_spin_lock_set_owner(struct k_spinlock *l)
{
uint8_t cpu_id = _current_cpu->id;
l->thread_cpu = cpu_id | (uintptr_t)_current;
if (z_held_spinlock[cpu_id] == NULL) {
z_held_spinlock[cpu_id] = l;
}
z_held_spinlock_count[cpu_id]++;
}
EXPORT_SYMBOL(z_spin_lock_set_owner);
/* Called from do_swap() after z_current_thread_set() to transfer ownership
* of an inherited lock. Does NOT update the tracking arrays.
*/
void z_spin_lock_transfer_owner(struct k_spinlock *l)
{
l->thread_cpu = _current_cpu->id | (uintptr_t)_current;
}
EXPORT_SYMBOL(z_spin_lock_transfer_owner);
/* Reset per-CPU spinlock tracking after abnormal thread termination.
* All locks the dying thread held are abandoned (it will never resume to
* release them), so only the locks still being released by the cleanup
* path need to be counted. lock_held=true if exactly one such lock
* remains and will be released after this call.
*/
void z_spin_validate_reset(bool lock_held)
{
uint8_t cpu_id = _current_cpu->id;
z_held_spinlock[cpu_id] = NULL;
z_held_spinlock_count[cpu_id] = lock_held ? 1 : 0;
}
/* Internal: caller must ensure IRQs are disabled. */
static inline struct k_spinlock *z_spin_get_held_lock_locked(uint8_t cpu_id)
{
return z_held_spinlock[cpu_id];
}
#ifdef CONFIG_TEST
struct k_spinlock *z_spin_get_held_lock(void)
{
unsigned int key = arch_irq_lock();
struct k_spinlock *l = z_spin_get_held_lock_locked(_current_cpu->id);
arch_irq_unlock(key);
return l;
}
#endif /* CONFIG_TEST */
/*
* Verify that a context switch is safe to perform. Called from
* do_swap() and z_swap_irqlock() before the scheduler takes over.
*
* swap_lock is the spinlock being released as part of this swap (e.g.
* _sched_spinlock), or NULL for the irq-lock-only path. It may legitimately
* be held at the point of the call; any other held spinlock is a bug.
*
* key must show that interrupts were unlocked before the swap was entered.
* If not, we are context switching out of a nested irq_lock() critical
* section — i.e. breaking the lock of someone higher up the call stack,
* which is forbidden.
*/
void z_assert_can_swap(unsigned int key, struct k_spinlock *swap_lock)
{
uint8_t cpu_id = _current_cpu->id;
struct k_spinlock *held = z_spin_get_held_lock_locked(cpu_id);
int count = z_held_spinlock_count[cpu_id];
int extra = (swap_lock != NULL) ? count - 1 : count;
ARG_UNUSED(held);
ARG_UNUSED(extra);
/* ztest expected-fault: thread aborted with a lock held.*/
if ((_current->base.thread_state & _THREAD_DEAD) &&
_current->base.swap_data == (void *)&z_spinlock_abort_sentinel) {
z_spin_validate_reset(swap_lock != NULL);
} else {
__ASSERT(extra >= 0,
"swap_lock %p is not tracked in the spinlock hold count!", swap_lock);
__ASSERT(held == NULL || held == swap_lock,
"Context switching while holding spinlock %p!", held);
/* Catches lock(A); lock(B); unlock(A); z_swap(): slot cleared when A
* is released but count stays at 1 because B is still held.
*/
__ASSERT(extra == 0,
"Context switching while holding %d extra spinlock(s)!", extra);
}
#ifndef CONFIG_ARM64
/* Dummy threads start with IRQs masked and dead threads are being
* torn down; in both cases the IRQ state self-heals on the next
* switch-in, so they are exempt. Disabled on ARM64 where FP/SIMD
* usage in exception context may leave IRQs masked without holding
* a lock (see #94285).
*/
__ASSERT(arch_irq_unlocked(key) ||
_current->base.thread_state & (_THREAD_DUMMY | _THREAD_DEAD),
"Context switching with irq_lock held!");
#endif
}
#ifdef CONFIG_KERNEL_COHERENCE
bool z_spin_lock_mem_coherent(struct k_spinlock *l)
{
return sys_cache_is_mem_coherent((void *)l);
}
EXPORT_SYMBOL(z_spin_lock_mem_coherent);
#endif /* CONFIG_KERNEL_COHERENCE */