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}