18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Out of line spinlock code. 48c2ecf20Sopenharmony_ci * 58c2ecf20Sopenharmony_ci * Copyright IBM Corp. 2004, 2006 68c2ecf20Sopenharmony_ci * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com) 78c2ecf20Sopenharmony_ci */ 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#include <linux/types.h> 108c2ecf20Sopenharmony_ci#include <linux/export.h> 118c2ecf20Sopenharmony_ci#include <linux/spinlock.h> 128c2ecf20Sopenharmony_ci#include <linux/jiffies.h> 138c2ecf20Sopenharmony_ci#include <linux/init.h> 148c2ecf20Sopenharmony_ci#include <linux/smp.h> 158c2ecf20Sopenharmony_ci#include <linux/percpu.h> 168c2ecf20Sopenharmony_ci#include <asm/alternative.h> 178c2ecf20Sopenharmony_ci#include <asm/io.h> 188c2ecf20Sopenharmony_ci 198c2ecf20Sopenharmony_ciint spin_retry = -1; 208c2ecf20Sopenharmony_ci 218c2ecf20Sopenharmony_cistatic int __init spin_retry_init(void) 228c2ecf20Sopenharmony_ci{ 238c2ecf20Sopenharmony_ci if (spin_retry < 0) 248c2ecf20Sopenharmony_ci spin_retry = 1000; 258c2ecf20Sopenharmony_ci return 0; 268c2ecf20Sopenharmony_ci} 278c2ecf20Sopenharmony_ciearly_initcall(spin_retry_init); 288c2ecf20Sopenharmony_ci 298c2ecf20Sopenharmony_ci/** 308c2ecf20Sopenharmony_ci * spin_retry= parameter 318c2ecf20Sopenharmony_ci */ 328c2ecf20Sopenharmony_cistatic int __init spin_retry_setup(char *str) 338c2ecf20Sopenharmony_ci{ 348c2ecf20Sopenharmony_ci spin_retry = simple_strtoul(str, &str, 0); 358c2ecf20Sopenharmony_ci return 1; 368c2ecf20Sopenharmony_ci} 378c2ecf20Sopenharmony_ci__setup("spin_retry=", spin_retry_setup); 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_cistruct spin_wait { 408c2ecf20Sopenharmony_ci struct spin_wait *next, *prev; 418c2ecf20Sopenharmony_ci int node_id; 428c2ecf20Sopenharmony_ci} __aligned(32); 438c2ecf20Sopenharmony_ci 448c2ecf20Sopenharmony_cistatic DEFINE_PER_CPU_ALIGNED(struct spin_wait, spin_wait[4]); 458c2ecf20Sopenharmony_ci 468c2ecf20Sopenharmony_ci#define _Q_LOCK_CPU_OFFSET 0 478c2ecf20Sopenharmony_ci#define _Q_LOCK_STEAL_OFFSET 16 488c2ecf20Sopenharmony_ci#define _Q_TAIL_IDX_OFFSET 18 498c2ecf20Sopenharmony_ci#define _Q_TAIL_CPU_OFFSET 20 508c2ecf20Sopenharmony_ci 518c2ecf20Sopenharmony_ci#define _Q_LOCK_CPU_MASK 0x0000ffff 528c2ecf20Sopenharmony_ci#define _Q_LOCK_STEAL_ADD 0x00010000 538c2ecf20Sopenharmony_ci#define _Q_LOCK_STEAL_MASK 0x00030000 548c2ecf20Sopenharmony_ci#define _Q_TAIL_IDX_MASK 0x000c0000 558c2ecf20Sopenharmony_ci#define _Q_TAIL_CPU_MASK 0xfff00000 568c2ecf20Sopenharmony_ci 578c2ecf20Sopenharmony_ci#define _Q_LOCK_MASK (_Q_LOCK_CPU_MASK | _Q_LOCK_STEAL_MASK) 588c2ecf20Sopenharmony_ci#define _Q_TAIL_MASK (_Q_TAIL_IDX_MASK | _Q_TAIL_CPU_MASK) 598c2ecf20Sopenharmony_ci 608c2ecf20Sopenharmony_civoid arch_spin_lock_setup(int cpu) 618c2ecf20Sopenharmony_ci{ 628c2ecf20Sopenharmony_ci struct spin_wait *node; 638c2ecf20Sopenharmony_ci int ix; 648c2ecf20Sopenharmony_ci 658c2ecf20Sopenharmony_ci node = per_cpu_ptr(&spin_wait[0], cpu); 668c2ecf20Sopenharmony_ci for (ix = 0; ix < 4; ix++, node++) { 678c2ecf20Sopenharmony_ci memset(node, 0, sizeof(*node)); 688c2ecf20Sopenharmony_ci node->node_id = ((cpu + 1) << _Q_TAIL_CPU_OFFSET) + 698c2ecf20Sopenharmony_ci (ix << _Q_TAIL_IDX_OFFSET); 708c2ecf20Sopenharmony_ci } 718c2ecf20Sopenharmony_ci} 728c2ecf20Sopenharmony_ci 738c2ecf20Sopenharmony_cistatic inline int arch_load_niai4(int *lock) 748c2ecf20Sopenharmony_ci{ 758c2ecf20Sopenharmony_ci int owner; 768c2ecf20Sopenharmony_ci 778c2ecf20Sopenharmony_ci asm_inline volatile( 788c2ecf20Sopenharmony_ci ALTERNATIVE("", ".long 0xb2fa0040", 49) /* NIAI 4 */ 798c2ecf20Sopenharmony_ci " l %0,%1\n" 808c2ecf20Sopenharmony_ci : "=d" (owner) : "Q" (*lock) : "memory"); 818c2ecf20Sopenharmony_ci return owner; 828c2ecf20Sopenharmony_ci} 838c2ecf20Sopenharmony_ci 848c2ecf20Sopenharmony_cistatic inline int arch_cmpxchg_niai8(int *lock, int old, int new) 858c2ecf20Sopenharmony_ci{ 868c2ecf20Sopenharmony_ci int expected = old; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci asm_inline volatile( 898c2ecf20Sopenharmony_ci ALTERNATIVE("", ".long 0xb2fa0080", 49) /* NIAI 8 */ 908c2ecf20Sopenharmony_ci " cs %0,%3,%1\n" 918c2ecf20Sopenharmony_ci : "=d" (old), "=Q" (*lock) 928c2ecf20Sopenharmony_ci : "0" (old), "d" (new), "Q" (*lock) 938c2ecf20Sopenharmony_ci : "cc", "memory"); 948c2ecf20Sopenharmony_ci return expected == old; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_cistatic inline struct spin_wait *arch_spin_decode_tail(int lock) 988c2ecf20Sopenharmony_ci{ 998c2ecf20Sopenharmony_ci int ix, cpu; 1008c2ecf20Sopenharmony_ci 1018c2ecf20Sopenharmony_ci ix = (lock & _Q_TAIL_IDX_MASK) >> _Q_TAIL_IDX_OFFSET; 1028c2ecf20Sopenharmony_ci cpu = (lock & _Q_TAIL_CPU_MASK) >> _Q_TAIL_CPU_OFFSET; 1038c2ecf20Sopenharmony_ci return per_cpu_ptr(&spin_wait[ix], cpu - 1); 1048c2ecf20Sopenharmony_ci} 1058c2ecf20Sopenharmony_ci 1068c2ecf20Sopenharmony_cistatic inline int arch_spin_yield_target(int lock, struct spin_wait *node) 1078c2ecf20Sopenharmony_ci{ 1088c2ecf20Sopenharmony_ci if (lock & _Q_LOCK_CPU_MASK) 1098c2ecf20Sopenharmony_ci return lock & _Q_LOCK_CPU_MASK; 1108c2ecf20Sopenharmony_ci if (node == NULL || node->prev == NULL) 1118c2ecf20Sopenharmony_ci return 0; /* 0 -> no target cpu */ 1128c2ecf20Sopenharmony_ci while (node->prev) 1138c2ecf20Sopenharmony_ci node = node->prev; 1148c2ecf20Sopenharmony_ci return node->node_id >> _Q_TAIL_CPU_OFFSET; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_cistatic inline void arch_spin_lock_queued(arch_spinlock_t *lp) 1188c2ecf20Sopenharmony_ci{ 1198c2ecf20Sopenharmony_ci struct spin_wait *node, *next; 1208c2ecf20Sopenharmony_ci int lockval, ix, node_id, tail_id, old, new, owner, count; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci ix = S390_lowcore.spinlock_index++; 1238c2ecf20Sopenharmony_ci barrier(); 1248c2ecf20Sopenharmony_ci lockval = SPINLOCK_LOCKVAL; /* cpu + 1 */ 1258c2ecf20Sopenharmony_ci node = this_cpu_ptr(&spin_wait[ix]); 1268c2ecf20Sopenharmony_ci node->prev = node->next = NULL; 1278c2ecf20Sopenharmony_ci node_id = node->node_id; 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci /* Enqueue the node for this CPU in the spinlock wait queue */ 1308c2ecf20Sopenharmony_ci while (1) { 1318c2ecf20Sopenharmony_ci old = READ_ONCE(lp->lock); 1328c2ecf20Sopenharmony_ci if ((old & _Q_LOCK_CPU_MASK) == 0 && 1338c2ecf20Sopenharmony_ci (old & _Q_LOCK_STEAL_MASK) != _Q_LOCK_STEAL_MASK) { 1348c2ecf20Sopenharmony_ci /* 1358c2ecf20Sopenharmony_ci * The lock is free but there may be waiters. 1368c2ecf20Sopenharmony_ci * With no waiters simply take the lock, if there 1378c2ecf20Sopenharmony_ci * are waiters try to steal the lock. The lock may 1388c2ecf20Sopenharmony_ci * be stolen three times before the next queued 1398c2ecf20Sopenharmony_ci * waiter will get the lock. 1408c2ecf20Sopenharmony_ci */ 1418c2ecf20Sopenharmony_ci new = (old ? (old + _Q_LOCK_STEAL_ADD) : 0) | lockval; 1428c2ecf20Sopenharmony_ci if (__atomic_cmpxchg_bool(&lp->lock, old, new)) 1438c2ecf20Sopenharmony_ci /* Got the lock */ 1448c2ecf20Sopenharmony_ci goto out; 1458c2ecf20Sopenharmony_ci /* lock passing in progress */ 1468c2ecf20Sopenharmony_ci continue; 1478c2ecf20Sopenharmony_ci } 1488c2ecf20Sopenharmony_ci /* Make the node of this CPU the new tail. */ 1498c2ecf20Sopenharmony_ci new = node_id | (old & _Q_LOCK_MASK); 1508c2ecf20Sopenharmony_ci if (__atomic_cmpxchg_bool(&lp->lock, old, new)) 1518c2ecf20Sopenharmony_ci break; 1528c2ecf20Sopenharmony_ci } 1538c2ecf20Sopenharmony_ci /* Set the 'next' pointer of the tail node in the queue */ 1548c2ecf20Sopenharmony_ci tail_id = old & _Q_TAIL_MASK; 1558c2ecf20Sopenharmony_ci if (tail_id != 0) { 1568c2ecf20Sopenharmony_ci node->prev = arch_spin_decode_tail(tail_id); 1578c2ecf20Sopenharmony_ci WRITE_ONCE(node->prev->next, node); 1588c2ecf20Sopenharmony_ci } 1598c2ecf20Sopenharmony_ci 1608c2ecf20Sopenharmony_ci /* Pass the virtual CPU to the lock holder if it is not running */ 1618c2ecf20Sopenharmony_ci owner = arch_spin_yield_target(old, node); 1628c2ecf20Sopenharmony_ci if (owner && arch_vcpu_is_preempted(owner - 1)) 1638c2ecf20Sopenharmony_ci smp_yield_cpu(owner - 1); 1648c2ecf20Sopenharmony_ci 1658c2ecf20Sopenharmony_ci /* Spin on the CPU local node->prev pointer */ 1668c2ecf20Sopenharmony_ci if (tail_id != 0) { 1678c2ecf20Sopenharmony_ci count = spin_retry; 1688c2ecf20Sopenharmony_ci while (READ_ONCE(node->prev) != NULL) { 1698c2ecf20Sopenharmony_ci if (count-- >= 0) 1708c2ecf20Sopenharmony_ci continue; 1718c2ecf20Sopenharmony_ci count = spin_retry; 1728c2ecf20Sopenharmony_ci /* Query running state of lock holder again. */ 1738c2ecf20Sopenharmony_ci owner = arch_spin_yield_target(old, node); 1748c2ecf20Sopenharmony_ci if (owner && arch_vcpu_is_preempted(owner - 1)) 1758c2ecf20Sopenharmony_ci smp_yield_cpu(owner - 1); 1768c2ecf20Sopenharmony_ci } 1778c2ecf20Sopenharmony_ci } 1788c2ecf20Sopenharmony_ci 1798c2ecf20Sopenharmony_ci /* Spin on the lock value in the spinlock_t */ 1808c2ecf20Sopenharmony_ci count = spin_retry; 1818c2ecf20Sopenharmony_ci while (1) { 1828c2ecf20Sopenharmony_ci old = READ_ONCE(lp->lock); 1838c2ecf20Sopenharmony_ci owner = old & _Q_LOCK_CPU_MASK; 1848c2ecf20Sopenharmony_ci if (!owner) { 1858c2ecf20Sopenharmony_ci tail_id = old & _Q_TAIL_MASK; 1868c2ecf20Sopenharmony_ci new = ((tail_id != node_id) ? tail_id : 0) | lockval; 1878c2ecf20Sopenharmony_ci if (__atomic_cmpxchg_bool(&lp->lock, old, new)) 1888c2ecf20Sopenharmony_ci /* Got the lock */ 1898c2ecf20Sopenharmony_ci break; 1908c2ecf20Sopenharmony_ci continue; 1918c2ecf20Sopenharmony_ci } 1928c2ecf20Sopenharmony_ci if (count-- >= 0) 1938c2ecf20Sopenharmony_ci continue; 1948c2ecf20Sopenharmony_ci count = spin_retry; 1958c2ecf20Sopenharmony_ci if (!MACHINE_IS_LPAR || arch_vcpu_is_preempted(owner - 1)) 1968c2ecf20Sopenharmony_ci smp_yield_cpu(owner - 1); 1978c2ecf20Sopenharmony_ci } 1988c2ecf20Sopenharmony_ci 1998c2ecf20Sopenharmony_ci /* Pass lock_spin job to next CPU in the queue */ 2008c2ecf20Sopenharmony_ci if (node_id && tail_id != node_id) { 2018c2ecf20Sopenharmony_ci /* Wait until the next CPU has set up the 'next' pointer */ 2028c2ecf20Sopenharmony_ci while ((next = READ_ONCE(node->next)) == NULL) 2038c2ecf20Sopenharmony_ci ; 2048c2ecf20Sopenharmony_ci next->prev = NULL; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci out: 2088c2ecf20Sopenharmony_ci S390_lowcore.spinlock_index--; 2098c2ecf20Sopenharmony_ci} 2108c2ecf20Sopenharmony_ci 2118c2ecf20Sopenharmony_cistatic inline void arch_spin_lock_classic(arch_spinlock_t *lp) 2128c2ecf20Sopenharmony_ci{ 2138c2ecf20Sopenharmony_ci int lockval, old, new, owner, count; 2148c2ecf20Sopenharmony_ci 2158c2ecf20Sopenharmony_ci lockval = SPINLOCK_LOCKVAL; /* cpu + 1 */ 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_ci /* Pass the virtual CPU to the lock holder if it is not running */ 2188c2ecf20Sopenharmony_ci owner = arch_spin_yield_target(READ_ONCE(lp->lock), NULL); 2198c2ecf20Sopenharmony_ci if (owner && arch_vcpu_is_preempted(owner - 1)) 2208c2ecf20Sopenharmony_ci smp_yield_cpu(owner - 1); 2218c2ecf20Sopenharmony_ci 2228c2ecf20Sopenharmony_ci count = spin_retry; 2238c2ecf20Sopenharmony_ci while (1) { 2248c2ecf20Sopenharmony_ci old = arch_load_niai4(&lp->lock); 2258c2ecf20Sopenharmony_ci owner = old & _Q_LOCK_CPU_MASK; 2268c2ecf20Sopenharmony_ci /* Try to get the lock if it is free. */ 2278c2ecf20Sopenharmony_ci if (!owner) { 2288c2ecf20Sopenharmony_ci new = (old & _Q_TAIL_MASK) | lockval; 2298c2ecf20Sopenharmony_ci if (arch_cmpxchg_niai8(&lp->lock, old, new)) { 2308c2ecf20Sopenharmony_ci /* Got the lock */ 2318c2ecf20Sopenharmony_ci return; 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci continue; 2348c2ecf20Sopenharmony_ci } 2358c2ecf20Sopenharmony_ci if (count-- >= 0) 2368c2ecf20Sopenharmony_ci continue; 2378c2ecf20Sopenharmony_ci count = spin_retry; 2388c2ecf20Sopenharmony_ci if (!MACHINE_IS_LPAR || arch_vcpu_is_preempted(owner - 1)) 2398c2ecf20Sopenharmony_ci smp_yield_cpu(owner - 1); 2408c2ecf20Sopenharmony_ci } 2418c2ecf20Sopenharmony_ci} 2428c2ecf20Sopenharmony_ci 2438c2ecf20Sopenharmony_civoid arch_spin_lock_wait(arch_spinlock_t *lp) 2448c2ecf20Sopenharmony_ci{ 2458c2ecf20Sopenharmony_ci if (test_cpu_flag(CIF_DEDICATED_CPU)) 2468c2ecf20Sopenharmony_ci arch_spin_lock_queued(lp); 2478c2ecf20Sopenharmony_ci else 2488c2ecf20Sopenharmony_ci arch_spin_lock_classic(lp); 2498c2ecf20Sopenharmony_ci} 2508c2ecf20Sopenharmony_ciEXPORT_SYMBOL(arch_spin_lock_wait); 2518c2ecf20Sopenharmony_ci 2528c2ecf20Sopenharmony_ciint arch_spin_trylock_retry(arch_spinlock_t *lp) 2538c2ecf20Sopenharmony_ci{ 2548c2ecf20Sopenharmony_ci int cpu = SPINLOCK_LOCKVAL; 2558c2ecf20Sopenharmony_ci int owner, count; 2568c2ecf20Sopenharmony_ci 2578c2ecf20Sopenharmony_ci for (count = spin_retry; count > 0; count--) { 2588c2ecf20Sopenharmony_ci owner = READ_ONCE(lp->lock); 2598c2ecf20Sopenharmony_ci /* Try to get the lock if it is free. */ 2608c2ecf20Sopenharmony_ci if (!owner) { 2618c2ecf20Sopenharmony_ci if (__atomic_cmpxchg_bool(&lp->lock, 0, cpu)) 2628c2ecf20Sopenharmony_ci return 1; 2638c2ecf20Sopenharmony_ci } 2648c2ecf20Sopenharmony_ci } 2658c2ecf20Sopenharmony_ci return 0; 2668c2ecf20Sopenharmony_ci} 2678c2ecf20Sopenharmony_ciEXPORT_SYMBOL(arch_spin_trylock_retry); 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_civoid arch_read_lock_wait(arch_rwlock_t *rw) 2708c2ecf20Sopenharmony_ci{ 2718c2ecf20Sopenharmony_ci if (unlikely(in_interrupt())) { 2728c2ecf20Sopenharmony_ci while (READ_ONCE(rw->cnts) & 0x10000) 2738c2ecf20Sopenharmony_ci barrier(); 2748c2ecf20Sopenharmony_ci return; 2758c2ecf20Sopenharmony_ci } 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci /* Remove this reader again to allow recursive read locking */ 2788c2ecf20Sopenharmony_ci __atomic_add_const(-1, &rw->cnts); 2798c2ecf20Sopenharmony_ci /* Put the reader into the wait queue */ 2808c2ecf20Sopenharmony_ci arch_spin_lock(&rw->wait); 2818c2ecf20Sopenharmony_ci /* Now add this reader to the count value again */ 2828c2ecf20Sopenharmony_ci __atomic_add_const(1, &rw->cnts); 2838c2ecf20Sopenharmony_ci /* Loop until the writer is done */ 2848c2ecf20Sopenharmony_ci while (READ_ONCE(rw->cnts) & 0x10000) 2858c2ecf20Sopenharmony_ci barrier(); 2868c2ecf20Sopenharmony_ci arch_spin_unlock(&rw->wait); 2878c2ecf20Sopenharmony_ci} 2888c2ecf20Sopenharmony_ciEXPORT_SYMBOL(arch_read_lock_wait); 2898c2ecf20Sopenharmony_ci 2908c2ecf20Sopenharmony_civoid arch_write_lock_wait(arch_rwlock_t *rw) 2918c2ecf20Sopenharmony_ci{ 2928c2ecf20Sopenharmony_ci int old; 2938c2ecf20Sopenharmony_ci 2948c2ecf20Sopenharmony_ci /* Add this CPU to the write waiters */ 2958c2ecf20Sopenharmony_ci __atomic_add(0x20000, &rw->cnts); 2968c2ecf20Sopenharmony_ci 2978c2ecf20Sopenharmony_ci /* Put the writer into the wait queue */ 2988c2ecf20Sopenharmony_ci arch_spin_lock(&rw->wait); 2998c2ecf20Sopenharmony_ci 3008c2ecf20Sopenharmony_ci while (1) { 3018c2ecf20Sopenharmony_ci old = READ_ONCE(rw->cnts); 3028c2ecf20Sopenharmony_ci if ((old & 0x1ffff) == 0 && 3038c2ecf20Sopenharmony_ci __atomic_cmpxchg_bool(&rw->cnts, old, old | 0x10000)) 3048c2ecf20Sopenharmony_ci /* Got the lock */ 3058c2ecf20Sopenharmony_ci break; 3068c2ecf20Sopenharmony_ci barrier(); 3078c2ecf20Sopenharmony_ci } 3088c2ecf20Sopenharmony_ci 3098c2ecf20Sopenharmony_ci arch_spin_unlock(&rw->wait); 3108c2ecf20Sopenharmony_ci} 3118c2ecf20Sopenharmony_ciEXPORT_SYMBOL(arch_write_lock_wait); 3128c2ecf20Sopenharmony_ci 3138c2ecf20Sopenharmony_civoid arch_spin_relax(arch_spinlock_t *lp) 3148c2ecf20Sopenharmony_ci{ 3158c2ecf20Sopenharmony_ci int cpu; 3168c2ecf20Sopenharmony_ci 3178c2ecf20Sopenharmony_ci cpu = READ_ONCE(lp->lock) & _Q_LOCK_CPU_MASK; 3188c2ecf20Sopenharmony_ci if (!cpu) 3198c2ecf20Sopenharmony_ci return; 3208c2ecf20Sopenharmony_ci if (MACHINE_IS_LPAR && !arch_vcpu_is_preempted(cpu - 1)) 3218c2ecf20Sopenharmony_ci return; 3228c2ecf20Sopenharmony_ci smp_yield_cpu(cpu - 1); 3238c2ecf20Sopenharmony_ci} 3248c2ecf20Sopenharmony_ciEXPORT_SYMBOL(arch_spin_relax); 325