998 lines
23 KiB
C
998 lines
23 KiB
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>
|
|
|
|
#define MAX_NODES 4
|
|
|
|
struct qnode {
|
|
struct qnode *next;
|
|
struct qspinlock *lock;
|
|
int cpu;
|
|
int yield_cpu;
|
|
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_propagate_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;
|
|
|
|
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(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_LOCKED_VAL) {
|
|
if (!(val & _Q_SLEEPY_VAL))
|
|
try_set_sleepy(lock, val);
|
|
}
|
|
}
|
|
}
|
|
|
|
static struct qnode *get_tail_qnode(struct qspinlock *lock, u32 val)
|
|
{
|
|
int cpu = decode_tail_cpu(val);
|
|
struct qnodes *qnodesp = per_cpu_ptr(&qnodes, 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_yield_cpu(struct qnode *node, u32 val, int *set_yield_cpu, bool paravirt)
|
|
{
|
|
struct qnode *next;
|
|
int owner;
|
|
|
|
if (!paravirt)
|
|
return;
|
|
if (!pv_yield_propagate_owner)
|
|
return;
|
|
|
|
owner = get_owner_cpu(val);
|
|
if (*set_yield_cpu == owner)
|
|
return;
|
|
|
|
next = READ_ONCE(node->next);
|
|
if (!next)
|
|
return;
|
|
|
|
if (vcpu_is_preempted(owner)) {
|
|
next->yield_cpu = owner;
|
|
*set_yield_cpu = owner;
|
|
} else if (*set_yield_cpu != -1) {
|
|
next->yield_cpu = owner;
|
|
*set_yield_cpu = owner;
|
|
}
|
|
}
|
|
|
|
/* Called inside spin_begin() */
|
|
static __always_inline bool yield_to_prev(struct qspinlock *lock, struct qnode *node, u32 val, bool paravirt)
|
|
{
|
|
int prev_cpu = decode_tail_cpu(val);
|
|
u32 yield_count;
|
|
int yield_cpu;
|
|
bool preempted = false;
|
|
|
|
if (!paravirt)
|
|
goto relax;
|
|
|
|
if (!pv_yield_propagate_owner)
|
|
goto yield_prev;
|
|
|
|
yield_cpu = READ_ONCE(node->yield_cpu);
|
|
if (yield_cpu == -1) {
|
|
/* Propagate back the -1 CPU */
|
|
if (node->next && node->next->yield_cpu != -1)
|
|
node->next->yield_cpu = yield_cpu;
|
|
goto yield_prev;
|
|
}
|
|
|
|
yield_count = yield_count_of(yield_cpu);
|
|
if ((yield_count & 1) == 0)
|
|
goto yield_prev; /* owner vcpu is running */
|
|
|
|
spin_end();
|
|
|
|
preempted = true;
|
|
seen_sleepy_node(lock, val);
|
|
|
|
smp_rmb();
|
|
|
|
if (yield_cpu == node->yield_cpu) {
|
|
if (node->next && node->next->yield_cpu != yield_cpu)
|
|
node->next->yield_cpu = yield_cpu;
|
|
yield_to_preempted(yield_cpu, yield_count);
|
|
spin_begin();
|
|
return preempted;
|
|
}
|
|
spin_begin();
|
|
|
|
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(lock, val);
|
|
|
|
smp_rmb(); /* See __yield_to_locked_owner comment */
|
|
|
|
if (!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 set_yield_cpu = -1;
|
|
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->yield_cpu = -1;
|
|
node->locked = 0;
|
|
|
|
tail = encode_tail_cpu(node->cpu);
|
|
|
|
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) {
|
|
struct qnode *prev = get_tail_qnode(lock, old);
|
|
|
|
/* Link @node into the waitqueue. */
|
|
WRITE_ONCE(prev->next, node);
|
|
|
|
/* Wait for mcs node lock to be released */
|
|
spin_begin();
|
|
while (!node->locked) {
|
|
spec_barrier();
|
|
|
|
if (yield_to_prev(lock, node, old, paravirt))
|
|
seen_preempted = true;
|
|
}
|
|
spec_barrier();
|
|
spin_end();
|
|
|
|
/* Clear out stale propagated yield_cpu */
|
|
if (paravirt && pv_yield_propagate_owner && node->yield_cpu != -1)
|
|
node->yield_cpu = -1;
|
|
|
|
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_yield_cpu(node, val, &set_yield_cpu, 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:
|
|
qnodesp->count--; /* release the node */
|
|
}
|
|
|
|
void queued_spin_lock_slowpath(struct qspinlock *lock)
|
|
{
|
|
/*
|
|
* 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();
|
|
return;
|
|
}
|
|
queued_spin_lock_mcs_queue(lock, true);
|
|
} else {
|
|
if (try_to_steal_lock(lock, false)) {
|
|
spec_barrier();
|
|
return;
|
|
}
|
|
queued_spin_lock_mcs_queue(lock, false);
|
|
}
|
|
}
|
|
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_propagate_owner_set(void *data, u64 val)
|
|
{
|
|
pv_yield_propagate_owner = !!val;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int pv_yield_propagate_owner_get(void *data, u64 *val)
|
|
{
|
|
*val = pv_yield_propagate_owner;
|
|
|
|
return 0;
|
|
}
|
|
|
|
DEFINE_SIMPLE_ATTRIBUTE(fops_pv_yield_propagate_owner, pv_yield_propagate_owner_get, pv_yield_propagate_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_propagate_owner", 0600, arch_debugfs_dir, NULL, &fops_pv_yield_propagate_owner);
|
|
debugfs_create_file("qspl_pv_prod_head", 0600, arch_debugfs_dir, NULL, &fops_pv_prod_head);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
device_initcall(spinlock_debugfs_init);
|