/* $OpenBSD: kern_lock.c,v 1.86 2026/02/04 00:10:27 jsg Exp $ */ /* * Copyright (c) 2017 Visa Hankala * Copyright (c) 2014 David Gwynne * Copyright (c) 2004 Artur Grabowski * * Permission to use, copy, modify, and distribute this software for any * purpose with or without fee is hereby granted, provided that the above * copyright notice and this permission notice appear in all copies. * * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #include #include #include #include #include #include #include #include #ifdef MP_LOCKDEBUG #ifndef DDB #error "MP_LOCKDEBUG requires DDB" #endif /* * CPU-dependent timing, this needs to be settable from ddb. * Use a "long" to allow larger thresholds on fast 64 bits machines. */ long __mp_lock_spinout = 1L * INT_MAX; #endif /* MP_LOCKDEBUG */ extern int ncpusfound; /* * Min & max numbers of "busy cycles" to waste before trying again to * acquire a contended lock using an atomic operation. * * The min number must be as small as possible to not introduce extra * latency. It also doesn't matter if the first steps of an exponential * backoff are smalls. * * The max number is used to cap the exponential backoff. It should * be small enough to not waste too many cycles in %sys time and big * enough to reduce (ideally avoid) cache line contention. */ #define CPU_MIN_BUSY_CYCLES 1 #define CPU_MAX_BUSY_CYCLES ncpusfound #ifdef MULTIPROCESSOR #include /* CACHELINESIZE */ #include struct __mp_lock kernel_lock; #ifdef __USE_MI_MUTEX static void mtx_init_parking(void); #endif /* __USE_MI_MUTEX */ /* * Functions for manipulating the kernel_lock. We put them here * so that they show up in profiles. */ void _kernel_lock_init(void) { __mp_lock_init(&kernel_lock); #ifdef __USE_MI_MUTEX mtx_init_parking(); #endif /* __USE_MI_MUTEX */ } /* * Acquire/release the kernel lock. Intended for use in the scheduler * and the lower half of the kernel. */ void _kernel_lock(void) { SCHED_ASSERT_UNLOCKED(); __mp_lock(&kernel_lock); } void _kernel_unlock(void) { __mp_unlock(&kernel_lock); } int _kernel_lock_held(void) { if (panicstr || db_active) return 1; return (__mp_lock_held(&kernel_lock, curcpu())); } #ifdef __USE_MI_MPLOCK /* Ticket lock implementation */ #include void ___mp_lock_init(struct __mp_lock *mpl, const struct lock_type *type) { memset(mpl->mpl_cpus, 0, sizeof(mpl->mpl_cpus)); mpl->mpl_users = 0; mpl->mpl_ticket = 1; #ifdef WITNESS mpl->mpl_lock_obj.lo_name = type->lt_name; mpl->mpl_lock_obj.lo_type = type; if (mpl == &kernel_lock) mpl->mpl_lock_obj.lo_flags = LO_WITNESS | LO_INITIALIZED | LO_SLEEPABLE | (LO_CLASS_KERNEL_LOCK << LO_CLASSSHIFT); WITNESS_INIT(&mpl->mpl_lock_obj, type); #endif } static __inline void __mp_lock_spin(struct __mp_lock *mpl, u_int me) { struct schedstate_percpu *spc = &curcpu()->ci_schedstate; #ifdef MP_LOCKDEBUG long nticks = __mp_lock_spinout; #endif spc->spc_spinning++; while (mpl->mpl_ticket != me) { CPU_BUSY_CYCLE(); #ifdef MP_LOCKDEBUG if (--nticks <= 0) { db_printf("%s: %p lock spun out\n", __func__, mpl); db_enter(); nticks = __mp_lock_spinout; } #endif } spc->spc_spinning--; } void __mp_lock(struct __mp_lock *mpl) { struct __mp_lock_cpu *cpu = &mpl->mpl_cpus[cpu_number()]; unsigned long s; #ifdef WITNESS if (!__mp_lock_held(mpl, curcpu())) WITNESS_CHECKORDER(&mpl->mpl_lock_obj, LOP_EXCLUSIVE | LOP_NEWORDER, NULL); #endif s = intr_disable(); if (cpu->mplc_depth++ == 0) cpu->mplc_ticket = atomic_inc_int_nv(&mpl->mpl_users); intr_restore(s); __mp_lock_spin(mpl, cpu->mplc_ticket); membar_enter_after_atomic(); WITNESS_LOCK(&mpl->mpl_lock_obj, LOP_EXCLUSIVE); } void __mp_unlock(struct __mp_lock *mpl) { struct __mp_lock_cpu *cpu = &mpl->mpl_cpus[cpu_number()]; unsigned long s; #ifdef MP_LOCKDEBUG if (!__mp_lock_held(mpl, curcpu())) { db_printf("__mp_unlock(%p): not held lock\n", mpl); db_enter(); } #endif WITNESS_UNLOCK(&mpl->mpl_lock_obj, LOP_EXCLUSIVE); s = intr_disable(); if (--cpu->mplc_depth == 0) { membar_exit(); mpl->mpl_ticket++; } intr_restore(s); } int __mp_release_all(struct __mp_lock *mpl) { struct __mp_lock_cpu *cpu = &mpl->mpl_cpus[cpu_number()]; unsigned long s; int rv; #ifdef WITNESS int i; #endif s = intr_disable(); rv = cpu->mplc_depth; #ifdef WITNESS for (i = 0; i < rv; i++) WITNESS_UNLOCK(&mpl->mpl_lock_obj, LOP_EXCLUSIVE); #endif cpu->mplc_depth = 0; membar_exit(); mpl->mpl_ticket++; intr_restore(s); return (rv); } void __mp_acquire_count(struct __mp_lock *mpl, int count) { while (count--) __mp_lock(mpl); } int __mp_lock_held(struct __mp_lock *mpl, struct cpu_info *ci) { struct __mp_lock_cpu *cpu = &mpl->mpl_cpus[CPU_INFO_UNIT(ci)]; return (cpu->mplc_ticket == mpl->mpl_ticket && cpu->mplc_depth > 0); } #endif /* __USE_MI_MPLOCK */ #endif /* MULTIPROCESSOR */ #ifdef __USE_MI_MUTEX void __mtx_init(struct mutex *mtx, int wantipl) { mtx->mtx_owner = 0; mtx->mtx_wantipl = wantipl; mtx->mtx_oldipl = IPL_NONE; } #ifdef MULTIPROCESSOR struct mtx_waiter { struct mutex *mtx; volatile unsigned int wait; TAILQ_ENTRY(mtx_waiter) entry; }; TAILQ_HEAD(mtx_waitlist, mtx_waiter); struct mtx_park { struct cpu_info *volatile lock; struct mtx_waitlist waiters; } __aligned(CACHELINESIZE); #define MTX_PARKING_BITS 7 #define MTX_PARKING_LOTS (1 << MTX_PARKING_BITS) #define MTX_PARKING_MASK (MTX_PARKING_LOTS - 1) static struct mtx_park mtx_parking[MTX_PARKING_LOTS]; static void mtx_init_parking(void) { size_t i; for (i = 0; i < nitems(mtx_parking); i++) { struct mtx_park *p = &mtx_parking[i]; p->lock = NULL; TAILQ_INIT(&p->waiters); } } #ifdef DDB void mtx_print_parks(void) { size_t i; for (i = 0; i < nitems(mtx_parking); i++) { struct mtx_park *p = &mtx_parking[i]; struct mtx_waiter *w; db_printf("park %zu @ %p lock %p\n", i, p, p->lock); TAILQ_FOREACH(w, &p->waiters, entry) { db_printf("\twaiter mtx %p wait %u\n", w->mtx, w->wait); } } } #endif /* DDB */ static struct mtx_park * mtx_park(struct mutex *mtx) { unsigned long addr = (unsigned long)mtx; addr >>= 6; addr ^= addr >> MTX_PARKING_BITS; addr &= MTX_PARKING_MASK; return &mtx_parking[addr]; } static unsigned long mtx_enter_park(struct mtx_park *p) { struct cpu_info *ci = curcpu(); struct cpu_info *owner; unsigned long m; m = intr_disable(); while ((owner = atomic_cas_ptr(&p->lock, NULL, ci)) != NULL) CPU_BUSY_CYCLE(); membar_enter_after_atomic(); return (m); } static void mtx_leave_park(struct mtx_park *p, unsigned long m) { membar_exit(); p->lock = NULL; intr_restore(m); } static inline unsigned long mtx_cas(struct mutex *mtx, unsigned long e, unsigned long v) { return atomic_cas_ulong(&mtx->mtx_owner, e, v); } int mtx_enter_try(struct mutex *mtx) { struct cpu_info *ci = curcpu(); unsigned long owner, self = (unsigned long)ci; int s; /* Avoid deadlocks after panic or in DDB */ if (panicstr || db_active) return (1); if (mtx->mtx_wantipl != IPL_NONE) s = splraise(mtx->mtx_wantipl); owner = mtx_cas(mtx, 0, self); if (owner == 0) { membar_enter_after_atomic(); if (mtx->mtx_wantipl != IPL_NONE) mtx->mtx_oldipl = s; #ifdef DIAGNOSTIC ci->ci_mutex_level++; #endif WITNESS_LOCK(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE); return (1); } if (mtx->mtx_wantipl != IPL_NONE) splx(s); #ifdef DIAGNOSTIC if (__predict_false((owner & ~1UL) == self)) panic("mtx %p: locking against myself", mtx); #endif return (0); } void mtx_enter(struct mutex *mtx) { struct cpu_info *ci = curcpu(); struct schedstate_percpu *spc = &ci->ci_schedstate; unsigned long owner, self = (unsigned long)ci; struct mtx_park *p; struct mtx_waiter w; unsigned long m; int spins = 0; int s; #ifdef MP_LOCKDEBUG long nticks = __mp_lock_spinout; #endif /* Avoid deadlocks after panic or in DDB */ if (panicstr || db_active) return; WITNESS_CHECKORDER(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE | LOP_NEWORDER, NULL); if (mtx->mtx_wantipl != IPL_NONE) s = splraise(mtx->mtx_wantipl); owner = mtx_cas(mtx, 0, self); if (owner == 0) { /* we got the lock first go. this is the fast path */ goto locked; } #ifdef DIAGNOSTIC if (__predict_false((owner & ~1ULL) == self)) panic("mtx %p: locking against myself", mtx); #endif /* we're going to have to spin for it now */ spc->spc_spinning++; for (spins = 0; spins < 40; spins++) { if (ISSET(owner, 1)) { /* don't spin if cpus are already parked */ break; } CPU_BUSY_CYCLE(); owner = mtx->mtx_owner; if (owner == 0) { owner = mtx_cas(mtx, 0, self); if (owner == 0) goto spinlocked; } } /* take the really slow path */ p = mtx_park(mtx); /* publish our existence in the parking lot */ w.mtx = mtx; m = mtx_enter_park(p); TAILQ_INSERT_TAIL(&p->waiters, &w, entry); mtx_leave_park(p, m); do { unsigned long o; w.wait = 1; /* ensure wait is visible before attempting the cas */ membar_enter(); /* StoreStore | StoreLoad */ o = mtx_cas(mtx, owner, owner | 1); if (o == owner) { while (w.wait) { CPU_BUSY_CYCLE(); #ifdef MP_LOCKDEBUG if (--nticks <= 0) { db_printf("%s: %p lock spun out\n", __func__, mtx); db_enter(); nticks = __mp_lock_spinout; } #endif } membar_consumer(); } else if (o != 0) { owner = o; continue; } owner = mtx_cas(mtx, 0, self | 1); } while (owner != 0); m = mtx_enter_park(p); TAILQ_REMOVE(&p->waiters, &w, entry); mtx_leave_park(p, m); spinlocked: spc->spc_spinning--; locked: membar_enter_after_atomic(); if (mtx->mtx_wantipl != IPL_NONE) mtx->mtx_oldipl = s; #ifdef DIAGNOSTIC ci->ci_mutex_level++; #endif WITNESS_LOCK(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE); } void mtx_leave(struct mutex *mtx) { struct cpu_info *ci = curcpu(); unsigned long owner, self = (unsigned long)ci; int s; /* Avoid deadlocks after panic or in DDB */ if (panicstr || db_active) return; WITNESS_UNLOCK(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE); #ifdef DIAGNOSTIC curcpu()->ci_mutex_level--; #endif s = mtx->mtx_oldipl; membar_exit_before_atomic(); owner = atomic_cas_ulong(&mtx->mtx_owner, self, 0); if (owner != self) { struct mtx_park *p; unsigned long m; struct mtx_waiter *w; #ifdef DIAGNOSTIC if (__predict_false((owner & ~1ULL) != self)) panic("mtx %p: not held", mtx); #endif p = mtx_park(mtx); m = mtx_enter_park(p); mtx->mtx_owner = 0; membar_producer(); TAILQ_FOREACH(w, &p->waiters, entry) { if (w->mtx == mtx) { w->wait = 0; break; } } mtx_leave_park(p, m); } if (mtx->mtx_wantipl != IPL_NONE) splx(s); } #else /* MULTIPROCESSOR */ void mtx_enter(struct mutex *mtx) { unsigned long self = mtx_curcpu(); /* Avoid deadlocks after panic or in DDB */ if (panicstr || db_active) return; WITNESS_CHECKORDER(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE | LOP_NEWORDER, NULL); #ifdef DIAGNOSTIC if (__predict_false(mtx_owner(mtx) == self)) panic("mtx %p: locking against myself", mtx); #endif if (mtx->mtx_wantipl != IPL_NONE) mtx->mtx_oldipl = splraise(mtx->mtx_wantipl); mtx->mtx_owner = self; #ifdef DIAGNOSTIC curcpu()->ci_mutex_level++; #endif WITNESS_LOCK(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE); } int mtx_enter_try(struct mutex *mtx) { mtx_enter(mtx); return (1); } void mtx_leave(struct mutex *mtx) { int s; /* Avoid deadlocks after panic or in DDB */ if (panicstr || db_active) return; MUTEX_ASSERT_LOCKED(mtx); WITNESS_UNLOCK(MUTEX_LOCK_OBJECT(mtx), LOP_EXCLUSIVE); #ifdef DIAGNOSTIC curcpu()->ci_mutex_level--; #endif s = mtx->mtx_oldipl; mtx->mtx_owner = 0; if (mtx->mtx_wantipl != IPL_NONE) splx(s); } #endif /* MULTIPROCESSOR */ #ifdef DDB void db_mtx_enter(struct db_mutex *mtx) { struct cpu_info *ci = curcpu(), *owner; unsigned int i, ncycle = CPU_MIN_BUSY_CYCLES; unsigned long s; #ifdef DIAGNOSTIC if (__predict_false(mtx->mtx_owner == ci)) panic("%s: mtx %p: locking against myself", __func__, mtx); #endif s = intr_disable(); for (;;) { /* * Avoid unconditional atomic operation to prevent cache * line contention. */ owner = mtx->mtx_owner; if (owner == NULL) { owner = atomic_cas_ptr(&mtx->mtx_owner, NULL, ci); if (owner == NULL) break; /* Busy loop with exponential backoff. */ for (i = ncycle; i > 0; i--) CPU_BUSY_CYCLE(); if (ncycle < CPU_MAX_BUSY_CYCLES) ncycle += ncycle; } } membar_enter_after_atomic(); mtx->mtx_intr_state = s; #ifdef DIAGNOSTIC ci->ci_mutex_level++; #endif } void db_mtx_leave(struct db_mutex *mtx) { #ifdef DIAGNOSTIC struct cpu_info *ci = curcpu(); #endif unsigned long s; #ifdef DIAGNOSTIC if (__predict_false(mtx->mtx_owner != ci)) panic("%s: mtx %p: not owned by this CPU", __func__, mtx); ci->ci_mutex_level--; #endif s = mtx->mtx_intr_state; #ifdef MULTIPROCESSOR membar_exit(); #endif mtx->mtx_owner = NULL; intr_restore(s); } #endif /* DDB */ #endif /* __USE_MI_MUTEX */ #ifdef WITNESS void _mtx_init_flags(struct mutex *m, int ipl, const char *name, int flags, const struct lock_type *type) { struct lock_object *lo = MUTEX_LOCK_OBJECT(m); lo->lo_flags = MTX_LO_FLAGS(flags); if (name != NULL) lo->lo_name = name; else lo->lo_name = type->lt_name; WITNESS_INIT(lo, type); _mtx_init(m, ipl); } #endif /* WITNESS */ void pc_lock_init(struct pc_lock *pcl) { pcl->pcl_gen = 0; } unsigned int pc_sprod_enter(struct pc_lock *pcl) { unsigned int gen; gen = pcl->pcl_gen; pcl->pcl_gen = ++gen; membar_producer(); return (gen); } void pc_sprod_leave(struct pc_lock *pcl, unsigned int gen) { membar_producer(); pcl->pcl_gen = ++gen; } #ifdef MULTIPROCESSOR unsigned int pc_mprod_enter(struct pc_lock *pcl) { unsigned int gen, ngen, ogen; gen = pcl->pcl_gen; for (;;) { while (gen & 1) { CPU_BUSY_CYCLE(); gen = pcl->pcl_gen; } ngen = 1 + gen; ogen = atomic_cas_uint(&pcl->pcl_gen, gen, ngen); if (gen == ogen) break; CPU_BUSY_CYCLE(); gen = ogen; } membar_enter_after_atomic(); return (ngen); } void pc_mprod_leave(struct pc_lock *pcl, unsigned int gen) { membar_exit(); pcl->pcl_gen = ++gen; } #else /* MULTIPROCESSOR */ unsigned int pc_mprod_enter(struct pc_lock *) __attribute__((alias("pc_sprod_enter"))); void pc_mprod_leave(struct pc_lock *, unsigned int) __attribute__((alias("pc_sprod_leave"))); #endif /* MULTIPROCESSOR */ void pc_cons_enter(struct pc_lock *pcl, unsigned int *genp) { unsigned int gen; gen = pcl->pcl_gen; while (gen & 1) { CPU_BUSY_CYCLE(); gen = pcl->pcl_gen; } membar_consumer(); *genp = gen; } int pc_cons_leave(struct pc_lock *pcl, unsigned int *genp) { unsigned int gen; membar_consumer(); gen = pcl->pcl_gen; if (gen & 1) { do { CPU_BUSY_CYCLE(); gen = pcl->pcl_gen; } while (gen & 1); } else if (gen == *genp) return (0); *genp = gen; return (EBUSY); }