Skip to main content

ax_cpu/
user_access.rs

1//! Nofault access to user-space words.
2
3/// Direction of an architecture-level user-memory access check.
4#[derive(Clone, Copy, Debug, Eq, PartialEq)]
5pub enum UserAccessType {
6    /// The kernel intends to read bytes supplied by user space.
7    Read,
8    /// The kernel intends to write bytes into user space.
9    Write,
10}
11
12/// Failure returned by a nofault user read.
13#[derive(Clone, Copy, Debug, Eq, PartialEq)]
14pub enum UserAccessError {
15    /// The user word was not accessible without resolving a page fault.
16    Fault,
17}
18
19/// Atomic operation supported by [`user_atomic_u32`].
20#[repr(u32)]
21#[derive(Clone, Copy, Debug, Eq, PartialEq)]
22pub enum UserAtomicU32Op {
23    /// Replaces the word with the supplied argument.
24    Set    = 0,
25    /// Adds the supplied argument with wrapping semantics.
26    Add    = 1,
27    /// ORs the word with the supplied argument.
28    Or     = 2,
29    /// ANDs the word with the inverse of the supplied argument.
30    AndNot = 3,
31    /// XORs the word with the supplied argument.
32    Xor    = 4,
33}
34
35/// Failure returned by a nofault user atomic operation.
36#[derive(Clone, Copy, Debug, Eq, PartialEq)]
37pub enum UserAtomicError {
38    /// The user word was not accessible without resolving a page fault.
39    Fault,
40    /// A bounded load-linked/store-conditional sequence made no progress.
41    Retry,
42}
43
44unsafe extern "C" {
45    fn __axcpu_user_read_u32(address: *const u32, value: *mut u32) -> u32;
46
47    fn __axcpu_user_cmpxchg_u32(
48        address: *mut u32,
49        expected: u32,
50        replacement: u32,
51        observed: *mut u32,
52    ) -> u32;
53
54    fn __axcpu_user_atomic_u32(
55        address: *mut u32,
56        operation: u32,
57        argument: u32,
58        old_value: *mut u32,
59    ) -> u32;
60}
61
62/// Reads one aligned user-space word without resolving faults.
63///
64/// # Safety
65///
66/// `address` must be aligned for `u32` and point into the calling task's user
67/// address range. The caller must resolve [`UserAccessError::Fault`] only after
68/// releasing every lock whose critical section must remain nofault.
69pub unsafe fn user_read_u32(address: *const u32) -> Result<u32, UserAccessError> {
70    let mut value = 0;
71    let status = unsafe { __axcpu_user_read_u32(address, &mut value) };
72    match status {
73        0 => Ok(value),
74        1 => Err(UserAccessError::Fault),
75        _ => unreachable!("architecture returned an invalid user read status"),
76    }
77}
78
79/// Atomically updates one aligned user-space word without resolving faults.
80///
81/// The previous value is returned on success. A fault is redirected through
82/// the dedicated nofault exception table before the OS page-fault handler is
83/// invoked, so this function never sleeps or allocates.
84///
85/// # Safety
86///
87/// `address` must be aligned for `u32` and point into the calling task's user
88/// address range. The caller must serialize this operation with the protocol
89/// that consumes its result and must resolve [`UserAtomicError::Fault`] only
90/// after releasing every lock whose critical section must remain nofault.
91pub unsafe fn user_atomic_u32(
92    address: *mut u32,
93    operation: UserAtomicU32Op,
94    argument: u32,
95) -> Result<u32, UserAtomicError> {
96    let mut old_value = 0;
97    let status =
98        unsafe { __axcpu_user_atomic_u32(address, operation as u32, argument, &mut old_value) };
99    match status {
100        0 => Ok(old_value),
101        1 => Err(UserAtomicError::Fault),
102        2 => Err(UserAtomicError::Retry),
103        _ => unreachable!("architecture returned an invalid user atomic status"),
104    }
105}
106
107/// Compares and conditionally replaces one aligned user word without faulting in pages.
108///
109/// Returns the observed word: equality with `expected` means the replacement
110/// was stored. A successful replacement orders surrounding memory accesses;
111/// a mismatch only promises an atomic observation. Faults and bounded LL/SC
112/// exhaustion use the same exception-table protocol as [`user_atomic_u32`].
113///
114/// # Safety
115///
116/// `address` must be aligned for `u32` and lie in the current task's user
117/// address range. Its active address space and user-access permissions must
118/// remain installed through the call. Resolve faults only after releasing
119/// every lock whose critical section must remain nofault.
120pub unsafe fn user_cmpxchg_u32(
121    address: *mut u32,
122    expected: u32,
123    replacement: u32,
124) -> Result<u32, UserAtomicError> {
125    let mut observed = 0;
126    // SAFETY: the caller supplies the active user address; the output points
127    // to this function's initialized kernel-local word for the whole call.
128    let status = unsafe { __axcpu_user_cmpxchg_u32(address, expected, replacement, &mut observed) };
129    match status {
130        0 => Ok(observed),
131        1 => Err(UserAtomicError::Fault),
132        2 => Err(UserAtomicError::Retry),
133        _ => unreachable!("architecture returned an invalid user cmpxchg status"),
134    }
135}