Skip to content

Commit 6413844

Browse files
committed
std: xous: add support for locks
Add support for Condvar, Mutex, and RWLock. These are all backed by the ticktimer server. Signed-off-by: Sean Cross <[email protected]>
1 parent 46a4ec3 commit 6413844

File tree

6 files changed

+310
-1
lines changed

6 files changed

+310
-1
lines changed

library/std/src/os/xous/services/ticktimer.rs

+4
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ pub(crate) enum TicktimerScalar {
88
UnlockMutex(usize /* cookie */),
99
WaitForCondition(usize /* cookie */, usize /* timeout (ms) */),
1010
NotifyCondition(usize /* cookie */, usize /* count */),
11+
FreeMutex(usize /* cookie */),
12+
FreeCondition(usize /* cookie */),
1113
}
1214

1315
impl Into<[usize; 5]> for TicktimerScalar {
@@ -19,6 +21,8 @@ impl Into<[usize; 5]> for TicktimerScalar {
1921
TicktimerScalar::UnlockMutex(cookie) => [7, cookie, 0, 0, 0],
2022
TicktimerScalar::WaitForCondition(cookie, timeout_ms) => [8, cookie, timeout_ms, 0, 0],
2123
TicktimerScalar::NotifyCondition(cookie, count) => [9, cookie, count, 0, 0],
24+
TicktimerScalar::FreeMutex(cookie) => [10, cookie, 0, 0, 0],
25+
TicktimerScalar::FreeCondition(cookie) => [11, cookie, 0, 0, 0],
2226
}
2327
}
2428
}
+111
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
use super::mutex::Mutex;
2+
use crate::os::xous::ffi::{blocking_scalar, scalar};
3+
use crate::os::xous::services::ticktimer_server;
4+
use crate::sync::Mutex as StdMutex;
5+
use crate::time::Duration;
6+
7+
// The implementation is inspired by Andrew D. Birrell's paper
8+
// "Implementing Condition Variables with Semaphores"
9+
10+
pub struct Condvar {
11+
counter: StdMutex<usize>,
12+
}
13+
14+
unsafe impl Send for Condvar {}
15+
unsafe impl Sync for Condvar {}
16+
17+
impl Condvar {
18+
#[inline]
19+
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
20+
pub const fn new() -> Condvar {
21+
Condvar { counter: StdMutex::new(0) }
22+
}
23+
24+
pub fn notify_one(&self) {
25+
let mut counter = self.counter.lock().unwrap();
26+
if *counter <= 0 {
27+
return;
28+
} else {
29+
*counter -= 1;
30+
}
31+
let result = blocking_scalar(
32+
ticktimer_server(),
33+
crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), 1).into(),
34+
);
35+
drop(counter);
36+
result.expect("failure to send NotifyCondition command");
37+
}
38+
39+
pub fn notify_all(&self) {
40+
let mut counter = self.counter.lock().unwrap();
41+
if *counter <= 0 {
42+
return;
43+
}
44+
let result = blocking_scalar(
45+
ticktimer_server(),
46+
crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), *counter)
47+
.into(),
48+
);
49+
*counter = 0;
50+
drop(counter);
51+
52+
result.expect("failure to send NotifyCondition command");
53+
}
54+
55+
fn index(&self) -> usize {
56+
self as *const Condvar as usize
57+
}
58+
59+
pub unsafe fn wait(&self, mutex: &Mutex) {
60+
let mut counter = self.counter.lock().unwrap();
61+
*counter += 1;
62+
unsafe { mutex.unlock() };
63+
drop(counter);
64+
65+
let result = blocking_scalar(
66+
ticktimer_server(),
67+
crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), 0).into(),
68+
);
69+
unsafe { mutex.lock() };
70+
71+
result.expect("Ticktimer: failure to send WaitForCondition command");
72+
}
73+
74+
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
75+
let mut counter = self.counter.lock().unwrap();
76+
*counter += 1;
77+
unsafe { mutex.unlock() };
78+
drop(counter);
79+
80+
let mut millis = dur.as_millis() as usize;
81+
if millis == 0 {
82+
millis = 1;
83+
}
84+
85+
let result = blocking_scalar(
86+
ticktimer_server(),
87+
crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), millis)
88+
.into(),
89+
);
90+
unsafe { mutex.lock() };
91+
92+
let result = result.expect("Ticktimer: failure to send WaitForCondition command")[0] == 0;
93+
94+
// If we awoke due to a timeout, decrement the wake count, as that would not have
95+
// been done in the `notify()` call.
96+
if !result {
97+
*self.counter.lock().unwrap() -= 1;
98+
}
99+
result
100+
}
101+
}
102+
103+
impl Drop for Condvar {
104+
fn drop(&mut self) {
105+
scalar(
106+
ticktimer_server(),
107+
crate::os::xous::services::TicktimerScalar::FreeCondition(self.index()).into(),
108+
)
109+
.ok();
110+
}
111+
}

