root/arch/powerpc/lib/qspinlock.c
// SPDX-License-Identifier: GPL-2.0-or-later
#include <linux/bug.h>
#include <linux/compiler.h>
#include <linux/export.h>
#include <linux/percpu.h>
#include <linux/processor.h>
#include <linux/smp.h>
#include <linux/topology.h>
#include <linux/sched/clock.h>
#include <asm/qspinlock.h>
#include <asm/paravirt.h>
#include <trace/events/lock.h>

#define MAX_NODES       4

struct qnode {
        struct qnode    *next;
        struct qspinlock *lock;
        int             cpu;
        u8              sleepy; /* 1 if the previous vCPU was preempted or
                                 * if the previous node was sleepy */
        u8              locked; /* 1 if lock acquired */
};

struct qnodes {
        int             count;
        struct qnode nodes[MAX_NODES];
};

/* Tuning parameters */
static int steal_spins __read_mostly = (1 << 5);
static int remote_steal_spins __read_mostly = (1 << 2);
#if _Q_SPIN_TRY_LOCK_STEAL == 1
static const bool maybe_stealers = true;
#else
static bool maybe_stealers __read_mostly = true;
#endif
static int head_spins __read_mostly = (1 << 8);

static bool pv_yield_owner __read_mostly = true;
static bool pv_yield_allow_steal __read_mostly = false;
static bool pv_spin_on_preempted_owner __read_mostly = false;
static bool pv_sleepy_lock __read_mostly = true;
static bool pv_sleepy_lock_sticky __read_mostly = false;
static u64 pv_sleepy_lock_interval_ns __read_mostly = 0;
static int pv_sleepy_lock_factor __read_mostly = 256;
static bool pv_yield_prev __read_mostly = true;
static bool pv_yield_sleepy_owner __read_mostly = true;
static bool pv_prod_head __read_mostly = false;

static DEFINE_PER_CPU_ALIGNED(struct qnodes, qnodes);
static DEFINE_PER_CPU_ALIGNED(u64, sleepy_lock_seen_clock);

#if _Q_SPIN_SPEC_BARRIER == 1
#define spec_barrier() do { asm volatile("ori 31,31,0" ::: "memory"); } while (0)
#else
#define spec_barrier() do { } while (0)
#endif

static __always_inline bool recently_sleepy(void)
{
        /* pv_sleepy_lock is true when this is called */
        if (pv_sleepy_lock_interval_ns) {
                u64 seen = this_cpu_read(sleepy_lock_seen_clock);

                if (seen) {
                        u64 delta = sched_clock() - seen;
                        if (delta < pv_sleepy_lock_interval_ns)
                                return true;
                        this_cpu_write(sleepy_lock_seen_clock, 0);
                }
        }

        return false;
}

static __always_inline int get_steal_spins(bool paravirt, bool sleepy)
{
        if (paravirt && sleepy)
                return steal_spins * pv_sleepy_lock_factor;
        else
                return steal_spins;
}

static __always_inline int get_remote_steal_spins(bool paravirt, bool sleepy)
{
        if (paravirt && sleepy)
                return remote_steal_spins * pv_sleepy_lock_factor;
        else
                return remote_steal_spins;
}

static __always_inline int get_head_spins(bool paravirt, bool sleepy)
{
        if (paravirt && sleepy)
                return head_spins * pv_sleepy_lock_factor;
        else
                return head_spins;
}

static inline u32 encode_tail_cpu(int cpu)
{
        return (cpu + 1) << _Q_TAIL_CPU_OFFSET;
}

static inline int decode_tail_cpu(u32 val)
{
        return (val >> _Q_TAIL_CPU_OFFSET) - 1;
}

static inline int get_owner_cpu(u32 val)
{
        return (val & _Q_OWNER_CPU_MASK) >> _Q_OWNER_CPU_OFFSET;
}

/*
 * Try to acquire the lock if it was not already locked. If the tail matches
 * mytail then clear it, otherwise leave it unchnaged. Return previous value.
 *
 * This is used by the head of the queue to acquire the lock and clean up
 * its tail if it was the last one queued.
 */
