Skip to content

Commit e0da598

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 d7c82e8 commit e0da598

File tree

5 files changed

+235
-1
lines changed

5 files changed

+235
-1
lines changed
+65
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
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::atomic::{AtomicUsize, Ordering::SeqCst};
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: AtomicUsize,
12+
}
13+
14+
pub(crate) type MovableCondvar = Condvar;
15+
16+
unsafe impl Send for Condvar {}
17+
unsafe impl Sync for Condvar {}
18+
19+
impl Condvar {
20+
#[inline]
21+
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
22+
pub const fn new() -> Condvar {
23+
Condvar { counter: AtomicUsize::new(0) }
24+
}
25+
26+
pub unsafe fn notify_one(&self) {
27+
if self.counter.load(SeqCst) > 0 {
28+
self.counter.fetch_sub(1, SeqCst);
29+
scalar(ticktimer_server(), [9 /* NotifyCondition */, self.index(), 1, 0, 0])
30+
.expect("failure to send NotifyCondition command");
31+
}
32+
}
33+
34+
pub unsafe fn notify_all(&self) {
35+
let counter = self.counter.swap(0, SeqCst);
36+
scalar(ticktimer_server(), [9 /* NotifyCondition */, self.index(), counter, 0, 0])
37+
.expect("failure to send NotifyCondition command");
38+
}
39+
40+
fn index(&self) -> usize {
41+
self as *const Condvar as usize
42+
}
43+
44+
pub unsafe fn wait(&self, mutex: &Mutex) {
45+
self.counter.fetch_add(1, SeqCst);
46+
unsafe { mutex.unlock() };
47+
blocking_scalar(ticktimer_server(), [8 /* WaitForCondition */, self.index(), 0, 0, 0])
48+
.expect("Ticktimer: failure to send WaitForCondition command");
49+
unsafe { mutex.lock() };
50+
}
51+
52+
pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
53+
self.counter.fetch_add(1, SeqCst);
54+
unsafe { mutex.unlock() };
55+
let millis = dur.as_millis() as usize;
56+
let result = blocking_scalar(
57+
ticktimer_server(),
58+
[8 /* WaitForCondition */, self.index(), millis, 0, 0],
59+
)
60+
.expect("Ticktimer: failure to send WaitForCondition command");
61+
unsafe { mutex.lock() };
62+
63+
result[0] == 0
64+
}
65+
}

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::*;
+89
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
use crate::os::xous::ffi::{blocking_scalar, do_yield, scalar};
2+
use crate::os::xous::services::ticktimer_server;
3+
use crate::sync::atomic::{AtomicUsize, 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+
19+
pub type MovableMutex = Mutex;
20+
21+
impl Mutex {
22+
#[inline]
23+
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
24+
pub const fn new() -> Mutex {
25+
Mutex { locked: AtomicUsize::new(0) }
26+
}
27+
28+
#[inline]
29+
pub unsafe fn lock(&self) {
30+
// Try multiple times to acquire the lock without resorting to the ticktimer
31+
// server. For locks that are held for a short amount of time, this will
32+
// result in the ticktimer server never getting invoked. The `locked` value
33+
// will be either 0 or 1.
34+
for _attempts in 0..3 {
35+
if unsafe { self.try_lock() } {
36+
return;
37+
}
38+
do_yield();
39+
}
40+
41+
// Try one more time to lock. If the lock is released between the previous code and
42+
// here, then the inner `locked` value will be 1 at the end of this. If it was not
43+
// locked, then the value will be more than 1, for example if there are multiple other
44+
// threads waiting on this lock.
45+
if unsafe { self.try_lock_or_poison() } {
46+
return;
47+
}
48+
49+
// The lock is now "contended". When the lock is released, a Message will get sent to the
50+
// ticktimer server to wake it up. Note that this may already have happened, so the actual
51+
// value of `lock` may be anything (0, 1, 2, ...).
52+
blocking_scalar(
53+
ticktimer_server(),
54+
[6 /* LockMutex */, self as *const Mutex as usize, 0, 0, 0],
55+
)
56+
.expect("failure to send LockMutex command");
57+
}
58+
59+
#[inline]
60+
pub unsafe fn unlock(&self) {
61+
let prev = self.locked.fetch_sub(1, SeqCst);
62+
63+
// If the previous value was 1, then this was a "fast path" unlock, so no
64+
// need to involve the Ticktimer server
65+
if prev == 1 {
66+
return;
67+
}
68+
69+
// If it was 0, then something has gone seriously wrong and the counter
70+
// has just wrapped around.
71+
if prev == 0 {
72+
panic!("mutex lock count underflowed");
73+
}
74+
75+
// Unblock one thread that is waiting on this message.
76+
scalar(ticktimer_server(), [7 /* UnlockMutex */, self as *const Mutex as usize, 0, 0, 0])
77+
.expect("failure to send UnlockMutex command");
78+
}
79+
80+
#[inline]
81+
pub unsafe fn try_lock(&self) -> bool {
82+
self.locked.compare_exchange(0, 1, SeqCst, SeqCst).is_ok()
83+
}
84+
85+
#[inline]
86+
pub unsafe fn try_lock_or_poison(&self) -> bool {
87+
self.locked.fetch_add(1, SeqCst) == 0
88+
}
89+
}
+74
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
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+
pub type MovableRwLock = RwLock;
18+
19+
unsafe impl Send for RwLock {}
20+
unsafe impl Sync for RwLock {}
21+
22+
impl RwLock {
23+
#[inline]
24+
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
25+
pub const fn new() -> RwLock {
26+
RwLock { mode: AtomicIsize::new(0) }
27+
}
28+
29+
#[inline]
30+
pub unsafe fn read(&self) {
31+
while !unsafe { self.try_read() } {
32+
do_yield();
33+
}
34+
}
35+
36+
#[inline]
37+
pub unsafe fn try_read(&self) -> bool {
38+
// Non-atomically determine the current value.
39+
let current = self.mode.load(SeqCst);
40+
41+
// If it's currently locked for writing, then we cannot read.
42+
if current < 0 {
43+
return false;
44+
}
45+
46+
// Attempt to lock. If the `current` value has changed, then this
47+
// operation will fail and we will not obtain the lock even if we
48+
// could potentially keep it.
49+
let new = current + 1;
50+
self.mode.compare_exchange(new, current, SeqCst, SeqCst).is_ok()
51+
}
52+
53+
#[inline]
54+
pub unsafe fn write(&self) {
55+
while !unsafe { self.try_write() } {
56+
do_yield();
57+
}
58+
}
59+
60+
#[inline]
61+
pub unsafe fn try_write(&self) -> bool {
62+
self.mode.compare_exchange(0, -1, SeqCst, SeqCst).is_ok()
63+
}
64+
65+
#[inline]
66+
pub unsafe fn read_unlock(&self) {
67+
self.mode.fetch_sub(1, SeqCst);
68+
}
69+
70+
#[inline]
71+
pub unsafe fn write_unlock(&self) {
72+
assert_eq!(self.mode.compare_exchange(-1, 0, SeqCst, SeqCst), Ok(-1));
73+
}
74+
}

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)