18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
28c2ecf20Sopenharmony_ci/*
38c2ecf20Sopenharmony_ci * Generic implementation of 64-bit atomics using spinlocks,
48c2ecf20Sopenharmony_ci * useful on processors that don't have 64-bit atomic instructions.
58c2ecf20Sopenharmony_ci *
68c2ecf20Sopenharmony_ci * Copyright © 2009 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
78c2ecf20Sopenharmony_ci */
88c2ecf20Sopenharmony_ci#include <linux/types.h>
98c2ecf20Sopenharmony_ci#include <linux/cache.h>
108c2ecf20Sopenharmony_ci#include <linux/spinlock.h>
118c2ecf20Sopenharmony_ci#include <linux/init.h>
128c2ecf20Sopenharmony_ci#include <linux/export.h>
138c2ecf20Sopenharmony_ci#include <linux/atomic.h>
148c2ecf20Sopenharmony_ci
158c2ecf20Sopenharmony_ci/*
168c2ecf20Sopenharmony_ci * We use a hashed array of spinlocks to provide exclusive access
178c2ecf20Sopenharmony_ci * to each atomic64_t variable.  Since this is expected to used on
188c2ecf20Sopenharmony_ci * systems with small numbers of CPUs (<= 4 or so), we use a
198c2ecf20Sopenharmony_ci * relatively small array of 16 spinlocks to avoid wasting too much
208c2ecf20Sopenharmony_ci * memory on the spinlock array.
218c2ecf20Sopenharmony_ci */
228c2ecf20Sopenharmony_ci#define NR_LOCKS	16
238c2ecf20Sopenharmony_ci
248c2ecf20Sopenharmony_ci/*
258c2ecf20Sopenharmony_ci * Ensure each lock is in a separate cacheline.
268c2ecf20Sopenharmony_ci */
278c2ecf20Sopenharmony_cistatic union {
288c2ecf20Sopenharmony_ci	raw_spinlock_t lock;
298c2ecf20Sopenharmony_ci	char pad[L1_CACHE_BYTES];
308c2ecf20Sopenharmony_ci} atomic64_lock[NR_LOCKS] __cacheline_aligned_in_smp = {
318c2ecf20Sopenharmony_ci	[0 ... (NR_LOCKS - 1)] = {
328c2ecf20Sopenharmony_ci		.lock =  __RAW_SPIN_LOCK_UNLOCKED(atomic64_lock.lock),
338c2ecf20Sopenharmony_ci	},
348c2ecf20Sopenharmony_ci};
358c2ecf20Sopenharmony_ci
368c2ecf20Sopenharmony_cistatic inline raw_spinlock_t *lock_addr(const atomic64_t *v)
378c2ecf20Sopenharmony_ci{
388c2ecf20Sopenharmony_ci	unsigned long addr = (unsigned long) v;
398c2ecf20Sopenharmony_ci
408c2ecf20Sopenharmony_ci	addr >>= L1_CACHE_SHIFT;
418c2ecf20Sopenharmony_ci	addr ^= (addr >> 8) ^ (addr >> 16);
428c2ecf20Sopenharmony_ci	return &atomic64_lock[addr & (NR_LOCKS - 1)].lock;
438c2ecf20Sopenharmony_ci}
448c2ecf20Sopenharmony_ci
458c2ecf20Sopenharmony_cis64 atomic64_read(const atomic64_t *v)
468c2ecf20Sopenharmony_ci{
478c2ecf20Sopenharmony_ci	unsigned long flags;
488c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
498c2ecf20Sopenharmony_ci	s64 val;
508c2ecf20Sopenharmony_ci
518c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
528c2ecf20Sopenharmony_ci	val = v->counter;
538c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
548c2ecf20Sopenharmony_ci	return val;
558c2ecf20Sopenharmony_ci}
568c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_read);
578c2ecf20Sopenharmony_ci
588c2ecf20Sopenharmony_civoid atomic64_set(atomic64_t *v, s64 i)
598c2ecf20Sopenharmony_ci{
608c2ecf20Sopenharmony_ci	unsigned long flags;
618c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
628c2ecf20Sopenharmony_ci
638c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
648c2ecf20Sopenharmony_ci	v->counter = i;
658c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
668c2ecf20Sopenharmony_ci}
678c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_set);
688c2ecf20Sopenharmony_ci
698c2ecf20Sopenharmony_ci#define ATOMIC64_OP(op, c_op)						\
708c2ecf20Sopenharmony_civoid atomic64_##op(s64 a, atomic64_t *v)				\
718c2ecf20Sopenharmony_ci{									\
728c2ecf20Sopenharmony_ci	unsigned long flags;						\
738c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);				\
748c2ecf20Sopenharmony_ci									\
758c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);				\
768c2ecf20Sopenharmony_ci	v->counter c_op a;						\
778c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);			\
788c2ecf20Sopenharmony_ci}									\
798c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_##op);
808c2ecf20Sopenharmony_ci
818c2ecf20Sopenharmony_ci#define ATOMIC64_OP_RETURN(op, c_op)					\
828c2ecf20Sopenharmony_cis64 atomic64_##op##_return(s64 a, atomic64_t *v)			\
838c2ecf20Sopenharmony_ci{									\
848c2ecf20Sopenharmony_ci	unsigned long flags;						\
858c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);				\
868c2ecf20Sopenharmony_ci	s64 val;							\
878c2ecf20Sopenharmony_ci									\
888c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);				\
898c2ecf20Sopenharmony_ci	val = (v->counter c_op a);					\
908c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);			\
918c2ecf20Sopenharmony_ci	return val;							\
928c2ecf20Sopenharmony_ci}									\
938c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_##op##_return);
948c2ecf20Sopenharmony_ci
958c2ecf20Sopenharmony_ci#define ATOMIC64_FETCH_OP(op, c_op)					\
968c2ecf20Sopenharmony_cis64 atomic64_fetch_##op(s64 a, atomic64_t *v)				\
978c2ecf20Sopenharmony_ci{									\
988c2ecf20Sopenharmony_ci	unsigned long flags;						\
998c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);				\
1008c2ecf20Sopenharmony_ci	s64 val;							\
1018c2ecf20Sopenharmony_ci									\
1028c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);				\
1038c2ecf20Sopenharmony_ci	val = v->counter;						\
1048c2ecf20Sopenharmony_ci	v->counter c_op a;						\
1058c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);			\
1068c2ecf20Sopenharmony_ci	return val;							\
1078c2ecf20Sopenharmony_ci}									\
1088c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_fetch_##op);
1098c2ecf20Sopenharmony_ci
1108c2ecf20Sopenharmony_ci#define ATOMIC64_OPS(op, c_op)						\
1118c2ecf20Sopenharmony_ci	ATOMIC64_OP(op, c_op)						\
1128c2ecf20Sopenharmony_ci	ATOMIC64_OP_RETURN(op, c_op)					\
1138c2ecf20Sopenharmony_ci	ATOMIC64_FETCH_OP(op, c_op)
1148c2ecf20Sopenharmony_ci
1158c2ecf20Sopenharmony_ciATOMIC64_OPS(add, +=)
1168c2ecf20Sopenharmony_ciATOMIC64_OPS(sub, -=)
1178c2ecf20Sopenharmony_ci
1188c2ecf20Sopenharmony_ci#undef ATOMIC64_OPS
1198c2ecf20Sopenharmony_ci#define ATOMIC64_OPS(op, c_op)						\
1208c2ecf20Sopenharmony_ci	ATOMIC64_OP(op, c_op)						\
1218c2ecf20Sopenharmony_ci	ATOMIC64_OP_RETURN(op, c_op)					\
1228c2ecf20Sopenharmony_ci	ATOMIC64_FETCH_OP(op, c_op)
1238c2ecf20Sopenharmony_ci
1248c2ecf20Sopenharmony_ciATOMIC64_OPS(and, &=)
1258c2ecf20Sopenharmony_ciATOMIC64_OPS(or, |=)
1268c2ecf20Sopenharmony_ciATOMIC64_OPS(xor, ^=)
1278c2ecf20Sopenharmony_ci
1288c2ecf20Sopenharmony_ci#undef ATOMIC64_OPS
1298c2ecf20Sopenharmony_ci#undef ATOMIC64_FETCH_OP
1308c2ecf20Sopenharmony_ci#undef ATOMIC64_OP_RETURN
1318c2ecf20Sopenharmony_ci#undef ATOMIC64_OP
1328c2ecf20Sopenharmony_ci
1338c2ecf20Sopenharmony_cis64 atomic64_dec_if_positive(atomic64_t *v)
1348c2ecf20Sopenharmony_ci{
1358c2ecf20Sopenharmony_ci	unsigned long flags;
1368c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
1378c2ecf20Sopenharmony_ci	s64 val;
1388c2ecf20Sopenharmony_ci
1398c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
1408c2ecf20Sopenharmony_ci	val = v->counter - 1;
1418c2ecf20Sopenharmony_ci	if (val >= 0)
1428c2ecf20Sopenharmony_ci		v->counter = val;
1438c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
1448c2ecf20Sopenharmony_ci	return val;
1458c2ecf20Sopenharmony_ci}
1468c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_dec_if_positive);
1478c2ecf20Sopenharmony_ci
1488c2ecf20Sopenharmony_cis64 atomic64_cmpxchg(atomic64_t *v, s64 o, s64 n)
1498c2ecf20Sopenharmony_ci{
1508c2ecf20Sopenharmony_ci	unsigned long flags;
1518c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
1528c2ecf20Sopenharmony_ci	s64 val;
1538c2ecf20Sopenharmony_ci
1548c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
1558c2ecf20Sopenharmony_ci	val = v->counter;
1568c2ecf20Sopenharmony_ci	if (val == o)
1578c2ecf20Sopenharmony_ci		v->counter = n;
1588c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
1598c2ecf20Sopenharmony_ci	return val;
1608c2ecf20Sopenharmony_ci}
1618c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_cmpxchg);
1628c2ecf20Sopenharmony_ci
1638c2ecf20Sopenharmony_cis64 atomic64_xchg(atomic64_t *v, s64 new)
1648c2ecf20Sopenharmony_ci{
1658c2ecf20Sopenharmony_ci	unsigned long flags;
1668c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
1678c2ecf20Sopenharmony_ci	s64 val;
1688c2ecf20Sopenharmony_ci
1698c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
1708c2ecf20Sopenharmony_ci	val = v->counter;
1718c2ecf20Sopenharmony_ci	v->counter = new;
1728c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
1738c2ecf20Sopenharmony_ci	return val;
1748c2ecf20Sopenharmony_ci}
1758c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_xchg);
1768c2ecf20Sopenharmony_ci
1778c2ecf20Sopenharmony_cis64 atomic64_fetch_add_unless(atomic64_t *v, s64 a, s64 u)
1788c2ecf20Sopenharmony_ci{
1798c2ecf20Sopenharmony_ci	unsigned long flags;
1808c2ecf20Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
1818c2ecf20Sopenharmony_ci	s64 val;
1828c2ecf20Sopenharmony_ci
1838c2ecf20Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
1848c2ecf20Sopenharmony_ci	val = v->counter;
1858c2ecf20Sopenharmony_ci	if (val != u)
1868c2ecf20Sopenharmony_ci		v->counter += a;
1878c2ecf20Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
1888c2ecf20Sopenharmony_ci
1898c2ecf20Sopenharmony_ci	return val;
1908c2ecf20Sopenharmony_ci}
1918c2ecf20Sopenharmony_ciEXPORT_SYMBOL(atomic64_fetch_add_unless);
192