static __always_inline u32 trylock_clean_tail(struct qspinlock *lock, u32 tail)
{
        u32 newval = queued_spin_encode_locked_val();
        u32 prev, tmp;

        asm volatile(
"1:     lwarx   %0,0,%2,%7      # trylock_clean_tail                    \n"
        /* This test is necessary if there could be stealers */
"       andi.   %1,%0,%5                                                \n"
"       bne     3f                                                      \n"
        /* Test whether the lock tail == mytail */
"       and     %1,%0,%6                                                \n"
"       cmpw    0,%1,%3                                                 \n"
        /* Merge the new locked value */
"       or      %1,%1,%4                                                \n"
"       bne     2f                                                      \n"
        /* If the lock tail matched, then clear it, otherwise leave it. */
"       andc    %1,%1,%6                                                \n"
"2:     stwcx.  %1,0,%2                                                 \n"
"       bne-    1b                                                      \n"
"\t"    PPC_ACQUIRE_BARRIER "                                           \n"
"3:                                                                     \n"
        : "=&r" (prev), "=&r" (tmp)
        : "r" (&lock->val), "r"(tail), "r" (newval),
          "i" (_Q_LOCKED_VAL),
          "r" (_Q_TAIL_CPU_MASK),
          "i" (_Q_SPIN_EH_HINT)
        : "cr0", "memory");

        return prev;
}

/*
 * Publish our tail, replacing previous tail. Return previous value.
 *
 * This provides a release barrier for publishing node, this pairs with the
 * acquire barrier in get_tail_qnode() when the next CPU finds this tail
 * value.
 */
static __always_inline u32 publish_tail_cpu(struct qspinlock *lock, u32 tail)
{
        u32 prev, tmp;

        kcsan_release();

        asm volatile(
"\t"    PPC_RELEASE_BARRIER "                                           \n"
"1:     lwarx   %0,0,%2         # publish_tail_cpu                      \n"
"       andc    %1,%0,%4                                                \n"
"       or      %1,%1,%3                                                \n"
"       stwcx.  %1,0,%2                                                 \n"
"       bne-    1b                                                      \n"
        : "=&r" (prev), "=&r"(tmp)
        : "r" (&lock->val), "r" (tail), "r"(_Q_TAIL_CPU_MASK)
        : "cr0", "memory");

        return prev;
}

static __always_inline u32 set_mustq(struct qspinlock *lock)
{
        u32 prev;

        asm volatile(
"1:     lwarx   %0,0,%1         # set_mustq                             \n"
"       or      %0,%0,%2                                                \n"
"       stwcx.  %0,0,%1                                                 \n"
"       bne-    1b                                                      \n"
        : "=&r" (prev)
        : "r" (&lock->val), "r" (_Q_MUST_Q_VAL)
        : "cr0", "memory");

        return prev;
}

static __always_inline u32 clear_mustq(struct qspinlock *lock)
{
        u32 prev;

        asm volatile(
"1:     lwarx   %0,0,%1         # clear_mustq                           \n"
"       andc    %0,%0,%2                                                \n"
"       stwcx.  %0,0,%1                                                 \n"
"       bne-    1b                                                      \n"
        : "=&r" (prev)
        : "r" (&lock->val), "r" (_Q_MUST_Q_VAL)
        : "cr0", "memory");

        return prev;
}

static __always_inline bool try_set_sleepy(struct qspinlock *lock, u32 old)
{
        u32 prev;
        u32 new = old | _Q_SLEEPY_VAL;

        BUG_ON(!(old & _Q_LOCKED_VAL));
        BUG_ON(old & _Q_SLEEPY_VAL);

        asm volatile(
"1:     lwarx   %0,0,%1         # try_set_sleepy                        \n"
"       cmpw    0,%0,%2                                                 \n"
"       bne-    2f                                                      \n"
"       stwcx.  %3,0,%1                                                 \n"
"       bne-    1b                                                      \n"
"2:                                                                     \n"
        : "=&r" (prev)
        : "r" (&lock->val), "r"(old), "r" (new)
        : "cr0", "memory");

        return likely(prev == old);
}

static __always_inline void seen_sleepy_owner(struct qspinlock *lock, u32 val)
{
        if (pv_sleepy_lock) {
                if (pv_sleepy_lock_interval_ns)
                        this_cpu_write(sleepy_lock_seen_clock, sched_clock());
                if (!(val & _Q_SLEEPY_VAL))
                        try_set_sleepy(lock, val);
        }
}

static __always_inline void seen_sleepy_lock(void)
{
        if (pv_sleepy_lock && pv_sleepy_lock_interval_ns)
                this_cpu_write(sleepy_lock_seen_clock, sched_clock());
}