library/std/src/sys/xous/locks/mod.rs

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
mod condvar;
2+
mod mutex;
3+
mod rwlock;
4+
5+
pub use condvar::*;
6+
pub use mutex::*;
7+
pub use rwlock::*;
+116
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
use crate::os::xous::ffi::{blocking_scalar, do_yield, scalar};
2+
use crate::os::xous::services::ticktimer_server;
3+
use crate::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed, Ordering::SeqCst};
4+
5+
pub struct Mutex {
6+
/// The "locked" value indicates how many threads are waiting on this
7+
/// Mutex. Possible values are:
8+
/// 0: The lock is unlocked
9+
/// 1: The lock is locked and uncontended
10+
/// >=2: The lock is locked and contended
11+
///
12+
/// A lock is "contended" when there is more than one thread waiting
13+
/// for a lock, or it is locked for long periods of time. Rather than
14+
/// spinning, these locks send a Message to the ticktimer server
15+
/// requesting that they be woken up when a lock is unlocked.
16+
locked: AtomicUsize,
17+
18+
/// Whether this Mutex ever was contended, and therefore made a trip
19+
/// to the ticktimer server. If this was never set, then we were never
20+
/// on the slow path and can skip deregistering the mutex.
21+
contended: AtomicBool,
22+
}
23+
24+
impl Mutex {
25+
#[inline]
26+
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
27+
pub const fn new() -> Mutex {
28+
Mutex { locked: AtomicUsize::new(0), contended: AtomicBool::new(false) }
29+
}
30+
31+
fn index(&self) -> usize {
32+
self as *const Mutex as usize
33+
}
34+
35+
#[inline]
36+
pub unsafe fn lock(&self) {
37+
// Try multiple times to acquire the lock without resorting to the ticktimer
38+
// server. For locks that are held for a short amount of time, this will
39+
// result in the ticktimer server never getting invoked. The `locked` value
40+
// will be either 0 or 1.
41+
for _attempts in 0..3 {
42+
if unsafe { self.try_lock() } {
43+
return;
44+
}
45+
do_yield();
46+
}
47+
48+
// Try one more time to lock. If the lock is released between the previous code and
49+
// here, then the inner `locked` value will be 1 at the end of this. If it was not
50+
// locked, then the value will be more than 1, for example if there are multiple other
51+
// threads waiting on this lock.
52+
if unsafe { self.try_lock_or_poison() } {
53+
return;
54+
}
55+
56+
// When this mutex is dropped, we will need to deregister it with the server.
57+
self.contended.store(true, Relaxed);
58+
59+
// The lock is now "contended". When the lock is released, a Message will get sent to the
60+
// ticktimer server to wake it up. Note that this may already have happened, so the actual
61+
// value of `lock` may be anything (0, 1, 2, ...).
62+
blocking_scalar(
63+
ticktimer_server(),
64+
crate::os::xous::services::TicktimerScalar::LockMutex(self.index()).into(),
65+
)
66+
.expect("failure to send LockMutex command");
67+
}
68+
69+
#[inline]
70+
pub unsafe fn unlock(&self) {
71+
let prev = self.locked.fetch_sub(1, SeqCst);
72+
73+
// If the previous value was 1, then this was a "fast path" unlock, so no
74+
// need to involve the Ticktimer server
75+
if prev == 1 {
76+
return;
77+
}
78+
79+
// If it was 0, then something has gone seriously wrong and the counter
80+
// has just wrapped around.
81+
if prev == 0 {
82+
panic!("mutex lock count underflowed");
83+
}
84+
85+
// Unblock one thread that is waiting on this message.
86+
scalar(
87+
ticktimer_server(),
88+
crate::os::xous::services::TicktimerScalar::UnlockMutex(self.index()).into(),
89+
)
90+
.expect("failure to send UnlockMutex command");
91+
}
92+
93+
#[inline]
94+
pub unsafe fn try_lock(&self) -> bool {
95+
self.locked.compare_exchange(0, 1, SeqCst, SeqCst).is_ok()
96+
}
97+
98+
#[inline]
99+
pub unsafe fn try_lock_or_poison(&self) -> bool {
100+
self.locked.fetch_add(1, SeqCst) == 0
101+
}
102+
}
103+
104+
impl Drop for Mutex {
105+
fn drop(&mut self) {
106+
// If there was Mutex contention, then we involved the ticktimer. Free
107+
// the resources associated with this Mutex as it is deallocated.
108+
if self.contended.load(Relaxed) {
109+
scalar(
110+
ticktimer_server(),
111+
crate::os::xous::services::TicktimerScalar::FreeMutex(self.index()).into(),
112+
)
113+
.ok();
114+
}
115+
}
116+
}
+72
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
use crate::os::xous::ffi::do_yield;
2+
use crate::sync::atomic::{AtomicIsize, Ordering::SeqCst};
3+
4+
pub struct RwLock {
5+
/// The "mode" value indicates how many threads are waiting on this
6+
/// Mutex. Possible values are:
7+
/// -1: The lock is locked for writing
8+
/// 0: The lock is unlocked
9+
/// >=1: The lock is locked for reading
10+
///
11+
/// This currently spins waiting for the lock to be freed. An
12+
/// optimization would be to involve the ticktimer server to
13+
/// coordinate unlocks.
14+
mode: AtomicIsize,
15+
}
16+
17+
unsafe impl Send for RwLock {}
18+
unsafe impl Sync for RwLock {}
19+
20+
impl RwLock {
21+
#[inline]
22+
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
23+
pub const fn new() -> RwLock {
24+
RwLock { mode: AtomicIsize::new(0) }
25+
}
26+
27+
#[inline]
28+
pub unsafe fn read(&self) {
29+
while !unsafe { self.try_read() } {
30+
do_yield();
31+
}
32+
}
33+
34+
#[inline]
35+
pub unsafe fn try_read(&self) -> bool {
36+
// Non-atomically determine the current value.
37+
let current = self.mode.load(SeqCst);
38+
39+
// If it's currently locked for writing, then we cannot read.
40+
if current < 0 {
41+
return false;
42+
}
43+
44+
// Attempt to lock. If the `current` value has changed, then this
45+
// operation will fail and we will not obtain the lock even if we
46+
// could potentially keep it.
47+
let new = current + 1;
48+
self.mode.compare_exchange(current, new, SeqCst, SeqCst).is_ok()
49+
}
50+
51+
#[inline]
52+
pub unsafe fn write(&self) {
53+
while !unsafe { self.try_write() } {
54+
do_yield();
55+
}
56+
}
57+
58+
#[inline]
59+
pub unsafe fn try_write(&self) -> bool {
60+
self.mode.compare_exchange(0, -1, SeqCst, SeqCst).is_ok()
61+
}
62+
63+
#[inline]
64+
pub unsafe fn read_unlock(&self) {
65+
self.mode.fetch_sub(1, SeqCst);
66+
}
67+
68+
#[inline]
69+
pub unsafe fn write_unlock(&self) {
70+
assert_eq!(self.mode.compare_exchange(-1, 0, SeqCst, SeqCst), Ok(-1));
71+
}
72+
}

library/std/src/sys/xous/mod.rs

-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,6 @@ pub mod env;
1111
pub mod fs;
1212
#[path = "../unsupported/io.rs"]
1313
pub mod io;
14-
#[path = "../unsupported/locks/mod.rs"]
1514
pub mod locks;
1615
#[path = "../unsupported/net.rs"]
1716
pub mod net;

0 commit comments

Comments
 (0)