root/sys/kern/kern_lock.c
/*      $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 <dlg@openbsd.org>
 * Copyright (c) 2004 Artur Grabowski <art@openbsd.org>
 *
 * 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 <sys/param.h>
#include <sys/systm.h>
#include <sys/sched.h>
#include <sys/atomic.h>
#include <sys/witness.h>
#include <sys/mutex.h>
#include <sys/pclock.h>

#include <ddb/db_output.h>

#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 <sys/percpu.h> /* CACHELINESIZE */
#include <sys/mplock.h>
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 <machine/cpu.h>

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);
}