static __always_inline void seen_sleepy_node(void)
{
        if (pv_sleepy_lock) {
                if (pv_sleepy_lock_interval_ns)
                        this_cpu_write(sleepy_lock_seen_clock, sched_clock());
                /* Don't set sleepy because we likely have a stale val */
        }
}

static struct qnode *get_tail_qnode(struct qspinlock *lock, int prev_cpu)
{
        struct qnodes *qnodesp = per_cpu_ptr(&qnodes, prev_cpu);
        int idx;

        /*
         * After publishing the new tail and finding a previous tail in the
         * previous val (which is the control dependency), this barrier
         * orders the release barrier in publish_tail_cpu performed by the
         * last CPU, with subsequently looking at its qnode structures
         * after the barrier.
         */
        smp_acquire__after_ctrl_dep();

        for (idx = 0; idx < MAX_NODES; idx++) {
                struct qnode *qnode = &qnodesp->nodes[idx];
                if (qnode->lock == lock)
                        return qnode;
        }

        BUG();
}

/* Called inside spin_begin(). Returns whether or not the vCPU was preempted. */
static __always_inline bool __yield_to_locked_owner(struct qspinlock *lock, u32 val, bool paravirt, bool mustq)
{
        int owner;
        u32 yield_count;
        bool preempted = false;

        BUG_ON(!(val & _Q_LOCKED_VAL));

        if (!paravirt)
                goto relax;

        if (!pv_yield_owner)
                goto relax;

        owner = get_owner_cpu(val);
        yield_count = yield_count_of(owner);

        if ((yield_count & 1) == 0)
                goto relax; /* owner vcpu is running */

        spin_end();

        seen_sleepy_owner(lock, val);
        preempted = true;

        /*
         * Read the lock word after sampling the yield count. On the other side
         * there may a wmb because the yield count update is done by the
         * hypervisor preemption and the value update by the OS, however this
         * ordering might reduce the chance of out of order accesses and
         * improve the heuristic.
         */
        smp_rmb();

        if (READ_ONCE(lock->val) == val) {
                if (mustq)
                        clear_mustq(lock);
                yield_to_preempted(owner, yield_count);
                if (mustq)
                        set_mustq(lock);
                spin_begin();

                /* Don't relax if we yielded. Maybe we should? */
                return preempted;
        }
        spin_begin();
relax:
        spin_cpu_relax();

        return preempted;
}

/* Called inside spin_begin(). Returns whether or not the vCPU was preempted. */
static __always_inline bool yield_to_locked_owner(struct qspinlock *lock, u32 val, bool paravirt)
{
        return __yield_to_locked_owner(lock, val, paravirt, false);
}

/* Called inside spin_begin(). Returns whether or not the vCPU was preempted. */
static __always_inline bool yield_head_to_locked_owner(struct qspinlock *lock, u32 val, bool paravirt)
{
        bool mustq = false;

        if ((val & _Q_MUST_Q_VAL) && pv_yield_allow_steal)
                mustq = true;

        return __yield_to_locked_owner(lock, val, paravirt, mustq);
}

static __always_inline void propagate_sleepy(struct qnode *node, u32 val, bool paravirt)
{
        struct qnode *next;
        int owner;

        if (!paravirt)
                return;
        if (!pv_yield_sleepy_owner)
                return;

        next = READ_ONCE(node->next);
        if (!next)
                return;

        if (next->sleepy)
                return;

        owner = get_owner_cpu(val);
        if (vcpu_is_preempted(owner))
                next->sleepy = 1;
}

/* Called inside spin_begin() */
static __always_inline bool yield_to_prev(struct qspinlock *lock, struct qnode *node, int prev_cpu, bool paravirt)
{
        u32 yield_count;
        bool preempted = false;

        if (!paravirt)
                goto relax;

        if (!pv_yield_sleepy_owner)
                goto yield_prev;

        /*
         * If the previous waiter was preempted it might not be able to
         * propagate sleepy to us, so check the lock in that case too.
         */
        if (node->sleepy || vcpu_is_preempted(prev_cpu)) {
                u32 val = READ_ONCE(lock->val);

                if (val & _Q_LOCKED_VAL) {
                        if (node->next && !node->next->sleepy) {
                                /*
                                 * Propagate sleepy to next waiter. Only if
                                 * owner is preempted, which allows the queue
                                 * to become "non-sleepy" if vCPU preemption
                                 * ceases to occur, even if the lock remains
                                 * highly contended.
                                 */
                                if (vcpu_is_preempted(get_owner_cpu(val)))
                                        node->next->sleepy = 1;
                        }

                        preempted = yield_to_locked_owner(lock, val, paravirt);
                        if (preempted)
                                return preempted;
                }
                node->sleepy = false;
        }

yield_prev:
        if (!pv_yield_prev)
                goto relax;

        yield_count = yield_count_of(prev_cpu);
        if ((yield_count & 1) == 0)
                goto relax; /* owner vcpu is running */

        spin_end();

        preempted = true;
        seen_sleepy_node();

        smp_rmb(); /* See __yield_to_locked_owner comment */

        if (!READ_ONCE(node->locked)) {
                yield_to_preempted(prev_cpu, yield_count);
                spin_begin();
                return preempted;
        }
        spin_begin();

relax:
        spin_cpu_relax();

        return preempted;
}

