b.liu | e958203 | 2025-04-17 19:18:16 +0800 | [diff] [blame^] | 1 | /* |
| 2 | * 1,2 and 4 byte cmpxchg and xchg implementations for OpenRISC. |
| 3 | * |
| 4 | * Copyright (C) 2014 Stefan Kristiansson <stefan.kristiansson@saunalahti.fi> |
| 5 | * Copyright (C) 2017 Stafford Horne <shorne@gmail.com> |
| 6 | * |
| 7 | * This file is licensed under the terms of the GNU General Public License |
| 8 | * version 2. This program is licensed "as is" without any warranty of any |
| 9 | * kind, whether express or implied. |
| 10 | * |
| 11 | * Note: |
| 12 | * The portable implementations of 1 and 2 byte xchg and cmpxchg using a 4 |
| 13 | * byte cmpxchg is sourced heavily from the sh and mips implementations. |
| 14 | */ |
| 15 | |
| 16 | #ifndef __ASM_OPENRISC_CMPXCHG_H |
| 17 | #define __ASM_OPENRISC_CMPXCHG_H |
| 18 | |
| 19 | #include <linux/bits.h> |
| 20 | #include <linux/compiler.h> |
| 21 | #include <linux/types.h> |
| 22 | |
| 23 | #define __HAVE_ARCH_CMPXCHG 1 |
| 24 | |
| 25 | static inline unsigned long cmpxchg_u32(volatile void *ptr, |
| 26 | unsigned long old, unsigned long new) |
| 27 | { |
| 28 | __asm__ __volatile__( |
| 29 | "1: l.lwa %0, 0(%1) \n" |
| 30 | " l.sfeq %0, %2 \n" |
| 31 | " l.bnf 2f \n" |
| 32 | " l.nop \n" |
| 33 | " l.swa 0(%1), %3 \n" |
| 34 | " l.bnf 1b \n" |
| 35 | " l.nop \n" |
| 36 | "2: \n" |
| 37 | : "=&r"(old) |
| 38 | : "r"(ptr), "r"(old), "r"(new) |
| 39 | : "cc", "memory"); |
| 40 | |
| 41 | return old; |
| 42 | } |
| 43 | |
| 44 | static inline unsigned long xchg_u32(volatile void *ptr, |
| 45 | unsigned long val) |
| 46 | { |
| 47 | __asm__ __volatile__( |
| 48 | "1: l.lwa %0, 0(%1) \n" |
| 49 | " l.swa 0(%1), %2 \n" |
| 50 | " l.bnf 1b \n" |
| 51 | " l.nop \n" |
| 52 | : "=&r"(val) |
| 53 | : "r"(ptr), "r"(val) |
| 54 | : "cc", "memory"); |
| 55 | |
| 56 | return val; |
| 57 | } |
| 58 | |
| 59 | static inline u32 cmpxchg_small(volatile void *ptr, u32 old, u32 new, |
| 60 | int size) |
| 61 | { |
| 62 | int off = (unsigned long)ptr % sizeof(u32); |
| 63 | volatile u32 *p = ptr - off; |
| 64 | #ifdef __BIG_ENDIAN |
| 65 | int bitoff = (sizeof(u32) - size - off) * BITS_PER_BYTE; |
| 66 | #else |
| 67 | int bitoff = off * BITS_PER_BYTE; |
| 68 | #endif |
| 69 | u32 bitmask = ((0x1 << size * BITS_PER_BYTE) - 1) << bitoff; |
| 70 | u32 load32, old32, new32; |
| 71 | u32 ret; |
| 72 | |
| 73 | load32 = READ_ONCE(*p); |
| 74 | |
| 75 | while (true) { |
| 76 | ret = (load32 & bitmask) >> bitoff; |
| 77 | if (old != ret) |
| 78 | return ret; |
| 79 | |
| 80 | old32 = (load32 & ~bitmask) | (old << bitoff); |
| 81 | new32 = (load32 & ~bitmask) | (new << bitoff); |
| 82 | |
| 83 | /* Do 32 bit cmpxchg */ |
| 84 | load32 = cmpxchg_u32(p, old32, new32); |
| 85 | if (load32 == old32) |
| 86 | return old; |
| 87 | } |
| 88 | } |
| 89 | |
| 90 | /* xchg */ |
| 91 | |
| 92 | static inline u32 xchg_small(volatile void *ptr, u32 x, int size) |
| 93 | { |
| 94 | int off = (unsigned long)ptr % sizeof(u32); |
| 95 | volatile u32 *p = ptr - off; |
| 96 | #ifdef __BIG_ENDIAN |
| 97 | int bitoff = (sizeof(u32) - size - off) * BITS_PER_BYTE; |
| 98 | #else |
| 99 | int bitoff = off * BITS_PER_BYTE; |
| 100 | #endif |
| 101 | u32 bitmask = ((0x1 << size * BITS_PER_BYTE) - 1) << bitoff; |
| 102 | u32 oldv, newv; |
| 103 | u32 ret; |
| 104 | |
| 105 | do { |
| 106 | oldv = READ_ONCE(*p); |
| 107 | ret = (oldv & bitmask) >> bitoff; |
| 108 | newv = (oldv & ~bitmask) | (x << bitoff); |
| 109 | } while (cmpxchg_u32(p, oldv, newv) != oldv); |
| 110 | |
| 111 | return ret; |
| 112 | } |
| 113 | |
| 114 | /* |
| 115 | * This function doesn't exist, so you'll get a linker error |
| 116 | * if something tries to do an invalid cmpxchg(). |
| 117 | */ |
| 118 | extern unsigned long __cmpxchg_called_with_bad_pointer(void) |
| 119 | __compiletime_error("Bad argument size for cmpxchg"); |
| 120 | |
| 121 | static inline unsigned long __cmpxchg(volatile void *ptr, unsigned long old, |
| 122 | unsigned long new, int size) |
| 123 | { |
| 124 | switch (size) { |
| 125 | case 1: |
| 126 | case 2: |
| 127 | return cmpxchg_small(ptr, old, new, size); |
| 128 | case 4: |
| 129 | return cmpxchg_u32(ptr, old, new); |
| 130 | default: |
| 131 | return __cmpxchg_called_with_bad_pointer(); |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | #define cmpxchg(ptr, o, n) \ |
| 136 | ({ \ |
| 137 | (__typeof__(*(ptr))) __cmpxchg((ptr), \ |
| 138 | (unsigned long)(o), \ |
| 139 | (unsigned long)(n), \ |
| 140 | sizeof(*(ptr))); \ |
| 141 | }) |
| 142 | |
| 143 | /* |
| 144 | * This function doesn't exist, so you'll get a linker error if |
| 145 | * something tries to do an invalidly-sized xchg(). |
| 146 | */ |
| 147 | extern unsigned long __xchg_called_with_bad_pointer(void) |
| 148 | __compiletime_error("Bad argument size for xchg"); |
| 149 | |
| 150 | static inline unsigned long __xchg(volatile void *ptr, unsigned long with, |
| 151 | int size) |
| 152 | { |
| 153 | switch (size) { |
| 154 | case 1: |
| 155 | case 2: |
| 156 | return xchg_small(ptr, with, size); |
| 157 | case 4: |
| 158 | return xchg_u32(ptr, with); |
| 159 | default: |
| 160 | return __xchg_called_with_bad_pointer(); |
| 161 | } |
| 162 | } |
| 163 | |
| 164 | #define xchg(ptr, with) \ |
| 165 | ({ \ |
| 166 | (__typeof__(*(ptr))) __xchg((ptr), \ |
| 167 | (unsigned long)(with), \ |
| 168 | sizeof(*(ptr))); \ |
| 169 | }) |
| 170 | |
| 171 | #endif /* __ASM_OPENRISC_CMPXCHG_H */ |