| use crate::os::xous::ffi::{blocking_scalar, scalar}; | |
| use crate::os::xous::services::{TicktimerScalar, ticktimer_server}; | |
| use crate::pin::Pin; | |
| use crate::ptr; | |
| use crate::sync::atomic::Ordering::{Acquire, Release}; | |
| use crate::sync::atomic::{Atomic, AtomicI8}; | |
| use crate::time::Duration; | |
| const NOTIFIED: i8 = 1; | |
| const EMPTY: i8 = 0; | |
| const PARKED: i8 = -1; | |
| pub struct Parker { | |
| state: Atomic<i8>, | |
| } | |
| impl Parker { | |
| pub unsafe fn new_in_place(parker: *mut Parker) { | |
| unsafe { parker.write(Parker { state: AtomicI8::new(EMPTY) }) } | |
| } | |
| fn index(&self) -> usize { | |
| ptr::from_ref(self).addr() | |
| } | |
| pub unsafe fn park(self: Pin<&Self>) { | |
| // Change NOTIFIED to EMPTY and EMPTY to PARKED. | |
| let state = self.state.fetch_sub(1, Acquire); | |
| if state == NOTIFIED { | |
| // The state has gone from NOTIFIED (1) to EMPTY (0) | |
| return; | |
| } | |
| // The state has gone from EMPTY (0) to PARKED (-1) | |
| assert!(state == EMPTY); | |
| // The state is now PARKED (-1). Wait until the `unpark` wakes us up. | |
| blocking_scalar( | |
| ticktimer_server(), | |
| TicktimerScalar::WaitForCondition(self.index(), 0).into(), | |
| ) | |
| .expect("failed to send WaitForCondition command"); | |
| let state = self.state.swap(EMPTY, Acquire); | |
| assert!(state == NOTIFIED || state == PARKED); | |
| } | |
| pub unsafe fn park_timeout(self: Pin<&Self>, timeout: Duration) { | |
| // Change NOTIFIED to EMPTY and EMPTY to PARKED. | |
| let state = self.state.fetch_sub(1, Acquire); | |
| if state == NOTIFIED { | |
| // The state has gone from NOTIFIED (1) to EMPTY (0) | |
| return; | |
| } | |
| // The state has gone from EMPTY (0) to PARKED (-1) | |
| assert!(state == EMPTY); | |
| // A value of zero indicates an indefinite wait. Clamp the number of | |
| // milliseconds to the allowed range. | |
| let millis = usize::max(timeout.as_millis().try_into().unwrap_or(usize::MAX), 1); | |
| // The state is now PARKED (-1). Wait until the `unpark` wakes us up, | |
| // or things time out. | |
| let _was_timeout = blocking_scalar( | |
| ticktimer_server(), | |
| TicktimerScalar::WaitForCondition(self.index(), millis).into(), | |
| ) | |
| .expect("failed to send WaitForCondition command")[0] | |
| != 0; | |
| let state = self.state.swap(EMPTY, Acquire); | |
| assert!(state == PARKED || state == NOTIFIED); | |
| } | |
| pub fn unpark(self: Pin<&Self>) { | |
| // If the state is already `NOTIFIED`, then another thread has | |
| // indicated it wants to wake up the target thread. | |
| // | |
| // If the state is `EMPTY` then there is nothing to wake up, and | |
| // the target thread will immediately exit from `park()` the | |
| // next time that function is called. | |
| if self.state.swap(NOTIFIED, Release) != PARKED { | |
| return; | |
| } | |
| // The thread is parked, wake it up. Keep trying until we wake something up. | |
| // This will happen when the `NotifyCondition` call returns the fact that | |
| // 1 condition was notified. | |
| // Alternately, keep going until the state is seen as `EMPTY`, indicating | |
| // the thread woke up and kept going. This can happen when the Park | |
| // times out before we can send the NotifyCondition message. | |
| while blocking_scalar( | |
| ticktimer_server(), | |
| TicktimerScalar::NotifyCondition(self.index(), 1).into(), | |
| ) | |
| .expect("failed to send NotifyCondition command")[0] | |
| == 0 | |
| && self.state.load(Acquire) != EMPTY | |
| { | |
| // The target thread hasn't yet hit the `WaitForCondition` call. | |
| // Yield to let the target thread run some more. | |
| crate::thread::yield_now(); | |
| } | |
| } | |
| } | |
| impl Drop for Parker { | |
| fn drop(&mut self) { | |
| scalar(ticktimer_server(), TicktimerScalar::FreeCondition(self.index()).into()).ok(); | |
| } | |
| } |