static __always_inline bool steal_break(u32 val, int iters, bool paravirt, bool sleepy)
{
        if (iters >= get_steal_spins(paravirt, sleepy))
                return true;

        if (IS_ENABLED(CONFIG_NUMA) &&
            (iters >= get_remote_steal_spins(paravirt, sleepy))) {
                int cpu = get_owner_cpu(val);
                if (numa_node_id() != cpu_to_node(cpu))
                        return true;
        }
        return false;
}

static __always_inline bool try_to_steal_lock(struct qspinlock *lock, bool paravirt)
{
        bool seen_preempted = false;
        bool sleepy = false;
        int iters = 0;
        u32 val;

        if (!steal_spins) {
                /* XXX: should spin_on_preempted_owner do anything here? */
                return false;
        }

        /* Attempt to steal the lock */
        spin_begin();
        do {
                bool preempted = false;

                val = READ_ONCE(lock->val);
                if (val & _Q_MUST_Q_VAL)
                        break;
                spec_barrier();

                if (unlikely(!(val & _Q_LOCKED_VAL))) {
                        spin_end();
                        if (__queued_spin_trylock_steal(lock))
                                return true;
                        spin_begin();
                } else {
                        preempted = yield_to_locked_owner(lock, val, paravirt);
                }

                if (paravirt && pv_sleepy_lock) {
                        if (!sleepy) {
                                if (val & _Q_SLEEPY_VAL) {
                                        seen_sleepy_lock();
                                        sleepy = true;
                                } else if (recently_sleepy()) {
                                        sleepy = true;
                                }
                        }
                        if (pv_sleepy_lock_sticky && seen_preempted &&
                            !(val & _Q_SLEEPY_VAL)) {
                                if (try_set_sleepy(lock, val))
                                        val |= _Q_SLEEPY_VAL;
                        }
                }

                if (preempted) {
                        seen_preempted = true;
                        sleepy = true;
                        if (!pv_spin_on_preempted_owner)
                                iters++;
                        /*
                         * pv_spin_on_preempted_owner don't increase iters
                         * while the owner is preempted -- we won't interfere
                         * with it by definition. This could introduce some
                         * latency issue if we continually observe preempted
                         * owners, but hopefully that's a rare corner case of
                         * a badly oversubscribed system.
                         */
                } else {
                        iters++;
                }
        } while (!steal_break(val, iters, paravirt, sleepy));

        spin_end();

        return false;
}

