162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Generic implementation of 64-bit atomics using spinlocks,
462306a36Sopenharmony_ci * useful on processors that don't have 64-bit atomic instructions.
562306a36Sopenharmony_ci *
662306a36Sopenharmony_ci * Copyright © 2009 Paul Mackerras, IBM Corp. <paulus@au1.ibm.com>
762306a36Sopenharmony_ci */
862306a36Sopenharmony_ci#include <linux/types.h>
962306a36Sopenharmony_ci#include <linux/cache.h>
1062306a36Sopenharmony_ci#include <linux/spinlock.h>
1162306a36Sopenharmony_ci#include <linux/init.h>
1262306a36Sopenharmony_ci#include <linux/export.h>
1362306a36Sopenharmony_ci#include <linux/atomic.h>
1462306a36Sopenharmony_ci
1562306a36Sopenharmony_ci/*
1662306a36Sopenharmony_ci * We use a hashed array of spinlocks to provide exclusive access
1762306a36Sopenharmony_ci * to each atomic64_t variable.  Since this is expected to used on
1862306a36Sopenharmony_ci * systems with small numbers of CPUs (<= 4 or so), we use a
1962306a36Sopenharmony_ci * relatively small array of 16 spinlocks to avoid wasting too much
2062306a36Sopenharmony_ci * memory on the spinlock array.
2162306a36Sopenharmony_ci */
2262306a36Sopenharmony_ci#define NR_LOCKS	16
2362306a36Sopenharmony_ci
2462306a36Sopenharmony_ci/*
2562306a36Sopenharmony_ci * Ensure each lock is in a separate cacheline.
2662306a36Sopenharmony_ci */
2762306a36Sopenharmony_cistatic union {
2862306a36Sopenharmony_ci	raw_spinlock_t lock;
2962306a36Sopenharmony_ci	char pad[L1_CACHE_BYTES];
3062306a36Sopenharmony_ci} atomic64_lock[NR_LOCKS] __cacheline_aligned_in_smp = {
3162306a36Sopenharmony_ci	[0 ... (NR_LOCKS - 1)] = {
3262306a36Sopenharmony_ci		.lock =  __RAW_SPIN_LOCK_UNLOCKED(atomic64_lock.lock),
3362306a36Sopenharmony_ci	},
3462306a36Sopenharmony_ci};
3562306a36Sopenharmony_ci
3662306a36Sopenharmony_cistatic inline raw_spinlock_t *lock_addr(const atomic64_t *v)
3762306a36Sopenharmony_ci{
3862306a36Sopenharmony_ci	unsigned long addr = (unsigned long) v;
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	addr >>= L1_CACHE_SHIFT;
4162306a36Sopenharmony_ci	addr ^= (addr >> 8) ^ (addr >> 16);
4262306a36Sopenharmony_ci	return &atomic64_lock[addr & (NR_LOCKS - 1)].lock;
4362306a36Sopenharmony_ci}
4462306a36Sopenharmony_ci
4562306a36Sopenharmony_cis64 generic_atomic64_read(const atomic64_t *v)
4662306a36Sopenharmony_ci{
4762306a36Sopenharmony_ci	unsigned long flags;
4862306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
4962306a36Sopenharmony_ci	s64 val;
5062306a36Sopenharmony_ci
5162306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
5262306a36Sopenharmony_ci	val = v->counter;
5362306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
5462306a36Sopenharmony_ci	return val;
5562306a36Sopenharmony_ci}
5662306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_read);
5762306a36Sopenharmony_ci
5862306a36Sopenharmony_civoid generic_atomic64_set(atomic64_t *v, s64 i)
5962306a36Sopenharmony_ci{
6062306a36Sopenharmony_ci	unsigned long flags;
6162306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
6262306a36Sopenharmony_ci
6362306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
6462306a36Sopenharmony_ci	v->counter = i;
6562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
6662306a36Sopenharmony_ci}
6762306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_set);
6862306a36Sopenharmony_ci
6962306a36Sopenharmony_ci#define ATOMIC64_OP(op, c_op)						\
7062306a36Sopenharmony_civoid generic_atomic64_##op(s64 a, atomic64_t *v)			\
7162306a36Sopenharmony_ci{									\
7262306a36Sopenharmony_ci	unsigned long flags;						\
7362306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);				\
7462306a36Sopenharmony_ci									\
7562306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);				\
7662306a36Sopenharmony_ci	v->counter c_op a;						\
7762306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);			\
7862306a36Sopenharmony_ci}									\
7962306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_##op);
8062306a36Sopenharmony_ci
8162306a36Sopenharmony_ci#define ATOMIC64_OP_RETURN(op, c_op)					\
8262306a36Sopenharmony_cis64 generic_atomic64_##op##_return(s64 a, atomic64_t *v)		\
8362306a36Sopenharmony_ci{									\
8462306a36Sopenharmony_ci	unsigned long flags;						\
8562306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);				\
8662306a36Sopenharmony_ci	s64 val;							\
8762306a36Sopenharmony_ci									\
8862306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);				\
8962306a36Sopenharmony_ci	val = (v->counter c_op a);					\
9062306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);			\
9162306a36Sopenharmony_ci	return val;							\
9262306a36Sopenharmony_ci}									\
9362306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_##op##_return);
9462306a36Sopenharmony_ci
9562306a36Sopenharmony_ci#define ATOMIC64_FETCH_OP(op, c_op)					\
9662306a36Sopenharmony_cis64 generic_atomic64_fetch_##op(s64 a, atomic64_t *v)			\
9762306a36Sopenharmony_ci{									\
9862306a36Sopenharmony_ci	unsigned long flags;						\
9962306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);				\
10062306a36Sopenharmony_ci	s64 val;							\
10162306a36Sopenharmony_ci									\
10262306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);				\
10362306a36Sopenharmony_ci	val = v->counter;						\
10462306a36Sopenharmony_ci	v->counter c_op a;						\
10562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);			\
10662306a36Sopenharmony_ci	return val;							\
10762306a36Sopenharmony_ci}									\
10862306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_fetch_##op);
10962306a36Sopenharmony_ci
11062306a36Sopenharmony_ci#define ATOMIC64_OPS(op, c_op)						\
11162306a36Sopenharmony_ci	ATOMIC64_OP(op, c_op)						\
11262306a36Sopenharmony_ci	ATOMIC64_OP_RETURN(op, c_op)					\
11362306a36Sopenharmony_ci	ATOMIC64_FETCH_OP(op, c_op)
11462306a36Sopenharmony_ci
11562306a36Sopenharmony_ciATOMIC64_OPS(add, +=)
11662306a36Sopenharmony_ciATOMIC64_OPS(sub, -=)
11762306a36Sopenharmony_ci
11862306a36Sopenharmony_ci#undef ATOMIC64_OPS
11962306a36Sopenharmony_ci#define ATOMIC64_OPS(op, c_op)						\
12062306a36Sopenharmony_ci	ATOMIC64_OP(op, c_op)						\
12162306a36Sopenharmony_ci	ATOMIC64_FETCH_OP(op, c_op)
12262306a36Sopenharmony_ci
12362306a36Sopenharmony_ciATOMIC64_OPS(and, &=)
12462306a36Sopenharmony_ciATOMIC64_OPS(or, |=)
12562306a36Sopenharmony_ciATOMIC64_OPS(xor, ^=)
12662306a36Sopenharmony_ci
12762306a36Sopenharmony_ci#undef ATOMIC64_OPS
12862306a36Sopenharmony_ci#undef ATOMIC64_FETCH_OP
12962306a36Sopenharmony_ci#undef ATOMIC64_OP
13062306a36Sopenharmony_ci
13162306a36Sopenharmony_cis64 generic_atomic64_dec_if_positive(atomic64_t *v)
13262306a36Sopenharmony_ci{
13362306a36Sopenharmony_ci	unsigned long flags;
13462306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
13562306a36Sopenharmony_ci	s64 val;
13662306a36Sopenharmony_ci
13762306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
13862306a36Sopenharmony_ci	val = v->counter - 1;
13962306a36Sopenharmony_ci	if (val >= 0)
14062306a36Sopenharmony_ci		v->counter = val;
14162306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
14262306a36Sopenharmony_ci	return val;
14362306a36Sopenharmony_ci}
14462306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_dec_if_positive);
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_cis64 generic_atomic64_cmpxchg(atomic64_t *v, s64 o, s64 n)
14762306a36Sopenharmony_ci{
14862306a36Sopenharmony_ci	unsigned long flags;
14962306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
15062306a36Sopenharmony_ci	s64 val;
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
15362306a36Sopenharmony_ci	val = v->counter;
15462306a36Sopenharmony_ci	if (val == o)
15562306a36Sopenharmony_ci		v->counter = n;
15662306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
15762306a36Sopenharmony_ci	return val;
15862306a36Sopenharmony_ci}
15962306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_cmpxchg);
16062306a36Sopenharmony_ci
16162306a36Sopenharmony_cis64 generic_atomic64_xchg(atomic64_t *v, s64 new)
16262306a36Sopenharmony_ci{
16362306a36Sopenharmony_ci	unsigned long flags;
16462306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
16562306a36Sopenharmony_ci	s64 val;
16662306a36Sopenharmony_ci
16762306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
16862306a36Sopenharmony_ci	val = v->counter;
16962306a36Sopenharmony_ci	v->counter = new;
17062306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
17162306a36Sopenharmony_ci	return val;
17262306a36Sopenharmony_ci}
17362306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_xchg);
17462306a36Sopenharmony_ci
17562306a36Sopenharmony_cis64 generic_atomic64_fetch_add_unless(atomic64_t *v, s64 a, s64 u)
17662306a36Sopenharmony_ci{
17762306a36Sopenharmony_ci	unsigned long flags;
17862306a36Sopenharmony_ci	raw_spinlock_t *lock = lock_addr(v);
17962306a36Sopenharmony_ci	s64 val;
18062306a36Sopenharmony_ci
18162306a36Sopenharmony_ci	raw_spin_lock_irqsave(lock, flags);
18262306a36Sopenharmony_ci	val = v->counter;
18362306a36Sopenharmony_ci	if (val != u)
18462306a36Sopenharmony_ci		v->counter += a;
18562306a36Sopenharmony_ci	raw_spin_unlock_irqrestore(lock, flags);
18662306a36Sopenharmony_ci
18762306a36Sopenharmony_ci	return val;
18862306a36Sopenharmony_ci}
18962306a36Sopenharmony_ciEXPORT_SYMBOL(generic_atomic64_fetch_add_unless);
190