18c2ecf20Sopenharmony_ci/* SPDX-License-Identifier: GPL-2.0 */ 28c2ecf20Sopenharmony_ci#ifndef __ASM_ARM_CMPXCHG_H 38c2ecf20Sopenharmony_ci#define __ASM_ARM_CMPXCHG_H 48c2ecf20Sopenharmony_ci 58c2ecf20Sopenharmony_ci#include <linux/irqflags.h> 68c2ecf20Sopenharmony_ci#include <linux/prefetch.h> 78c2ecf20Sopenharmony_ci#include <asm/barrier.h> 88c2ecf20Sopenharmony_ci 98c2ecf20Sopenharmony_ci#if defined(CONFIG_CPU_SA1100) || defined(CONFIG_CPU_SA110) 108c2ecf20Sopenharmony_ci/* 118c2ecf20Sopenharmony_ci * On the StrongARM, "swp" is terminally broken since it bypasses the 128c2ecf20Sopenharmony_ci * cache totally. This means that the cache becomes inconsistent, and, 138c2ecf20Sopenharmony_ci * since we use normal loads/stores as well, this is really bad. 148c2ecf20Sopenharmony_ci * Typically, this causes oopsen in filp_close, but could have other, 158c2ecf20Sopenharmony_ci * more disastrous effects. There are two work-arounds: 168c2ecf20Sopenharmony_ci * 1. Disable interrupts and emulate the atomic swap 178c2ecf20Sopenharmony_ci * 2. Clean the cache, perform atomic swap, flush the cache 188c2ecf20Sopenharmony_ci * 198c2ecf20Sopenharmony_ci * We choose (1) since its the "easiest" to achieve here and is not 208c2ecf20Sopenharmony_ci * dependent on the processor type. 218c2ecf20Sopenharmony_ci * 228c2ecf20Sopenharmony_ci * NOTE that this solution won't work on an SMP system, so explcitly 238c2ecf20Sopenharmony_ci * forbid it here. 248c2ecf20Sopenharmony_ci */ 258c2ecf20Sopenharmony_ci#define swp_is_buggy 268c2ecf20Sopenharmony_ci#endif 278c2ecf20Sopenharmony_ci 288c2ecf20Sopenharmony_cistatic inline unsigned long __xchg(unsigned long x, volatile void *ptr, int size) 298c2ecf20Sopenharmony_ci{ 308c2ecf20Sopenharmony_ci extern void __bad_xchg(volatile void *, int); 318c2ecf20Sopenharmony_ci unsigned long ret; 328c2ecf20Sopenharmony_ci#ifdef swp_is_buggy 338c2ecf20Sopenharmony_ci unsigned long flags; 348c2ecf20Sopenharmony_ci#endif 358c2ecf20Sopenharmony_ci#if __LINUX_ARM_ARCH__ >= 6 368c2ecf20Sopenharmony_ci unsigned int tmp; 378c2ecf20Sopenharmony_ci#endif 388c2ecf20Sopenharmony_ci 398c2ecf20Sopenharmony_ci prefetchw((const void *)ptr); 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci switch (size) { 428c2ecf20Sopenharmony_ci#if __LINUX_ARM_ARCH__ >= 6 438c2ecf20Sopenharmony_ci#ifndef CONFIG_CPU_V6 /* MIN ARCH >= V6K */ 448c2ecf20Sopenharmony_ci case 1: 458c2ecf20Sopenharmony_ci asm volatile("@ __xchg1\n" 468c2ecf20Sopenharmony_ci "1: ldrexb %0, [%3]\n" 478c2ecf20Sopenharmony_ci " strexb %1, %2, [%3]\n" 488c2ecf20Sopenharmony_ci " teq %1, #0\n" 498c2ecf20Sopenharmony_ci " bne 1b" 508c2ecf20Sopenharmony_ci : "=&r" (ret), "=&r" (tmp) 518c2ecf20Sopenharmony_ci : "r" (x), "r" (ptr) 528c2ecf20Sopenharmony_ci : "memory", "cc"); 538c2ecf20Sopenharmony_ci break; 548c2ecf20Sopenharmony_ci case 2: 558c2ecf20Sopenharmony_ci asm volatile("@ __xchg2\n" 568c2ecf20Sopenharmony_ci "1: ldrexh %0, [%3]\n" 578c2ecf20Sopenharmony_ci " strexh %1, %2, [%3]\n" 588c2ecf20Sopenharmony_ci " teq %1, #0\n" 598c2ecf20Sopenharmony_ci " bne 1b" 608c2ecf20Sopenharmony_ci : "=&r" (ret), "=&r" (tmp) 618c2ecf20Sopenharmony_ci : "r" (x), "r" (ptr) 628c2ecf20Sopenharmony_ci : "memory", "cc"); 638c2ecf20Sopenharmony_ci break; 648c2ecf20Sopenharmony_ci#endif 658c2ecf20Sopenharmony_ci case 4: 668c2ecf20Sopenharmony_ci asm volatile("@ __xchg4\n" 678c2ecf20Sopenharmony_ci "1: ldrex %0, [%3]\n" 688c2ecf20Sopenharmony_ci " strex %1, %2, [%3]\n" 698c2ecf20Sopenharmony_ci " teq %1, #0\n" 708c2ecf20Sopenharmony_ci " bne 1b" 718c2ecf20Sopenharmony_ci : "=&r" (ret), "=&r" (tmp) 728c2ecf20Sopenharmony_ci : "r" (x), "r" (ptr) 738c2ecf20Sopenharmony_ci : "memory", "cc"); 748c2ecf20Sopenharmony_ci break; 758c2ecf20Sopenharmony_ci#elif defined(swp_is_buggy) 768c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP 778c2ecf20Sopenharmony_ci#error SMP is not supported on this platform 788c2ecf20Sopenharmony_ci#endif 798c2ecf20Sopenharmony_ci case 1: 808c2ecf20Sopenharmony_ci raw_local_irq_save(flags); 818c2ecf20Sopenharmony_ci ret = *(volatile unsigned char *)ptr; 828c2ecf20Sopenharmony_ci *(volatile unsigned char *)ptr = x; 838c2ecf20Sopenharmony_ci raw_local_irq_restore(flags); 848c2ecf20Sopenharmony_ci break; 858c2ecf20Sopenharmony_ci 868c2ecf20Sopenharmony_ci case 4: 878c2ecf20Sopenharmony_ci raw_local_irq_save(flags); 888c2ecf20Sopenharmony_ci ret = *(volatile unsigned long *)ptr; 898c2ecf20Sopenharmony_ci *(volatile unsigned long *)ptr = x; 908c2ecf20Sopenharmony_ci raw_local_irq_restore(flags); 918c2ecf20Sopenharmony_ci break; 928c2ecf20Sopenharmony_ci#else 938c2ecf20Sopenharmony_ci case 1: 948c2ecf20Sopenharmony_ci asm volatile("@ __xchg1\n" 958c2ecf20Sopenharmony_ci " swpb %0, %1, [%2]" 968c2ecf20Sopenharmony_ci : "=&r" (ret) 978c2ecf20Sopenharmony_ci : "r" (x), "r" (ptr) 988c2ecf20Sopenharmony_ci : "memory", "cc"); 998c2ecf20Sopenharmony_ci break; 1008c2ecf20Sopenharmony_ci case 4: 1018c2ecf20Sopenharmony_ci asm volatile("@ __xchg4\n" 1028c2ecf20Sopenharmony_ci " swp %0, %1, [%2]" 1038c2ecf20Sopenharmony_ci : "=&r" (ret) 1048c2ecf20Sopenharmony_ci : "r" (x), "r" (ptr) 1058c2ecf20Sopenharmony_ci : "memory", "cc"); 1068c2ecf20Sopenharmony_ci break; 1078c2ecf20Sopenharmony_ci#endif 1088c2ecf20Sopenharmony_ci default: 1098c2ecf20Sopenharmony_ci /* Cause a link-time error, the xchg() size is not supported */ 1108c2ecf20Sopenharmony_ci __bad_xchg(ptr, size), ret = 0; 1118c2ecf20Sopenharmony_ci break; 1128c2ecf20Sopenharmony_ci } 1138c2ecf20Sopenharmony_ci 1148c2ecf20Sopenharmony_ci return ret; 1158c2ecf20Sopenharmony_ci} 1168c2ecf20Sopenharmony_ci 1178c2ecf20Sopenharmony_ci#define xchg_relaxed(ptr, x) ({ \ 1188c2ecf20Sopenharmony_ci (__typeof__(*(ptr)))__xchg((unsigned long)(x), (ptr), \ 1198c2ecf20Sopenharmony_ci sizeof(*(ptr))); \ 1208c2ecf20Sopenharmony_ci}) 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci#include <asm-generic/cmpxchg-local.h> 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci#if __LINUX_ARM_ARCH__ < 6 1258c2ecf20Sopenharmony_ci/* min ARCH < ARMv6 */ 1268c2ecf20Sopenharmony_ci 1278c2ecf20Sopenharmony_ci#ifdef CONFIG_SMP 1288c2ecf20Sopenharmony_ci#error "SMP is not supported on this platform" 1298c2ecf20Sopenharmony_ci#endif 1308c2ecf20Sopenharmony_ci 1318c2ecf20Sopenharmony_ci#define xchg xchg_relaxed 1328c2ecf20Sopenharmony_ci 1338c2ecf20Sopenharmony_ci/* 1348c2ecf20Sopenharmony_ci * cmpxchg_local and cmpxchg64_local are atomic wrt current CPU. Always make 1358c2ecf20Sopenharmony_ci * them available. 1368c2ecf20Sopenharmony_ci */ 1378c2ecf20Sopenharmony_ci#define cmpxchg_local(ptr, o, n) ({ \ 1388c2ecf20Sopenharmony_ci (__typeof(*ptr))__cmpxchg_local_generic((ptr), \ 1398c2ecf20Sopenharmony_ci (unsigned long)(o), \ 1408c2ecf20Sopenharmony_ci (unsigned long)(n), \ 1418c2ecf20Sopenharmony_ci sizeof(*(ptr))); \ 1428c2ecf20Sopenharmony_ci}) 1438c2ecf20Sopenharmony_ci 1448c2ecf20Sopenharmony_ci#define cmpxchg64_local(ptr, o, n) __cmpxchg64_local_generic((ptr), (o), (n)) 1458c2ecf20Sopenharmony_ci 1468c2ecf20Sopenharmony_ci#include <asm-generic/cmpxchg.h> 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci#else /* min ARCH >= ARMv6 */ 1498c2ecf20Sopenharmony_ci 1508c2ecf20Sopenharmony_ciextern void __bad_cmpxchg(volatile void *ptr, int size); 1518c2ecf20Sopenharmony_ci 1528c2ecf20Sopenharmony_ci/* 1538c2ecf20Sopenharmony_ci * cmpxchg only support 32-bits operands on ARMv6. 1548c2ecf20Sopenharmony_ci */ 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_cistatic inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old, 1578c2ecf20Sopenharmony_ci unsigned long new, int size) 1588c2ecf20Sopenharmony_ci{ 1598c2ecf20Sopenharmony_ci unsigned long oldval, res; 1608c2ecf20Sopenharmony_ci 1618c2ecf20Sopenharmony_ci prefetchw((const void *)ptr); 1628c2ecf20Sopenharmony_ci 1638c2ecf20Sopenharmony_ci switch (size) { 1648c2ecf20Sopenharmony_ci#ifndef CONFIG_CPU_V6 /* min ARCH >= ARMv6K */ 1658c2ecf20Sopenharmony_ci case 1: 1668c2ecf20Sopenharmony_ci do { 1678c2ecf20Sopenharmony_ci asm volatile("@ __cmpxchg1\n" 1688c2ecf20Sopenharmony_ci " ldrexb %1, [%2]\n" 1698c2ecf20Sopenharmony_ci " mov %0, #0\n" 1708c2ecf20Sopenharmony_ci " teq %1, %3\n" 1718c2ecf20Sopenharmony_ci " strexbeq %0, %4, [%2]\n" 1728c2ecf20Sopenharmony_ci : "=&r" (res), "=&r" (oldval) 1738c2ecf20Sopenharmony_ci : "r" (ptr), "Ir" (old), "r" (new) 1748c2ecf20Sopenharmony_ci : "memory", "cc"); 1758c2ecf20Sopenharmony_ci } while (res); 1768c2ecf20Sopenharmony_ci break; 1778c2ecf20Sopenharmony_ci case 2: 1788c2ecf20Sopenharmony_ci do { 1798c2ecf20Sopenharmony_ci asm volatile("@ __cmpxchg1\n" 1808c2ecf20Sopenharmony_ci " ldrexh %1, [%2]\n" 1818c2ecf20Sopenharmony_ci " mov %0, #0\n" 1828c2ecf20Sopenharmony_ci " teq %1, %3\n" 1838c2ecf20Sopenharmony_ci " strexheq %0, %4, [%2]\n" 1848c2ecf20Sopenharmony_ci : "=&r" (res), "=&r" (oldval) 1858c2ecf20Sopenharmony_ci : "r" (ptr), "Ir" (old), "r" (new) 1868c2ecf20Sopenharmony_ci : "memory", "cc"); 1878c2ecf20Sopenharmony_ci } while (res); 1888c2ecf20Sopenharmony_ci break; 1898c2ecf20Sopenharmony_ci#endif 1908c2ecf20Sopenharmony_ci case 4: 1918c2ecf20Sopenharmony_ci do { 1928c2ecf20Sopenharmony_ci asm volatile("@ __cmpxchg4\n" 1938c2ecf20Sopenharmony_ci " ldrex %1, [%2]\n" 1948c2ecf20Sopenharmony_ci " mov %0, #0\n" 1958c2ecf20Sopenharmony_ci " teq %1, %3\n" 1968c2ecf20Sopenharmony_ci " strexeq %0, %4, [%2]\n" 1978c2ecf20Sopenharmony_ci : "=&r" (res), "=&r" (oldval) 1988c2ecf20Sopenharmony_ci : "r" (ptr), "Ir" (old), "r" (new) 1998c2ecf20Sopenharmony_ci : "memory", "cc"); 2008c2ecf20Sopenharmony_ci } while (res); 2018c2ecf20Sopenharmony_ci break; 2028c2ecf20Sopenharmony_ci default: 2038c2ecf20Sopenharmony_ci __bad_cmpxchg(ptr, size); 2048c2ecf20Sopenharmony_ci oldval = 0; 2058c2ecf20Sopenharmony_ci } 2068c2ecf20Sopenharmony_ci 2078c2ecf20Sopenharmony_ci return oldval; 2088c2ecf20Sopenharmony_ci} 2098c2ecf20Sopenharmony_ci 2108c2ecf20Sopenharmony_ci#define cmpxchg_relaxed(ptr,o,n) ({ \ 2118c2ecf20Sopenharmony_ci (__typeof__(*(ptr)))__cmpxchg((ptr), \ 2128c2ecf20Sopenharmony_ci (unsigned long)(o), \ 2138c2ecf20Sopenharmony_ci (unsigned long)(n), \ 2148c2ecf20Sopenharmony_ci sizeof(*(ptr))); \ 2158c2ecf20Sopenharmony_ci}) 2168c2ecf20Sopenharmony_ci 2178c2ecf20Sopenharmony_cistatic inline unsigned long __cmpxchg_local(volatile void *ptr, 2188c2ecf20Sopenharmony_ci unsigned long old, 2198c2ecf20Sopenharmony_ci unsigned long new, int size) 2208c2ecf20Sopenharmony_ci{ 2218c2ecf20Sopenharmony_ci unsigned long ret; 2228c2ecf20Sopenharmony_ci 2238c2ecf20Sopenharmony_ci switch (size) { 2248c2ecf20Sopenharmony_ci#ifdef CONFIG_CPU_V6 /* min ARCH == ARMv6 */ 2258c2ecf20Sopenharmony_ci case 1: 2268c2ecf20Sopenharmony_ci case 2: 2278c2ecf20Sopenharmony_ci ret = __cmpxchg_local_generic(ptr, old, new, size); 2288c2ecf20Sopenharmony_ci break; 2298c2ecf20Sopenharmony_ci#endif 2308c2ecf20Sopenharmony_ci default: 2318c2ecf20Sopenharmony_ci ret = __cmpxchg(ptr, old, new, size); 2328c2ecf20Sopenharmony_ci } 2338c2ecf20Sopenharmony_ci 2348c2ecf20Sopenharmony_ci return ret; 2358c2ecf20Sopenharmony_ci} 2368c2ecf20Sopenharmony_ci 2378c2ecf20Sopenharmony_ci#define cmpxchg_local(ptr, o, n) ({ \ 2388c2ecf20Sopenharmony_ci (__typeof(*ptr))__cmpxchg_local((ptr), \ 2398c2ecf20Sopenharmony_ci (unsigned long)(o), \ 2408c2ecf20Sopenharmony_ci (unsigned long)(n), \ 2418c2ecf20Sopenharmony_ci sizeof(*(ptr))); \ 2428c2ecf20Sopenharmony_ci}) 2438c2ecf20Sopenharmony_ci 2448c2ecf20Sopenharmony_cistatic inline unsigned long long __cmpxchg64(unsigned long long *ptr, 2458c2ecf20Sopenharmony_ci unsigned long long old, 2468c2ecf20Sopenharmony_ci unsigned long long new) 2478c2ecf20Sopenharmony_ci{ 2488c2ecf20Sopenharmony_ci unsigned long long oldval; 2498c2ecf20Sopenharmony_ci unsigned long res; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci prefetchw(ptr); 2528c2ecf20Sopenharmony_ci 2538c2ecf20Sopenharmony_ci __asm__ __volatile__( 2548c2ecf20Sopenharmony_ci"1: ldrexd %1, %H1, [%3]\n" 2558c2ecf20Sopenharmony_ci" teq %1, %4\n" 2568c2ecf20Sopenharmony_ci" teqeq %H1, %H4\n" 2578c2ecf20Sopenharmony_ci" bne 2f\n" 2588c2ecf20Sopenharmony_ci" strexd %0, %5, %H5, [%3]\n" 2598c2ecf20Sopenharmony_ci" teq %0, #0\n" 2608c2ecf20Sopenharmony_ci" bne 1b\n" 2618c2ecf20Sopenharmony_ci"2:" 2628c2ecf20Sopenharmony_ci : "=&r" (res), "=&r" (oldval), "+Qo" (*ptr) 2638c2ecf20Sopenharmony_ci : "r" (ptr), "r" (old), "r" (new) 2648c2ecf20Sopenharmony_ci : "cc"); 2658c2ecf20Sopenharmony_ci 2668c2ecf20Sopenharmony_ci return oldval; 2678c2ecf20Sopenharmony_ci} 2688c2ecf20Sopenharmony_ci 2698c2ecf20Sopenharmony_ci#define cmpxchg64_relaxed(ptr, o, n) ({ \ 2708c2ecf20Sopenharmony_ci (__typeof__(*(ptr)))__cmpxchg64((ptr), \ 2718c2ecf20Sopenharmony_ci (unsigned long long)(o), \ 2728c2ecf20Sopenharmony_ci (unsigned long long)(n)); \ 2738c2ecf20Sopenharmony_ci}) 2748c2ecf20Sopenharmony_ci 2758c2ecf20Sopenharmony_ci#define cmpxchg64_local(ptr, o, n) cmpxchg64_relaxed((ptr), (o), (n)) 2768c2ecf20Sopenharmony_ci 2778c2ecf20Sopenharmony_ci#endif /* __LINUX_ARM_ARCH__ >= 6 */ 2788c2ecf20Sopenharmony_ci 2798c2ecf20Sopenharmony_ci#endif /* __ASM_ARM_CMPXCHG_H */ 280