summaryrefslogtreecommitdiffstats
path: root/library/std/src/sys/xous/thread_parking.rs
blob: aa39c6d27185f318317b917faf355148828f5da5 (plain)
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
use crate::os::xous::ffi::{blocking_scalar, scalar};
use crate::os::xous::services::{ticktimer_server, TicktimerScalar};
use crate::pin::Pin;
use crate::ptr;
use crate::sync::atomic::{
    AtomicI8,
    Ordering::{Acquire, Release},
};
use crate::time::Duration;

const NOTIFIED: i8 = 1;
const EMPTY: i8 = 0;
const PARKED: i8 = -1;

pub struct Parker {
    state: AtomicI8,
}

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 {
            return;
        }

        // The state was set to PARKED. Wait until the `unpark` wakes us up.
        blocking_scalar(
            ticktimer_server(),
            TicktimerScalar::WaitForCondition(self.index(), 0).into(),
        )
        .expect("failed to send WaitForCondition command");

        self.state.swap(EMPTY, Acquire);
    }

    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 {
            return;
        }

        // 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);

        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);
        if was_timeout && state == NOTIFIED {
            // The state was set to NOTIFIED after we returned from the wait
            // but before we reset the state. Therefore, a wakeup is on its
            // way, which we need to consume here.
            // NOTICE: this is a priority hole.
            blocking_scalar(
                ticktimer_server(),
                TicktimerScalar::WaitForCondition(self.index(), 0).into(),
            )
            .expect("failed to send WaitForCondition command");
        }
    }

    pub fn unpark(self: Pin<&Self>) {
        let state = self.state.swap(NOTIFIED, Release);
        if state == PARKED {
            // The thread is parked, wake it up.
            blocking_scalar(
                ticktimer_server(),
                TicktimerScalar::NotifyCondition(self.index(), 1).into(),
            )
            .expect("failed to send NotifyCondition command");
        }
    }
}

impl Drop for Parker {
    fn drop(&mut self) {
        scalar(ticktimer_server(), TicktimerScalar::FreeCondition(self.index()).into()).ok();
    }
}