1 // SPDX-License-Identifier: Apache-2.0 OR MIT
2
3 /*
4 64-bit atomic implementation using kuser_cmpxchg64 on pre-v6 Arm Linux/Android.
5
6 Refs:
7 - https://github.com/torvalds/linux/blob/v6.11/Documentation/arch/arm/kernel_user_helpers.rst
8 - https://github.com/rust-lang/compiler-builtins/blob/compiler_builtins-v0.1.124/src/arm_linux.rs
9
10 Note: On Miri and ThreadSanitizer which do not support inline assembly, we don't use
11 this module and use fallback implementation instead.
12 */
13
14 // TODO: Since Rust 1.64, the Linux kernel requirement for Rust when using std is 3.2+, so it should
15 // be possible to omit the dynamic kernel version check if the std feature is enabled on Rust 1.64+.
16 // https://blog.rust-lang.org/2022/08/01/Increasing-glibc-kernel-requirements.html
17
18 include!("macros.rs");
19
20 #[path = "../fallback/outline_atomics.rs"]
21 mod fallback;
22
23 #[cfg(not(portable_atomic_no_asm))]
24 use core::arch::asm;
25 use core::{mem, sync::atomic::Ordering};
26
27 use crate::utils::{Pair, U64};
28
29 // https://github.com/torvalds/linux/blob/v6.11/Documentation/arch/arm/kernel_user_helpers.rst
30 const KUSER_HELPER_VERSION: usize = 0xFFFF0FFC;
31 // __kuser_helper_version >= 5 (kernel version 3.1+)
32 const KUSER_CMPXCHG64: usize = 0xFFFF0F60;
33 #[inline]
__kuser_helper_version() -> i3234 fn __kuser_helper_version() -> i32 {
35 use core::sync::atomic::AtomicI32;
36
37 static CACHE: AtomicI32 = AtomicI32::new(0);
38 let mut v = CACHE.load(Ordering::Relaxed);
39 if v != 0 {
40 return v;
41 }
42 // SAFETY: core assumes that at least __kuser_memory_barrier (__kuser_helper_version >= 3,
43 // kernel version 2.6.15+) is available on this platform. __kuser_helper_version
44 // is always available on such a platform.
45 v = unsafe { (KUSER_HELPER_VERSION as *const i32).read() };
46 CACHE.store(v, Ordering::Relaxed);
47 v
48 }
49 #[inline]
has_kuser_cmpxchg64() -> bool50 fn has_kuser_cmpxchg64() -> bool {
51 // Note: detect_false cfg is intended to make it easy for portable-atomic developers to
52 // test cases such as has_cmpxchg16b == false, has_lse == false,
53 // __kuser_helper_version < 5, etc., and is not a public API.
54 if cfg!(portable_atomic_test_outline_atomics_detect_false) {
55 return false;
56 }
57 __kuser_helper_version() >= 5
58 }
59 #[inline]
__kuser_cmpxchg64(old_val: *const u64, new_val: *const u64, ptr: *mut u64) -> bool60 unsafe fn __kuser_cmpxchg64(old_val: *const u64, new_val: *const u64, ptr: *mut u64) -> bool {
61 // SAFETY: the caller must uphold the safety contract.
62 unsafe {
63 let f: extern "C" fn(*const u64, *const u64, *mut u64) -> u32 =
64 mem::transmute(KUSER_CMPXCHG64 as *const ());
65 f(old_val, new_val, ptr) == 0
66 }
67 }
68
69 // 64-bit atomic load by two 32-bit atomic loads.
70 #[inline]
byte_wise_atomic_load(src: *const u64) -> u6471 unsafe fn byte_wise_atomic_load(src: *const u64) -> u64 {
72 // SAFETY: the caller must uphold the safety contract.
73 unsafe {
74 let (out_lo, out_hi);
75 asm!(
76 "ldr {out_lo}, [{src}]",
77 "ldr {out_hi}, [{src}, #4]",
78 src = in(reg) src,
79 out_lo = out(reg) out_lo,
80 out_hi = out(reg) out_hi,
81 options(pure, nostack, preserves_flags, readonly),
82 );
83 U64 { pair: Pair { lo: out_lo, hi: out_hi } }.whole
84 }
85 }
86
87 #[inline(always)]
atomic_update_kuser_cmpxchg64<F>(dst: *mut u64, mut f: F) -> u64 where F: FnMut(u64) -> u64,88 unsafe fn atomic_update_kuser_cmpxchg64<F>(dst: *mut u64, mut f: F) -> u64
89 where
90 F: FnMut(u64) -> u64,
91 {
92 debug_assert!(dst as usize % 8 == 0);
93 debug_assert!(has_kuser_cmpxchg64());
94 // SAFETY: the caller must uphold the safety contract.
95 unsafe {
96 loop {
97 // This is not single-copy atomic reads, but this is ok because subsequent
98 // CAS will check for consistency.
99 //
100 // Arm's memory model allow mixed-sized atomic access.
101 // https://github.com/rust-lang/unsafe-code-guidelines/issues/345#issuecomment-1172891466
102 //
103 // Note that the C++20 memory model does not allow mixed-sized atomic access,
104 // so we must use inline assembly to implement byte_wise_atomic_load.
105 // (i.e., byte-wise atomic based on the standard library's atomic types
106 // cannot be used here).
107 let prev = byte_wise_atomic_load(dst);
108 let next = f(prev);
109 if __kuser_cmpxchg64(&prev, &next, dst) {
110 return prev;
111 }
112 }
113 }
114 }
115
116 macro_rules! atomic_with_ifunc {
117 (
118 unsafe fn $name:ident($($arg:tt)*) $(-> $ret_ty:ty)? { $($kuser_cmpxchg64_fn_body:tt)* }
119 fallback = $seqcst_fallback_fn:ident
120 ) => {
121 #[inline]
122 unsafe fn $name($($arg)*, _: Ordering) $(-> $ret_ty)? {
123 unsafe fn kuser_cmpxchg64_fn($($arg)*) $(-> $ret_ty)? {
124 $($kuser_cmpxchg64_fn_body)*
125 }
126 // SAFETY: the caller must uphold the safety contract.
127 // we only calls __kuser_cmpxchg64 if it is available.
128 unsafe {
129 ifunc!(unsafe fn($($arg)*) $(-> $ret_ty)? {
130 if has_kuser_cmpxchg64() {
131 kuser_cmpxchg64_fn
132 } else {
133 // Use SeqCst because __kuser_cmpxchg64 is always SeqCst.
134 // https://github.com/torvalds/linux/blob/v6.11/arch/arm/kernel/entry-armv.S#L692-L699
135 fallback::$seqcst_fallback_fn
136 }
137 })
138 }
139 }
140 };
141 }
142
143 atomic_with_ifunc! {
144 unsafe fn atomic_load(src: *mut u64) -> u64 {
145 // SAFETY: the caller must uphold the safety contract.
146 unsafe { atomic_update_kuser_cmpxchg64(src, |old| old) }
147 }
148 fallback = atomic_load_seqcst
149 }
150 atomic_with_ifunc! {
151 unsafe fn atomic_store(dst: *mut u64, val: u64) {
152 // SAFETY: the caller must uphold the safety contract.
153 unsafe { atomic_update_kuser_cmpxchg64(dst, |_| val); }
154 }
155 fallback = atomic_store_seqcst
156 }
157 atomic_with_ifunc! {
158 unsafe fn atomic_swap(dst: *mut u64, val: u64) -> u64 {
159 // SAFETY: the caller must uphold the safety contract.
160 unsafe { atomic_update_kuser_cmpxchg64(dst, |_| val) }
161 }
162 fallback = atomic_swap_seqcst
163 }
164 #[inline]
atomic_compare_exchange( dst: *mut u64, old: u64, new: u64, _: Ordering, _: Ordering, ) -> Result<u64, u64>165 unsafe fn atomic_compare_exchange(
166 dst: *mut u64,
167 old: u64,
168 new: u64,
169 _: Ordering,
170 _: Ordering,
171 ) -> Result<u64, u64> {
172 unsafe fn kuser_cmpxchg64_fn(dst: *mut u64, old: u64, new: u64) -> (u64, bool) {
173 // SAFETY: the caller must uphold the safety contract.
174 let prev =
175 unsafe { atomic_update_kuser_cmpxchg64(dst, |v| if v == old { new } else { v }) };
176 (prev, prev == old)
177 }
178 // SAFETY: the caller must uphold the safety contract.
179 // we only calls __kuser_cmpxchg64 if it is available.
180 let (prev, ok) = unsafe {
181 ifunc!(unsafe fn(dst: *mut u64, old: u64, new: u64) -> (u64, bool) {
182 if has_kuser_cmpxchg64() {
183 kuser_cmpxchg64_fn
184 } else {
185 // Use SeqCst because __kuser_cmpxchg64 is always SeqCst.
186 // https://github.com/torvalds/linux/blob/v6.11/arch/arm/kernel/entry-armv.S#L692-L699
187 fallback::atomic_compare_exchange_seqcst
188 }
189 })
190 };
191 if ok {
192 Ok(prev)
193 } else {
194 Err(prev)
195 }
196 }
197 use self::atomic_compare_exchange as atomic_compare_exchange_weak;
198 atomic_with_ifunc! {
199 unsafe fn atomic_add(dst: *mut u64, val: u64) -> u64 {
200 // SAFETY: the caller must uphold the safety contract.
201 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x.wrapping_add(val)) }
202 }
203 fallback = atomic_add_seqcst
204 }
205 atomic_with_ifunc! {
206 unsafe fn atomic_sub(dst: *mut u64, val: u64) -> u64 {
207 // SAFETY: the caller must uphold the safety contract.
208 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x.wrapping_sub(val)) }
209 }
210 fallback = atomic_sub_seqcst
211 }
212 atomic_with_ifunc! {
213 unsafe fn atomic_and(dst: *mut u64, val: u64) -> u64 {
214 // SAFETY: the caller must uphold the safety contract.
215 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x & val) }
216 }
217 fallback = atomic_and_seqcst
218 }
219 atomic_with_ifunc! {
220 unsafe fn atomic_nand(dst: *mut u64, val: u64) -> u64 {
221 // SAFETY: the caller must uphold the safety contract.
222 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| !(x & val)) }
223 }
224 fallback = atomic_nand_seqcst
225 }
226 atomic_with_ifunc! {
227 unsafe fn atomic_or(dst: *mut u64, val: u64) -> u64 {
228 // SAFETY: the caller must uphold the safety contract.
229 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x | val) }
230 }
231 fallback = atomic_or_seqcst
232 }
233 atomic_with_ifunc! {
234 unsafe fn atomic_xor(dst: *mut u64, val: u64) -> u64 {
235 // SAFETY: the caller must uphold the safety contract.
236 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| x ^ val) }
237 }
238 fallback = atomic_xor_seqcst
239 }
240 atomic_with_ifunc! {
241 unsafe fn atomic_max(dst: *mut u64, val: u64) -> u64 {
242 #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
243 // SAFETY: the caller must uphold the safety contract.
244 unsafe {
245 atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::max(x as i64, val as i64) as u64)
246 }
247 }
248 fallback = atomic_max_seqcst
249 }
250 atomic_with_ifunc! {
251 unsafe fn atomic_umax(dst: *mut u64, val: u64) -> u64 {
252 // SAFETY: the caller must uphold the safety contract.
253 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::max(x, val)) }
254 }
255 fallback = atomic_umax_seqcst
256 }
257 atomic_with_ifunc! {
258 unsafe fn atomic_min(dst: *mut u64, val: u64) -> u64 {
259 #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
260 // SAFETY: the caller must uphold the safety contract.
261 unsafe {
262 atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::min(x as i64, val as i64) as u64)
263 }
264 }
265 fallback = atomic_min_seqcst
266 }
267 atomic_with_ifunc! {
268 unsafe fn atomic_umin(dst: *mut u64, val: u64) -> u64 {
269 // SAFETY: the caller must uphold the safety contract.
270 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| core::cmp::min(x, val)) }
271 }
272 fallback = atomic_umin_seqcst
273 }
274 atomic_with_ifunc! {
275 unsafe fn atomic_not(dst: *mut u64) -> u64 {
276 // SAFETY: the caller must uphold the safety contract.
277 unsafe { atomic_update_kuser_cmpxchg64(dst, |x| !x) }
278 }
279 fallback = atomic_not_seqcst
280 }
281 atomic_with_ifunc! {
282 unsafe fn atomic_neg(dst: *mut u64) -> u64 {
283 // SAFETY: the caller must uphold the safety contract.
284 unsafe { atomic_update_kuser_cmpxchg64(dst, u64::wrapping_neg) }
285 }
286 fallback = atomic_neg_seqcst
287 }
288
289 #[inline]
is_lock_free() -> bool290 fn is_lock_free() -> bool {
291 has_kuser_cmpxchg64()
292 }
293 const IS_ALWAYS_LOCK_FREE: bool = false;
294
295 atomic64!(AtomicI64, i64, atomic_max, atomic_min);
296 atomic64!(AtomicU64, u64, atomic_umax, atomic_umin);
297
298 #[allow(
299 clippy::alloc_instead_of_core,
300 clippy::std_instead_of_alloc,
301 clippy::std_instead_of_core,
302 clippy::undocumented_unsafe_blocks,
303 clippy::wildcard_imports
304 )]
305 #[cfg(test)]
306 mod tests {
307 use super::*;
308
309 #[test]
kuser_helper_version()310 fn kuser_helper_version() {
311 let version = __kuser_helper_version();
312 assert!(version >= 5, "{:?}", version);
313 assert_eq!(version, unsafe { (KUSER_HELPER_VERSION as *const i32).read() });
314 }
315
316 test_atomic_int!(i64);
317 test_atomic_int!(u64);
318
319 // load/store/swap implementation is not affected by signedness, so it is
320 // enough to test only unsigned types.
321 stress_test!(u64);
322 }
323