• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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