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