static __always_inline void queued_spin_lock_mcs_queue(struct qspinlock *lock, bool paravirt)
{
        struct qnodes *qnodesp;
        struct qnode *next, *node;
        u32 val, old, tail;
        bool seen_preempted = false;
        bool sleepy = false;
        bool mustq = false;
        int idx;
        int iters = 0;

        BUILD_BUG_ON(CONFIG_NR_CPUS >= (1U << _Q_TAIL_CPU_BITS));

        qnodesp = this_cpu_ptr(&qnodes);
        if (unlikely(qnodesp->count >= MAX_NODES)) {
                spec_barrier();
                while (!queued_spin_trylock(lock))
                        cpu_relax();
                return;
        }

        idx = qnodesp->count++;
        /*
         * Ensure that we increment the head node->count before initialising
         * the actual node. If the compiler is kind enough to reorder these
         * stores, then an IRQ could overwrite our assignments.
         */
        barrier();
        node = &qnodesp->nodes[idx];
        node->next = NULL;
        node->lock = lock;
        node->cpu = smp_processor_id();
        node->sleepy = 0;
        node->locked = 0;

        tail = encode_tail_cpu(node->cpu);

        /*
         * Assign all attributes of a node before it can be published.
         * Issues an lwsync, serving as a release barrier, as well as a
         * compiler barrier.
         */
        old = publish_tail_cpu(lock, tail);

        /*
         * If there was a previous node; link it and wait until reaching the
         * head of the waitqueue.
         */
        if (old & _Q_TAIL_CPU_MASK) {
                int prev_cpu = decode_tail_cpu(old);
                struct qnode *prev = get_tail_qnode(lock, prev_cpu);

                /* Link @node into the waitqueue. */
                WRITE_ONCE(prev->next, node);

                /* Wait for mcs node lock to be released */
                spin_begin();
                while (!READ_ONCE(node->locked)) {
                        spec_barrier();

                        if (yield_to_prev(lock, node, prev_cpu, paravirt))
                                seen_preempted = true;
                }
                spec_barrier();
                spin_end();

                smp_rmb(); /* acquire barrier for the mcs lock */

                /*
                 * Generic qspinlocks have this prefetch here, but it seems
                 * like it could cause additional line transitions because
                 * the waiter will keep loading from it.
                 */
                if (_Q_SPIN_PREFETCH_NEXT) {
                        next = READ_ONCE(node->next);
                        if (next)
                                prefetchw(next);
                }
        }

        /* We're at the head of the waitqueue, wait for the lock. */
again:
        spin_begin();
        for (;;) {
                bool preempted;

                val = READ_ONCE(lock->val);
                if (!(val & _Q_LOCKED_VAL))
                        break;
                spec_barrier();

                if (paravirt && pv_sleepy_lock && maybe_stealers) {
                        if (!sleepy) {
                                if (val & _Q_SLEEPY_VAL) {
                                        seen_sleepy_lock();
                                        sleepy = true;
                                } else if (recently_sleepy()) {
                                        sleepy = true;
                                }
                        }
                        if (pv_sleepy_lock_sticky && seen_preempted &&
                            !(val & _Q_SLEEPY_VAL)) {
                                if (try_set_sleepy(lock, val))
                                        val |= _Q_SLEEPY_VAL;
                        }
                }

                propagate_sleepy(node, val, paravirt);
                preempted = yield_head_to_locked_owner(lock, val, paravirt);
                if (!maybe_stealers)
                        continue;

                if (preempted)
                        seen_preempted = true;

                if (paravirt && preempted) {
                        sleepy = true;

                        if (!pv_spin_on_preempted_owner)
                                iters++;
                } else {
                        iters++;
                }

                if (!mustq && iters >= get_head_spins(paravirt, sleepy)) {
                        mustq = true;
                        set_mustq(lock);
                        val |= _Q_MUST_Q_VAL;
                }
        }
        spec_barrier();
        spin_end();

        /* If we're the last queued, must clean up the tail. */
        old = trylock_clean_tail(lock, tail);
        if (unlikely(old & _Q_LOCKED_VAL)) {
                BUG_ON(!maybe_stealers);
                goto again; /* Can only be true if maybe_stealers. */
        }

        if ((old & _Q_TAIL_CPU_MASK) == tail)
                goto release; /* We were the tail, no next. */

        /* There is a next, must wait for node->next != NULL (MCS protocol) */
        next = READ_ONCE(node->next);
        if (!next) {
                spin_begin();
                while (!(next = READ_ONCE(node->next)))
                        cpu_relax();
                spin_end();
        }
        spec_barrier();

        /*
         * Unlock the next mcs waiter node. Release barrier is not required
         * here because the acquirer is only accessing the lock word, and
         * the acquire barrier we took the lock with orders that update vs
         * this store to locked. The corresponding barrier is the smp_rmb()
         * acquire barrier for mcs lock, above.
         */
        if (paravirt && pv_prod_head) {
                int next_cpu = next->cpu;
                WRITE_ONCE(next->locked, 1);
                if (_Q_SPIN_MISO)
                        asm volatile("miso" ::: "memory");
                if (vcpu_is_preempted(next_cpu))
                        prod_cpu(next_cpu);
        } else {
                WRITE_ONCE(next->locked, 1);
                if (_Q_SPIN_MISO)
                        asm volatile("miso" ::: "memory");
        }

release:
        /*
         * Clear the lock before releasing the node, as another CPU might see stale
         * values if an interrupt occurs after we increment qnodesp->count
         * but before node->lock is initialized. The barrier ensures that
         * there are no further stores to the node after it has been released.
         */
        node->lock = NULL;
        barrier();
        qnodesp->count--;
}

