Some architectures do not or not always support cmpxchg64(). This results in on/off problems when the function is used in common code. The latest example is net/sunrpc/auth_gss/gss_krb5_seal.c: In function 'gss_seq_send64_fetch_and_inc': net/sunrpc/auth_gss/gss_krb5_seal.c:145:14: error: implicit declaration of function 'cmpxchg64' which is seen with some powerpc and mips builds. Introduce a generic version of __cmpxchg_u64() and use it for affected architectures. Fixes: 21924765862a ("SUNRPC: use cmpxchg64() in gss_seq_send64_fetch_and_inc()") Cc: Arnd Bergmann <arnd@xxxxxxxx> Cc: Trond Myklebust <trond.myklebust@xxxxxxxxxxxxxxx> Signed-off-by: Guenter Roeck <linux@xxxxxxxxxxxx> --- Couple of questions: - Is this the right (or an acceptable) approach to fix the problem ? - Should I split the patch into three, one to introduce __cmpxchg_u64() and one per architecture ? - Who should take the patch (series) ? arch/mips/Kconfig | 1 + arch/mips/include/asm/cmpxchg.h | 3 ++ arch/powerpc/Kconfig | 1 + arch/powerpc/include/asm/cmpxchg.h | 3 ++ lib/Kconfig | 3 ++ lib/Makefile | 2 ++ lib/cmpxchg64.c | 60 ++++++++++++++++++++++++++++++++++++++ 7 files changed, 73 insertions(+) create mode 100644 lib/cmpxchg64.c diff --git a/arch/mips/Kconfig b/arch/mips/Kconfig index 80778b40f8fa..7392a5f4e517 100644 --- a/arch/mips/Kconfig +++ b/arch/mips/Kconfig @@ -18,6 +18,7 @@ config MIPS select CPU_PM if CPU_IDLE select DMA_DIRECT_OPS select GENERIC_ATOMIC64 if !64BIT + select GENERIC_CMPXCHG64 if !64BIT && SMP select GENERIC_CLOCKEVENTS select GENERIC_CMOS_UPDATE select GENERIC_CPU_AUTOPROBE diff --git a/arch/mips/include/asm/cmpxchg.h b/arch/mips/include/asm/cmpxchg.h index 89e9fb7976fe..ca837b05bf3d 100644 --- a/arch/mips/include/asm/cmpxchg.h +++ b/arch/mips/include/asm/cmpxchg.h @@ -206,6 +206,9 @@ static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old, #define cmpxchg64_local(ptr, o, n) __cmpxchg64_local_generic((ptr), (o), (n)) #ifndef CONFIG_SMP #define cmpxchg64(ptr, o, n) cmpxchg64_local((ptr), (o), (n)) +#else +extern u64 __cmpxchg_u64(u64 *p, u64 old, u64 new); +#define cmpxchg64(ptr, o, n) __cmpxchg_u64((ptr), (o), (n)) #endif #endif diff --git a/arch/powerpc/Kconfig b/arch/powerpc/Kconfig index e84943d24e5c..bd1d99c664c4 100644 --- a/arch/powerpc/Kconfig +++ b/arch/powerpc/Kconfig @@ -161,6 +161,7 @@ config PPC select EDAC_ATOMIC_SCRUB select EDAC_SUPPORT select GENERIC_ATOMIC64 if PPC32 + select GENERIC_CMPXCHG64 if PPC32 select GENERIC_CLOCKEVENTS select GENERIC_CLOCKEVENTS_BROADCAST if SMP select GENERIC_CMOS_UPDATE diff --git a/arch/powerpc/include/asm/cmpxchg.h b/arch/powerpc/include/asm/cmpxchg.h index 27183871eb3b..da8be4189731 100644 --- a/arch/powerpc/include/asm/cmpxchg.h +++ b/arch/powerpc/include/asm/cmpxchg.h @@ -534,8 +534,11 @@ __cmpxchg_acquire(void *ptr, unsigned long old, unsigned long new, cmpxchg_acquire((ptr), (o), (n)); \ }) #else +extern u64 __cmpxchg_u64(u64 *p, u64 old, u64 new); + #include <asm-generic/cmpxchg-local.h> #define cmpxchg64_local(ptr, o, n) __cmpxchg64_local_generic((ptr), (o), (n)) +#define cmpxchg64(ptr, o, n) __cmpxchg_u64((ptr), (o), (n)) #endif #endif /* __KERNEL__ */ diff --git a/lib/Kconfig b/lib/Kconfig index d1573a16aa92..2b581a70ded2 100644 --- a/lib/Kconfig +++ b/lib/Kconfig @@ -500,6 +500,9 @@ config NLATTR config GENERIC_ATOMIC64 bool +config GENERIC_CMPXCHG64 + bool + config LRU_CACHE tristate diff --git a/lib/Makefile b/lib/Makefile index 988949c4fd3a..4646a06ed418 100644 --- a/lib/Makefile +++ b/lib/Makefile @@ -172,6 +172,8 @@ obj-$(CONFIG_GENERIC_CSUM) += checksum.o obj-$(CONFIG_GENERIC_ATOMIC64) += atomic64.o +obj-$(CONFIG_GENERIC_CMPXCHG64) += cmpxchg64.o + obj-$(CONFIG_ATOMIC64_SELFTEST) += atomic64_test.o obj-$(CONFIG_CPU_RMAP) += cpu_rmap.o diff --git a/lib/cmpxchg64.c b/lib/cmpxchg64.c new file mode 100644 index 000000000000..239c43d05d00 --- /dev/null +++ b/lib/cmpxchg64.c @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic implementation of cmpxchg64(). + * Derived from implementation in arch/sparc/lib/atomic32.c + * and from locking code implemented in lib/atomic32.c. + */ + +#include <linux/cache.h> +#include <linux/export.h> +#include <linux/irqflags.h> +#include <linux/spinlock.h> +#include <linux/types.h> + +/* + * We use a hashed array of spinlocks to provide exclusive access + * to each variable. Since this is expected to used on systems + * with small numbers of CPUs (<= 4 or so), we use a relatively + * small array of 16 spinlocks to avoid wasting too much memory + * on the spinlock array. + */ +#define NR_LOCKS 16 + +/* Ensure that each lock is in a separate cacheline */ +static union { + raw_spinlock_t lock; + char pad[L1_CACHE_BYTES]; +} cmpxchg_lock[NR_LOCKS] __cacheline_aligned_in_smp = { + [0 ... (NR_LOCKS - 1)] = { + .lock = __RAW_SPIN_LOCK_UNLOCKED(cmpxchg_lock.lock), + }, +}; + +static inline raw_spinlock_t *lock_addr(const u64 *v) +{ + unsigned long addr = (unsigned long) v; + + addr >>= L1_CACHE_SHIFT; + addr ^= (addr >> 8) ^ (addr >> 16); + return &cmpxchg_lock[addr & (NR_LOCKS - 1)].lock; +} + +/* + * Generic version of __cmpxchg_u64, to be used for cmpxchg64(). + * Takes u64 parameters. + */ +u64 __cmpxchg_u64(u64 *ptr, u64 old, u64 new) +{ + raw_spinlock_t *lock = lock_addr(ptr); + unsigned long flags; + u64 prev; + + raw_spin_lock_irqsave(lock, flags); + prev = READ_ONCE(*ptr); + if (prev == old) + *ptr = new; + raw_spin_unlock_irqrestore(lock, flags); + + return prev; +} +EXPORT_SYMBOL(__cmpxchg_u64); -- 2.7.4