#include <sys/cdefs.h>
__KERNEL_RCSID(0, "$NetBSD: kern_lock.c,v 1.195 2026/03/29 08:36:43 kre Exp $");
#ifdef _KERNEL_OPT
#include "opt_lockdebug.h"
#endif
#include <sys/param.h>
#include <sys/types.h>
#include <sys/atomic.h>
#include <sys/cpu.h>
#include <sys/kernel.h>
#include <sys/lock.h>
#include <sys/lockdebug.h>
#include <sys/lwp.h>
#include <sys/proc.h>
#include <sys/pserialize.h>
#include <sys/sdt.h>
#include <sys/syslog.h>
#include <sys/systm.h>
#if defined(DIAGNOSTIC) && !defined(LOCKDEBUG)
#include <sys/ksyms.h>
#endif
#include <machine/lock.h>
#include <dev/lockstat.h>
SDT_PROBE_DEFINE1(sdt, kernel, lock, entry,
"unsigned");
SDT_PROBE_DEFINE1(sdt, kernel, lock, exit,
"unsigned");
#define RETURN_ADDRESS (uintptr_t)__builtin_return_address(0)
bool kernel_lock_dodebug;
struct kernel_lock {
__cpu_simple_lock_t lock __aligned(CACHE_LINE_SIZE);
struct cpu_info *volatile holder;
} kernel_lock_cacheline[CACHE_LINE_SIZE / sizeof(struct kernel_lock)]
__cacheline_aligned;
__strong_alias(kernel_lock, kernel_lock_cacheline)
#define kernel_lock_holder (kernel_lock_cacheline[0].holder)
void
assert_sleepable(void)
{
const char *reason;
long pctr;
bool idle;
if (__predict_false(panicstr != NULL)) {
return;
}
LOCKDEBUG_BARRIER(kernel_lock, 1);
do {
pctr = lwp_pctr();
idle = CURCPU_IDLE_P();
} while (__predict_false(pctr != lwp_pctr()));
reason = NULL;
if (__predict_false(idle) && !cold) {
reason = "idle";
goto panic;
}
if (__predict_false(cpu_intr_p())) {
reason = "interrupt";
goto panic;
}
if (__predict_false(cpu_softintr_p())) {
reason = "softint";
goto panic;
}
if (__predict_false(!pserialize_not_in_read_section())) {
reason = "pserialize";
goto panic;
}
return;
panic: panic("%s: %s caller=%p", __func__, reason, (void *)RETURN_ADDRESS);
}
#define _KERNEL_LOCK_ABORT(msg) \
LOCKDEBUG_ABORT(__func__, __LINE__, kernel_lock, &_kernel_lock_ops, msg)
#ifdef LOCKDEBUG
#define _KERNEL_LOCK_ASSERT(cond) \
do { \
if (!(cond)) \
_KERNEL_LOCK_ABORT("assertion failed: " #cond); \
} while ( 0)
#else
#define _KERNEL_LOCK_ASSERT(cond)
#endif
static void _kernel_lock_dump(const volatile void *, lockop_printer_t);
lockops_t _kernel_lock_ops = {
.lo_name = "Kernel lock",
.lo_type = LOCKOPS_SPIN,
.lo_dump = _kernel_lock_dump,
};
#ifdef DDB
#include <ddb/ddb.h>
#endif
static void
kernel_lock_trace_ipi(void *cookie)
{
printf("%s[%d %s]: hogging kernel lock\n", cpu_name(curcpu()),
curlwp->l_lid,
curlwp->l_name ? curlwp->l_name : curproc->p_comm);
#ifdef DDB
db_stacktrace();
__insn_barrier();
return;
#endif
}
static void
kernel_lock_spinout(void)
{
static volatile unsigned kernel_lock_last_report;
ipi_msg_t msg = {
.func = kernel_lock_trace_ipi,
};
unsigned now, then;
struct cpu_info *holder;
kpreempt_disable();
if (!__SIMPLELOCK_LOCKED_P(kernel_lock))
goto out;
holder = atomic_load_relaxed(&kernel_lock_holder);
if (holder == NULL)
goto out;
if (holder == curcpu())
goto out;
then = atomic_load_relaxed(&kernel_lock_last_report);
now = time_uptime;
if (now - then <= 10)
goto out;
if (atomic_cas_uint(&kernel_lock_last_report, then, now) != then)
goto out;
printf("%s[%d %s]: kernel lock spinout\n", cpu_name(curcpu()),
curlwp->l_lid,
curlwp->l_name ? curlwp->l_name : curproc->p_comm);
ipi_unicast(&msg, holder);
ipi_wait(&msg);
out:
kpreempt_enable();
#ifdef LOCKDEBUG
_KERNEL_LOCK_ABORT("spinout");
#endif
return;
}
void
kernel_lock_init(void)
{
__cpu_simple_lock_init(kernel_lock);
kernel_lock_dodebug = LOCKDEBUG_ALLOC(kernel_lock, &_kernel_lock_ops,
RETURN_ADDRESS);
}
CTASSERT(CACHE_LINE_SIZE >= sizeof(__cpu_simple_lock_t));
static void
_kernel_lock_dump(const volatile void *junk, lockop_printer_t pr)
{
struct cpu_info *ci = curcpu();
(void)junk;
pr("curcpu holds : %18d wanted by: %#018lx\n",
ci->ci_biglock_count, (long)ci->ci_biglock_wanted);
}
void
_kernel_lock(int nlocks)
{
struct cpu_info *ci;
LOCKSTAT_TIMER(spintime);
LOCKSTAT_FLAG(lsflag);
struct lwp *owant;
u_int starttime;
int s;
struct lwp *l = curlwp;
_KERNEL_LOCK_ASSERT(nlocks > 0);
s = splvm();
ci = curcpu();
if (ci->ci_biglock_count != 0) {
_KERNEL_LOCK_ASSERT(__SIMPLELOCK_LOCKED_P(kernel_lock));
SDT_PROBE1(sdt, kernel, lock, entry, nlocks);
ci->ci_biglock_count += nlocks;
l->l_blcnt += nlocks;
splx(s);
return;
}
_KERNEL_LOCK_ASSERT(l->l_blcnt == 0);
LOCKDEBUG_WANTLOCK(kernel_lock_dodebug, kernel_lock, RETURN_ADDRESS,
0);
if (__predict_true(__cpu_simple_lock_try(kernel_lock))) {
atomic_store_relaxed(&kernel_lock_holder, curcpu());
SDT_PROBE1(sdt, kernel, lock, entry, nlocks);
ci->ci_biglock_count = nlocks;
l->l_blcnt = nlocks;
LOCKDEBUG_LOCKED(kernel_lock_dodebug, kernel_lock, NULL,
RETURN_ADDRESS, 0);
splx(s);
return;
}
membar_producer();
owant = ci->ci_biglock_wanted;
atomic_store_relaxed(&ci->ci_biglock_wanted, l);
#if defined(DIAGNOSTIC) && !defined(LOCKDEBUG)
l->l_ld_wanted = __builtin_return_address(0);
#endif
LOCKSTAT_ENTER(lsflag);
LOCKSTAT_START_TIMER(lsflag, spintime);
starttime = getticks();
do {
splx(s);
while (__SIMPLELOCK_LOCKED_P(kernel_lock)) {
if (start_init_exec &&
(getticks() - starttime) > 10*hz) {
kernel_lock_spinout();
}
SPINLOCK_BACKOFF_HOOK;
SPINLOCK_SPIN_HOOK;
}
s = splvm();
} while (!__cpu_simple_lock_try(kernel_lock));
atomic_store_relaxed(&kernel_lock_holder, curcpu());
SDT_PROBE1(sdt, kernel, lock, entry, nlocks);
ci->ci_biglock_count = nlocks;
l->l_blcnt = nlocks;
LOCKSTAT_STOP_TIMER(lsflag, spintime);
LOCKDEBUG_LOCKED(kernel_lock_dodebug, kernel_lock, NULL,
RETURN_ADDRESS, 0);
if (owant == NULL) {
LOCKSTAT_EVENT_RA(lsflag, kernel_lock,
LB_KERNEL_LOCK | LB_SPIN, 1, spintime, RETURN_ADDRESS);
}
LOCKSTAT_EXIT(lsflag);
splx(s);
(void)atomic_swap_ptr(&ci->ci_biglock_wanted, owant);
#ifndef __HAVE_ATOMIC_AS_MEMBAR
membar_enter();
#endif
}
void
_kernel_unlock(int nlocks, int *countp)
{
struct cpu_info *ci;
u_int olocks;
int s;
struct lwp *l = curlwp;
_KERNEL_LOCK_ASSERT(nlocks < 2);
olocks = l->l_blcnt;
if (olocks == 0) {
_KERNEL_LOCK_ASSERT(nlocks <= 0);
if (countp != NULL)
*countp = 0;
return;
}
_KERNEL_LOCK_ASSERT(__SIMPLELOCK_LOCKED_P(kernel_lock));
if (nlocks == 0)
nlocks = olocks;
else if (nlocks == -1) {
nlocks = 1;
_KERNEL_LOCK_ASSERT(olocks == 1);
}
s = splvm();
ci = curcpu();
_KERNEL_LOCK_ASSERT(ci->ci_biglock_count >= l->l_blcnt);
if (ci->ci_biglock_count == nlocks) {
LOCKDEBUG_UNLOCKED(kernel_lock_dodebug, kernel_lock,
RETURN_ADDRESS, 0);
ci->ci_biglock_count = 0;
__cpu_simple_unlock(kernel_lock);
l->l_blcnt -= nlocks;
splx(s);
if (l->l_dopreempt)
kpreempt(0);
} else {
ci->ci_biglock_count -= nlocks;
l->l_blcnt -= nlocks;
splx(s);
}
SDT_PROBE1(sdt, kernel, lock, exit, nlocks);
if (countp != NULL)
*countp = olocks;
}
bool
_kernel_locked_p(void)
{
return __SIMPLELOCK_LOCKED_P(kernel_lock);
}