162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci *    Out of line spinlock code.
462306a36Sopenharmony_ci *
562306a36Sopenharmony_ci *    Copyright IBM Corp. 2004, 2006
662306a36Sopenharmony_ci *    Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci
962306a36Sopenharmony_ci#include <linux/types.h>
1062306a36Sopenharmony_ci#include <linux/export.h>
1162306a36Sopenharmony_ci#include <linux/spinlock.h>
1262306a36Sopenharmony_ci#include <linux/jiffies.h>
1362306a36Sopenharmony_ci#include <linux/init.h>
1462306a36Sopenharmony_ci#include <linux/smp.h>
1562306a36Sopenharmony_ci#include <linux/percpu.h>
1662306a36Sopenharmony_ci#include <linux/io.h>
1762306a36Sopenharmony_ci#include <asm/alternative.h>
1862306a36Sopenharmony_ci
1962306a36Sopenharmony_ciint spin_retry = -1;
2062306a36Sopenharmony_ci
2162306a36Sopenharmony_cistatic int __init spin_retry_init(void)
2262306a36Sopenharmony_ci{
2362306a36Sopenharmony_ci	if (spin_retry < 0)
2462306a36Sopenharmony_ci		spin_retry = 1000;
2562306a36Sopenharmony_ci	return 0;
2662306a36Sopenharmony_ci}
2762306a36Sopenharmony_ciearly_initcall(spin_retry_init);
2862306a36Sopenharmony_ci
2962306a36Sopenharmony_ci/*
3062306a36Sopenharmony_ci * spin_retry= parameter
3162306a36Sopenharmony_ci */
3262306a36Sopenharmony_cistatic int __init spin_retry_setup(char *str)
3362306a36Sopenharmony_ci{
3462306a36Sopenharmony_ci	spin_retry = simple_strtoul(str, &str, 0);
3562306a36Sopenharmony_ci	return 1;
3662306a36Sopenharmony_ci}
3762306a36Sopenharmony_ci__setup("spin_retry=", spin_retry_setup);
3862306a36Sopenharmony_ci
3962306a36Sopenharmony_cistruct spin_wait {
4062306a36Sopenharmony_ci	struct spin_wait *next, *prev;
4162306a36Sopenharmony_ci	int node_id;
4262306a36Sopenharmony_ci} __aligned(32);
4362306a36Sopenharmony_ci
4462306a36Sopenharmony_cistatic DEFINE_PER_CPU_ALIGNED(struct spin_wait, spin_wait[4]);
4562306a36Sopenharmony_ci
4662306a36Sopenharmony_ci#define _Q_LOCK_CPU_OFFSET	0
4762306a36Sopenharmony_ci#define _Q_LOCK_STEAL_OFFSET	16
4862306a36Sopenharmony_ci#define _Q_TAIL_IDX_OFFSET	18
4962306a36Sopenharmony_ci#define _Q_TAIL_CPU_OFFSET	20
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci#define _Q_LOCK_CPU_MASK	0x0000ffff
5262306a36Sopenharmony_ci#define _Q_LOCK_STEAL_ADD	0x00010000
5362306a36Sopenharmony_ci#define _Q_LOCK_STEAL_MASK	0x00030000
5462306a36Sopenharmony_ci#define _Q_TAIL_IDX_MASK	0x000c0000
5562306a36Sopenharmony_ci#define _Q_TAIL_CPU_MASK	0xfff00000
5662306a36Sopenharmony_ci
5762306a36Sopenharmony_ci#define _Q_LOCK_MASK		(_Q_LOCK_CPU_MASK | _Q_LOCK_STEAL_MASK)
5862306a36Sopenharmony_ci#define _Q_TAIL_MASK		(_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK)
5962306a36Sopenharmony_ci
6062306a36Sopenharmony_civoid arch_spin_lock_setup(int cpu)
6162306a36Sopenharmony_ci{
6262306a36Sopenharmony_ci	struct spin_wait *node;
6362306a36Sopenharmony_ci	int ix;
6462306a36Sopenharmony_ci
6562306a36Sopenharmony_ci	node = per_cpu_ptr(&spin_wait[0], cpu);
6662306a36Sopenharmony_ci	for (ix = 0; ix < 4; ix++, node++) {
6762306a36Sopenharmony_ci		memset(node, 0, sizeof(*node));
6862306a36Sopenharmony_ci		node->node_id = ((cpu + 1) << _Q_TAIL_CPU_OFFSET) +
6962306a36Sopenharmony_ci			(ix << _Q_TAIL_IDX_OFFSET);
7062306a36Sopenharmony_ci	}
7162306a36Sopenharmony_ci}
7262306a36Sopenharmony_ci
7362306a36Sopenharmony_cistatic inline int arch_load_niai4(int *lock)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	int owner;
7662306a36Sopenharmony_ci
7762306a36Sopenharmony_ci	asm_inline volatile(
7862306a36Sopenharmony_ci		ALTERNATIVE("nop", ".insn rre,0xb2fa0000,4,0", 49) /* NIAI 4 */
7962306a36Sopenharmony_ci		"	l	%0,%1\n"
8062306a36Sopenharmony_ci		: "=d" (owner) : "Q" (*lock) : "memory");
8162306a36Sopenharmony_ci	return owner;
8262306a36Sopenharmony_ci}
8362306a36Sopenharmony_ci
8462306a36Sopenharmony_cistatic inline int arch_cmpxchg_niai8(int *lock, int old, int new)
8562306a36Sopenharmony_ci{
8662306a36Sopenharmony_ci	int expected = old;
8762306a36Sopenharmony_ci
8862306a36Sopenharmony_ci	asm_inline volatile(
8962306a36Sopenharmony_ci		ALTERNATIVE("nop", ".insn rre,0xb2fa0000,8,0", 49) /* NIAI 8 */
9062306a36Sopenharmony_ci		"	cs	%0,%3,%1\n"
9162306a36Sopenharmony_ci		: "=d" (old), "=Q" (*lock)
9262306a36Sopenharmony_ci		: "0" (old), "d" (new), "Q" (*lock)
9362306a36Sopenharmony_ci		: "cc", "memory");
9462306a36Sopenharmony_ci	return expected == old;
9562306a36Sopenharmony_ci}
9662306a36Sopenharmony_ci
9762306a36Sopenharmony_cistatic inline struct spin_wait *arch_spin_decode_tail(int lock)
9862306a36Sopenharmony_ci{
9962306a36Sopenharmony_ci	int ix, cpu;
10062306a36Sopenharmony_ci
10162306a36Sopenharmony_ci	ix = (lock & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET;
10262306a36Sopenharmony_ci	cpu = (lock & _Q_TAIL_CPU_MASK) >> _Q_TAIL_CPU_OFFSET;
10362306a36Sopenharmony_ci	return per_cpu_ptr(&spin_wait[ix], cpu - 1);
10462306a36Sopenharmony_ci}
10562306a36Sopenharmony_ci
10662306a36Sopenharmony_cistatic inline int arch_spin_yield_target(int lock, struct spin_wait *node)
10762306a36Sopenharmony_ci{
10862306a36Sopenharmony_ci	if (lock & _Q_LOCK_CPU_MASK)
10962306a36Sopenharmony_ci		return lock & _Q_LOCK_CPU_MASK;
11062306a36Sopenharmony_ci	if (node == NULL || node->prev == NULL)
11162306a36Sopenharmony_ci		return 0;	/* 0 -> no target cpu */
11262306a36Sopenharmony_ci	while (node->prev)
11362306a36Sopenharmony_ci		node = node->prev;
11462306a36Sopenharmony_ci	return node->node_id >> _Q_TAIL_CPU_OFFSET;
11562306a36Sopenharmony_ci}
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_cistatic inline void arch_spin_lock_queued(arch_spinlock_t *lp)
11862306a36Sopenharmony_ci{
11962306a36Sopenharmony_ci	struct spin_wait *node, *next;
12062306a36Sopenharmony_ci	int lockval, ix, node_id, tail_id, old, new, owner, count;
12162306a36Sopenharmony_ci
12262306a36Sopenharmony_ci	ix = S390_lowcore.spinlock_index++;
12362306a36Sopenharmony_ci	barrier();
12462306a36Sopenharmony_ci	lockval = SPINLOCK_LOCKVAL;	/* cpu + 1 */
12562306a36Sopenharmony_ci	node = this_cpu_ptr(&spin_wait[ix]);
12662306a36Sopenharmony_ci	node->prev = node->next = NULL;
12762306a36Sopenharmony_ci	node_id = node->node_id;
12862306a36Sopenharmony_ci
12962306a36Sopenharmony_ci	/* Enqueue the node for this CPU in the spinlock wait queue */
13062306a36Sopenharmony_ci	while (1) {
13162306a36Sopenharmony_ci		old = READ_ONCE(lp->lock);
13262306a36Sopenharmony_ci		if ((old & _Q_LOCK_CPU_MASK) == 0 &&
13362306a36Sopenharmony_ci		    (old & _Q_LOCK_STEAL_MASK) != _Q_LOCK_STEAL_MASK) {
13462306a36Sopenharmony_ci			/*
13562306a36Sopenharmony_ci			 * The lock is free but there may be waiters.
13662306a36Sopenharmony_ci			 * With no waiters simply take the lock, if there
13762306a36Sopenharmony_ci			 * are waiters try to steal the lock. The lock may
13862306a36Sopenharmony_ci			 * be stolen three times before the next queued
13962306a36Sopenharmony_ci			 * waiter will get the lock.
14062306a36Sopenharmony_ci			 */
14162306a36Sopenharmony_ci			new = (old ? (old + _Q_LOCK_STEAL_ADD) : 0) | lockval;
14262306a36Sopenharmony_ci			if (__atomic_cmpxchg_bool(&lp->lock, old, new))
14362306a36Sopenharmony_ci				/* Got the lock */
14462306a36Sopenharmony_ci				goto out;
14562306a36Sopenharmony_ci			/* lock passing in progress */
14662306a36Sopenharmony_ci			continue;
14762306a36Sopenharmony_ci		}
14862306a36Sopenharmony_ci		/* Make the node of this CPU the new tail. */
14962306a36Sopenharmony_ci		new = node_id | (old & _Q_LOCK_MASK);
15062306a36Sopenharmony_ci		if (__atomic_cmpxchg_bool(&lp->lock, old, new))
15162306a36Sopenharmony_ci			break;
15262306a36Sopenharmony_ci	}
15362306a36Sopenharmony_ci	/* Set the 'next' pointer of the tail node in the queue */
15462306a36Sopenharmony_ci	tail_id = old & _Q_TAIL_MASK;
15562306a36Sopenharmony_ci	if (tail_id != 0) {
15662306a36Sopenharmony_ci		node->prev = arch_spin_decode_tail(tail_id);
15762306a36Sopenharmony_ci		WRITE_ONCE(node->prev->next, node);
15862306a36Sopenharmony_ci	}
15962306a36Sopenharmony_ci
16062306a36Sopenharmony_ci	/* Pass the virtual CPU to the lock holder if it is not running */
16162306a36Sopenharmony_ci	owner = arch_spin_yield_target(old, node);
16262306a36Sopenharmony_ci	if (owner && arch_vcpu_is_preempted(owner - 1))
16362306a36Sopenharmony_ci		smp_yield_cpu(owner - 1);
16462306a36Sopenharmony_ci
16562306a36Sopenharmony_ci	/* Spin on the CPU local node->prev pointer */
16662306a36Sopenharmony_ci	if (tail_id != 0) {
16762306a36Sopenharmony_ci		count = spin_retry;
16862306a36Sopenharmony_ci		while (READ_ONCE(node->prev) != NULL) {
16962306a36Sopenharmony_ci			if (count-- >= 0)
17062306a36Sopenharmony_ci				continue;
17162306a36Sopenharmony_ci			count = spin_retry;
17262306a36Sopenharmony_ci			/* Query running state of lock holder again. */
17362306a36Sopenharmony_ci			owner = arch_spin_yield_target(old, node);
17462306a36Sopenharmony_ci			if (owner && arch_vcpu_is_preempted(owner - 1))
17562306a36Sopenharmony_ci				smp_yield_cpu(owner - 1);
17662306a36Sopenharmony_ci		}
17762306a36Sopenharmony_ci	}
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci	/* Spin on the lock value in the spinlock_t */
18062306a36Sopenharmony_ci	count = spin_retry;
18162306a36Sopenharmony_ci	while (1) {
18262306a36Sopenharmony_ci		old = READ_ONCE(lp->lock);
18362306a36Sopenharmony_ci		owner = old & _Q_LOCK_CPU_MASK;
18462306a36Sopenharmony_ci		if (!owner) {
18562306a36Sopenharmony_ci			tail_id = old & _Q_TAIL_MASK;
18662306a36Sopenharmony_ci			new = ((tail_id != node_id) ? tail_id : 0) | lockval;
18762306a36Sopenharmony_ci			if (__atomic_cmpxchg_bool(&lp->lock, old, new))
18862306a36Sopenharmony_ci				/* Got the lock */
18962306a36Sopenharmony_ci				break;
19062306a36Sopenharmony_ci			continue;
19162306a36Sopenharmony_ci		}
19262306a36Sopenharmony_ci		if (count-- >= 0)
19362306a36Sopenharmony_ci			continue;
19462306a36Sopenharmony_ci		count = spin_retry;
19562306a36Sopenharmony_ci		if (!MACHINE_IS_LPAR || arch_vcpu_is_preempted(owner - 1))
19662306a36Sopenharmony_ci			smp_yield_cpu(owner - 1);
19762306a36Sopenharmony_ci	}
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	/* Pass lock_spin job to next CPU in the queue */
20062306a36Sopenharmony_ci	if (node_id && tail_id != node_id) {
20162306a36Sopenharmony_ci		/* Wait until the next CPU has set up the 'next' pointer */
20262306a36Sopenharmony_ci		while ((next = READ_ONCE(node->next)) == NULL)
20362306a36Sopenharmony_ci			;
20462306a36Sopenharmony_ci		next->prev = NULL;
20562306a36Sopenharmony_ci	}
20662306a36Sopenharmony_ci
20762306a36Sopenharmony_ci out:
20862306a36Sopenharmony_ci	S390_lowcore.spinlock_index--;
20962306a36Sopenharmony_ci}
21062306a36Sopenharmony_ci
21162306a36Sopenharmony_cistatic inline void arch_spin_lock_classic(arch_spinlock_t *lp)
21262306a36Sopenharmony_ci{
21362306a36Sopenharmony_ci	int lockval, old, new, owner, count;
21462306a36Sopenharmony_ci
21562306a36Sopenharmony_ci	lockval = SPINLOCK_LOCKVAL;	/* cpu + 1 */
21662306a36Sopenharmony_ci
21762306a36Sopenharmony_ci	/* Pass the virtual CPU to the lock holder if it is not running */
21862306a36Sopenharmony_ci	owner = arch_spin_yield_target(READ_ONCE(lp->lock), NULL);
21962306a36Sopenharmony_ci	if (owner && arch_vcpu_is_preempted(owner - 1))
22062306a36Sopenharmony_ci		smp_yield_cpu(owner - 1);
22162306a36Sopenharmony_ci
22262306a36Sopenharmony_ci	count = spin_retry;
22362306a36Sopenharmony_ci	while (1) {
22462306a36Sopenharmony_ci		old = arch_load_niai4(&lp->lock);
22562306a36Sopenharmony_ci		owner = old & _Q_LOCK_CPU_MASK;
22662306a36Sopenharmony_ci		/* Try to get the lock if it is free. */
22762306a36Sopenharmony_ci		if (!owner) {
22862306a36Sopenharmony_ci			new = (old & _Q_TAIL_MASK) | lockval;
22962306a36Sopenharmony_ci			if (arch_cmpxchg_niai8(&lp->lock, old, new)) {
23062306a36Sopenharmony_ci				/* Got the lock */
23162306a36Sopenharmony_ci				return;
23262306a36Sopenharmony_ci			}
23362306a36Sopenharmony_ci			continue;
23462306a36Sopenharmony_ci		}
23562306a36Sopenharmony_ci		if (count-- >= 0)
23662306a36Sopenharmony_ci			continue;
23762306a36Sopenharmony_ci		count = spin_retry;
23862306a36Sopenharmony_ci		if (!MACHINE_IS_LPAR || arch_vcpu_is_preempted(owner - 1))
23962306a36Sopenharmony_ci			smp_yield_cpu(owner - 1);
24062306a36Sopenharmony_ci	}
24162306a36Sopenharmony_ci}
24262306a36Sopenharmony_ci
24362306a36Sopenharmony_civoid arch_spin_lock_wait(arch_spinlock_t *lp)
24462306a36Sopenharmony_ci{
24562306a36Sopenharmony_ci	if (test_cpu_flag(CIF_DEDICATED_CPU))
24662306a36Sopenharmony_ci		arch_spin_lock_queued(lp);
24762306a36Sopenharmony_ci	else
24862306a36Sopenharmony_ci		arch_spin_lock_classic(lp);
24962306a36Sopenharmony_ci}
25062306a36Sopenharmony_ciEXPORT_SYMBOL(arch_spin_lock_wait);
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ciint arch_spin_trylock_retry(arch_spinlock_t *lp)
25362306a36Sopenharmony_ci{
25462306a36Sopenharmony_ci	int cpu = SPINLOCK_LOCKVAL;
25562306a36Sopenharmony_ci	int owner, count;
25662306a36Sopenharmony_ci
25762306a36Sopenharmony_ci	for (count = spin_retry; count > 0; count--) {
25862306a36Sopenharmony_ci		owner = READ_ONCE(lp->lock);
25962306a36Sopenharmony_ci		/* Try to get the lock if it is free. */
26062306a36Sopenharmony_ci		if (!owner) {
26162306a36Sopenharmony_ci			if (__atomic_cmpxchg_bool(&lp->lock, 0, cpu))
26262306a36Sopenharmony_ci				return 1;
26362306a36Sopenharmony_ci		}
26462306a36Sopenharmony_ci	}
26562306a36Sopenharmony_ci	return 0;
26662306a36Sopenharmony_ci}
26762306a36Sopenharmony_ciEXPORT_SYMBOL(arch_spin_trylock_retry);
26862306a36Sopenharmony_ci
26962306a36Sopenharmony_civoid arch_read_lock_wait(arch_rwlock_t *rw)
27062306a36Sopenharmony_ci{
27162306a36Sopenharmony_ci	if (unlikely(in_interrupt())) {
27262306a36Sopenharmony_ci		while (READ_ONCE(rw->cnts) & 0x10000)
27362306a36Sopenharmony_ci			barrier();
27462306a36Sopenharmony_ci		return;
27562306a36Sopenharmony_ci	}
27662306a36Sopenharmony_ci
27762306a36Sopenharmony_ci	/* Remove this reader again to allow recursive read locking */
27862306a36Sopenharmony_ci	__atomic_add_const(-1, &rw->cnts);
27962306a36Sopenharmony_ci	/* Put the reader into the wait queue */
28062306a36Sopenharmony_ci	arch_spin_lock(&rw->wait);
28162306a36Sopenharmony_ci	/* Now add this reader to the count value again */
28262306a36Sopenharmony_ci	__atomic_add_const(1, &rw->cnts);
28362306a36Sopenharmony_ci	/* Loop until the writer is done */
28462306a36Sopenharmony_ci	while (READ_ONCE(rw->cnts) & 0x10000)
28562306a36Sopenharmony_ci		barrier();
28662306a36Sopenharmony_ci	arch_spin_unlock(&rw->wait);
28762306a36Sopenharmony_ci}
28862306a36Sopenharmony_ciEXPORT_SYMBOL(arch_read_lock_wait);
28962306a36Sopenharmony_ci
29062306a36Sopenharmony_civoid arch_write_lock_wait(arch_rwlock_t *rw)
29162306a36Sopenharmony_ci{
29262306a36Sopenharmony_ci	int old;
29362306a36Sopenharmony_ci
29462306a36Sopenharmony_ci	/* Add this CPU to the write waiters */
29562306a36Sopenharmony_ci	__atomic_add(0x20000, &rw->cnts);
29662306a36Sopenharmony_ci
29762306a36Sopenharmony_ci	/* Put the writer into the wait queue */
29862306a36Sopenharmony_ci	arch_spin_lock(&rw->wait);
29962306a36Sopenharmony_ci
30062306a36Sopenharmony_ci	while (1) {
30162306a36Sopenharmony_ci		old = READ_ONCE(rw->cnts);
30262306a36Sopenharmony_ci		if ((old & 0x1ffff) == 0 &&
30362306a36Sopenharmony_ci		    __atomic_cmpxchg_bool(&rw->cnts, old, old | 0x10000))
30462306a36Sopenharmony_ci			/* Got the lock */
30562306a36Sopenharmony_ci			break;
30662306a36Sopenharmony_ci		barrier();
30762306a36Sopenharmony_ci	}
30862306a36Sopenharmony_ci
30962306a36Sopenharmony_ci	arch_spin_unlock(&rw->wait);
31062306a36Sopenharmony_ci}
31162306a36Sopenharmony_ciEXPORT_SYMBOL(arch_write_lock_wait);
31262306a36Sopenharmony_ci
31362306a36Sopenharmony_civoid arch_spin_relax(arch_spinlock_t *lp)
31462306a36Sopenharmony_ci{
31562306a36Sopenharmony_ci	int cpu;
31662306a36Sopenharmony_ci
31762306a36Sopenharmony_ci	cpu = READ_ONCE(lp->lock) & _Q_LOCK_CPU_MASK;
31862306a36Sopenharmony_ci	if (!cpu)
31962306a36Sopenharmony_ci		return;
32062306a36Sopenharmony_ci	if (MACHINE_IS_LPAR && !arch_vcpu_is_preempted(cpu - 1))
32162306a36Sopenharmony_ci		return;
32262306a36Sopenharmony_ci	smp_yield_cpu(cpu - 1);
32362306a36Sopenharmony_ci}
32462306a36Sopenharmony_ciEXPORT_SYMBOL(arch_spin_relax);
325