void __lockfunc queued_spin_lock_slowpath(struct qspinlock *lock)
{
        trace_contention_begin(lock, LCB_F_SPIN);
        /*
         * This looks funny, but it induces the compiler to inline both
         * sides of the branch rather than share code as when the condition
         * is passed as the paravirt argument to the functions.
         */
        if (IS_ENABLED(CONFIG_PARAVIRT_SPINLOCKS) && is_shared_processor()) {
                if (try_to_steal_lock(lock, true))
                        spec_barrier();
                else
                        queued_spin_lock_mcs_queue(lock, true);
        } else {
                if (try_to_steal_lock(lock, false))
                        spec_barrier();
                else
                        queued_spin_lock_mcs_queue(lock, false);
        }
        trace_contention_end(lock, 0);
}
EXPORT_SYMBOL(queued_spin_lock_slowpath);

#ifdef CONFIG_PARAVIRT_SPINLOCKS
void pv_spinlocks_init(void)
{
}
#endif

#include <linux/debugfs.h>
static int steal_spins_set(void *data, u64 val)
{
#if _Q_SPIN_TRY_LOCK_STEAL == 1
        /* MAYBE_STEAL remains true */
        steal_spins = val;
#else
        static DEFINE_MUTEX(lock);

        /*
         * The lock slow path has a !maybe_stealers case that can assume
         * the head of queue will not see concurrent waiters. That waiter
         * is unsafe in the presence of stealers, so must keep them away
         * from one another.
         */

        mutex_lock(&lock);
        if (val && !steal_spins) {
                maybe_stealers = true;
                /* wait for queue head waiter to go away */
                synchronize_rcu();
                steal_spins = val;
        } else if (!val && steal_spins) {
                steal_spins = val;
                /* wait for all possible stealers to go away */
                synchronize_rcu();
                maybe_stealers = false;
        } else {
                steal_spins = val;
        }
        mutex_unlock(&lock);
#endif

        return 0;
}

static int steal_spins_get(void *data, u64 *val)
{
        *val = steal_spins;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_steal_spins, steal_spins_get, steal_spins_set, "%llu\n");

static int remote_steal_spins_set(void *data, u64 val)
{
        remote_steal_spins = val;

        return 0;
}

static int remote_steal_spins_get(void *data, u64 *val)
{
        *val = remote_steal_spins;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_remote_steal_spins, remote_steal_spins_get, remote_steal_spins_set, "%llu\n");

static int head_spins_set(void *data, u64 val)
{
        head_spins = val;

        return 0;
}

static int head_spins_get(void *data, u64 *val)
{
        *val = head_spins;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_head_spins, head_spins_get, head_spins_set, "%llu\n");

static int pv_yield_owner_set(void *data, u64 val)
{
        pv_yield_owner = !!val;

        return 0;
}

static int pv_yield_owner_get(void *data, u64 *val)
{
        *val = pv_yield_owner;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_yield_owner, pv_yield_owner_get, pv_yield_owner_set, "%llu\n");

static int pv_yield_allow_steal_set(void *data, u64 val)
{
        pv_yield_allow_steal = !!val;

        return 0;
}

static int pv_yield_allow_steal_get(void *data, u64 *val)
{
        *val = pv_yield_allow_steal;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_yield_allow_steal, pv_yield_allow_steal_get, pv_yield_allow_steal_set, "%llu\n");

static int pv_spin_on_preempted_owner_set(void *data, u64 val)
{
        pv_spin_on_preempted_owner = !!val;

        return 0;
}

static int pv_spin_on_preempted_owner_get(void *data, u64 *val)
{
        *val = pv_spin_on_preempted_owner;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_spin_on_preempted_owner, pv_spin_on_preempted_owner_get, pv_spin_on_preempted_owner_set, "%llu\n");

static int pv_sleepy_lock_set(void *data, u64 val)
{
        pv_sleepy_lock = !!val;

        return 0;
}

static int pv_sleepy_lock_get(void *data, u64 *val)
{
        *val = pv_sleepy_lock;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_sleepy_lock, pv_sleepy_lock_get, pv_sleepy_lock_set, "%llu\n");

static int pv_sleepy_lock_sticky_set(void *data, u64 val)
{
        pv_sleepy_lock_sticky = !!val;

        return 0;
}

