twizzler_abi/syscall/
thread_sync.rs

1use core::{
2    ptr,
3    sync::atomic::{AtomicU32, AtomicU64},
4    time::Duration,
5};
6
7use bitflags::bitflags;
8use twizzler_rt_abi::error::TwzError;
9
10use super::{convert_codes_to_result, twzerr, Syscall};
11use crate::{arch::syscall::raw_syscall, object::ObjID};
12#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
13#[repr(u32)]
14/// Possible operations the kernel can perform when looking at the supplies reference and the
15/// supplied value. If the operation `*reference OP value` evaluates to true (or false if the INVERT
16/// flag is passed), then the thread is put
17/// to sleep.
18pub enum ThreadSyncOp {
19    /// Compare for equality
20    Equal = 0,
21}
22
23impl ThreadSyncOp {
24    /// Apply the operation to two values, returning the result.
25    pub fn check<T: Eq + PartialEq + Ord + PartialOrd>(
26        &self,
27        a: T,
28        b: T,
29        flags: ThreadSyncFlags,
30    ) -> bool {
31        let res = match self {
32            Self::Equal => a == b,
33        };
34
35        if flags.contains(ThreadSyncFlags::INVERT) {
36            !res
37        } else {
38            res
39        }
40    }
41}
42
43bitflags! {
44    /// Flags to pass to sys_thread_sync.
45    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
46    pub struct ThreadSyncFlags: u32 {
47        /// Invert the decision test for sleeping the thread.
48        const INVERT = 1;
49    }
50}
51
52#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
53#[repr(C)]
54/// A reference to a piece of data. May either be a non-realized persistent reference or a virtual
55/// address.
56pub enum ThreadSyncReference {
57    ObjectRef(ObjID, usize),
58    Virtual(*const AtomicU64),
59    Virtual32(*const AtomicU32),
60}
61unsafe impl Send for ThreadSyncReference {}
62
63impl ThreadSyncReference {
64    pub fn load(&self) -> u64 {
65        match self {
66            ThreadSyncReference::ObjectRef(_, _) => todo!(),
67            ThreadSyncReference::Virtual(p) => {
68                unsafe { &**p }.load(core::sync::atomic::Ordering::SeqCst)
69            }
70            ThreadSyncReference::Virtual32(p) => unsafe { &**p }
71                .load(core::sync::atomic::Ordering::SeqCst)
72                .into(),
73        }
74    }
75}
76
77#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
78#[repr(C)]
79/// Specification for a thread sleep request.
80pub struct ThreadSyncSleep {
81    /// Reference to an atomic u64 that we will compare to.
82    pub reference: ThreadSyncReference,
83    /// The value used for the comparison.
84    pub value: u64,
85    /// The operation to compare *reference and value to.
86    pub op: ThreadSyncOp,
87    /// Flags to apply to this sleep request.
88    pub flags: ThreadSyncFlags,
89}
90
91#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq, Hash)]
92#[repr(C)]
93/// Specification for a thread wake request.
94pub struct ThreadSyncWake {
95    /// Reference to the word for which we will wake up threads that have gone to sleep.
96    pub reference: ThreadSyncReference,
97    /// Number of threads to wake up.
98    pub count: usize,
99}
100
101impl ThreadSyncSleep {
102    /// Construct a new thread sync sleep request. The kernel will compare the 64-bit data at
103    /// `*reference` with the passed `value` using `op` (and optionally inverting the result). If
104    /// true, the kernel will put the thread to sleep until another thread comes along and performs
105    /// a wake request on that same word of memory.
106    ///
107    /// References always refer to a particular 64-bit value inside of an object. If a virtual
108    /// address is passed as a reference, it is first converted to an object-offset pair based on
109    /// the current object mapping of the address space.
110    pub fn new(
111        reference: ThreadSyncReference,
112        value: u64,
113        op: ThreadSyncOp,
114        flags: ThreadSyncFlags,
115    ) -> Self {
116        Self {
117            reference,
118            value,
119            op,
120            flags,
121        }
122    }
123
124    pub fn ready(&self) -> bool {
125        let st = self.reference.load();
126        match self.op {
127            ThreadSyncOp::Equal => st != self.value,
128        }
129    }
130}
131
132impl ThreadSyncWake {
133    /// Construct a new thread wake request. The reference works the same was as in
134    /// [ThreadSyncSleep]. The kernel will wake up `count` threads that are sleeping on this
135    /// particular word of object memory. If you want to wake up all threads, you can supply
136    /// `usize::MAX`.
137    pub fn new(reference: ThreadSyncReference, count: usize) -> Self {
138        Self { reference, count }
139    }
140}
141
142/// Result of sync operations.
143pub type ThreadSyncResult = Result<usize, TwzError>;
144
145#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Ord, Eq)]
146#[repr(C)]
147/// Either a sleep or wake request. The syscall comprises of a number of either sleep or wake
148/// requests.
149pub enum ThreadSync {
150    Sleep(ThreadSyncSleep, ThreadSyncResult),
151    Wake(ThreadSyncWake, ThreadSyncResult),
152}
153
154impl ThreadSync {
155    /// Build a sleep request.
156    pub fn new_sleep(sleep: ThreadSyncSleep) -> Self {
157        Self::Sleep(sleep, Ok(0))
158    }
159
160    /// Build a wake request.
161    pub fn new_wake(wake: ThreadSyncWake) -> Self {
162        Self::Wake(wake, Ok(0))
163    }
164
165    /// Get the result of the thread sync operation.
166    pub fn get_result(&self) -> ThreadSyncResult {
167        match self {
168            ThreadSync::Sleep(_, e) => *e,
169            ThreadSync::Wake(_, e) => *e,
170        }
171    }
172
173    pub fn ready(&self) -> bool {
174        match self {
175            ThreadSync::Sleep(o, _) => o.ready(),
176            ThreadSync::Wake(_, _) => true,
177        }
178    }
179}
180
181/// Perform a number of [ThreadSync] operations, either waking of threads waiting on words of
182/// memory, or sleeping this thread on one or more words of memory (or both). The order these
183/// requests are processed in is undefined.
184///
185/// The caller may optionally specify a timeout, causing this thread to sleep for at-most that
186/// Duration. However, the exact time is not guaranteed (it may be less if the thread is woken up,
187/// or slightly more due to scheduling uncertainty). If no operations are specified, the thread will
188/// sleep until the timeout expires.
189///
190/// Returns either Ok(ready_count), indicating how many operations were immediately ready, or
191/// Err([TwzError]), indicating failure. After return, the kernel may have modified the
192/// ThreadSync entries to indicate additional information about each request, with Err to indicate
193/// error and Ok(n) to indicate success. For sleep requests, n is 0 if the operation went to sleep
194/// or 1 otherwise. For wakeup requests, n indicates the number of threads woken up by this
195/// operation.
196///
197/// Note that spurious wakeups are possible, and that even if a timeout occurs the function may
198/// return Ok(0).
199pub fn sys_thread_sync(
200    operations: &mut [ThreadSync],
201    timeout: Option<Duration>,
202) -> Result<usize, TwzError> {
203    let ptr = operations.as_mut_ptr();
204    let count = operations.len();
205    let timeout = timeout
206        .as_ref()
207        .map_or(ptr::null(), |t| t as *const Duration);
208
209    let (code, val) = unsafe {
210        raw_syscall(
211            Syscall::ThreadSync,
212            &[ptr as u64, count as u64, timeout as u64],
213        )
214    };
215    convert_codes_to_result(code, val, |c, _| c != 0, |_, v| v as usize, twzerr)
216}