static int pv_sleepy_lock_sticky_get(void *data, u64 *val)
{
        *val = pv_sleepy_lock_sticky;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_sleepy_lock_sticky, pv_sleepy_lock_sticky_get, pv_sleepy_lock_sticky_set, "%llu\n");

static int pv_sleepy_lock_interval_ns_set(void *data, u64 val)
{
        pv_sleepy_lock_interval_ns = val;

        return 0;
}

static int pv_sleepy_lock_interval_ns_get(void *data, u64 *val)
{
        *val = pv_sleepy_lock_interval_ns;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_sleepy_lock_interval_ns, pv_sleepy_lock_interval_ns_get, pv_sleepy_lock_interval_ns_set, "%llu\n");

static int pv_sleepy_lock_factor_set(void *data, u64 val)
{
        pv_sleepy_lock_factor = val;

        return 0;
}

static int pv_sleepy_lock_factor_get(void *data, u64 *val)
{
        *val = pv_sleepy_lock_factor;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_sleepy_lock_factor, pv_sleepy_lock_factor_get, pv_sleepy_lock_factor_set, "%llu\n");

static int pv_yield_prev_set(void *data, u64 val)
{
        pv_yield_prev = !!val;

        return 0;
}

static int pv_yield_prev_get(void *data, u64 *val)
{
        *val = pv_yield_prev;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_yield_prev, pv_yield_prev_get, pv_yield_prev_set, "%llu\n");

static int pv_yield_sleepy_owner_set(void *data, u64 val)
{
        pv_yield_sleepy_owner = !!val;

        return 0;
}

static int pv_yield_sleepy_owner_get(void *data, u64 *val)
{
        *val = pv_yield_sleepy_owner;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_yield_sleepy_owner, pv_yield_sleepy_owner_get, pv_yield_sleepy_owner_set, "%llu\n");

static int pv_prod_head_set(void *data, u64 val)
{
        pv_prod_head = !!val;

        return 0;
}

static int pv_prod_head_get(void *data, u64 *val)
{
        *val = pv_prod_head;

        return 0;
}

DEFINE_SIMPLE_ATTRIBUTE(fops_pv_prod_head, pv_prod_head_get, pv_prod_head_set, "%llu\n");

static __init int spinlock_debugfs_init(void)
{
        debugfs_create_file("qspl_steal_spins", 0600, arch_debugfs_dir, NULL, &fops_steal_spins);
        debugfs_create_file("qspl_remote_steal_spins", 0600, arch_debugfs_dir, NULL, &fops_remote_steal_spins);
        debugfs_create_file("qspl_head_spins", 0600, arch_debugfs_dir, NULL, &fops_head_spins);
        if (is_shared_processor()) {
                debugfs_create_file("qspl_pv_yield_owner", 0600, arch_debugfs_dir, NULL, &fops_pv_yield_owner);
                debugfs_create_file("qspl_pv_yield_allow_steal", 0600, arch_debugfs_dir, NULL, &fops_pv_yield_allow_steal);
                debugfs_create_file("qspl_pv_spin_on_preempted_owner", 0600, arch_debugfs_dir, NULL, &fops_pv_spin_on_preempted_owner);
                debugfs_create_file("qspl_pv_sleepy_lock", 0600, arch_debugfs_dir, NULL, &fops_pv_sleepy_lock);
                debugfs_create_file("qspl_pv_sleepy_lock_sticky", 0600, arch_debugfs_dir, NULL, &fops_pv_sleepy_lock_sticky);
                debugfs_create_file("qspl_pv_sleepy_lock_interval_ns", 0600, arch_debugfs_dir, NULL, &fops_pv_sleepy_lock_interval_ns);
                debugfs_create_file("qspl_pv_sleepy_lock_factor", 0600, arch_debugfs_dir, NULL, &fops_pv_sleepy_lock_factor);
                debugfs_create_file("qspl_pv_yield_prev", 0600, arch_debugfs_dir, NULL, &fops_pv_yield_prev);
                debugfs_create_file("qspl_pv_yield_sleepy_owner", 0600, arch_debugfs_dir, NULL, &fops_pv_yield_sleepy_owner);
                debugfs_create_file("qspl_pv_prod_head", 0600, arch_debugfs_dir, NULL, &fops_pv_prod_head);
        }

        return 0;
}
device_initcall(spinlock_debugfs_init);