Skip to content

Commit

Permalink
std: xous: add support for locks
Browse files Browse the repository at this point in the history
Add support for Condvar, Mutex, and RWLock. These are all backed by the
ticktimer server.

Signed-off-by: Sean Cross <[email protected]>
  • Loading branch information
xobs committed Aug 22, 2023
1 parent 46a4ec3 commit 6413844
Show file tree
Hide file tree
Showing 6 changed files with 310 additions and 1 deletion.
4 changes: 4 additions & 0 deletions library/std/src/os/xous/services/ticktimer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ pub(crate) enum TicktimerScalar {
UnlockMutex(usize /* cookie */),
WaitForCondition(usize /* cookie */, usize /* timeout (ms) */),
NotifyCondition(usize /* cookie */, usize /* count */),
FreeMutex(usize /* cookie */),
FreeCondition(usize /* cookie */),
}

impl Into<[usize; 5]> for TicktimerScalar {
Expand All @@ -19,6 +21,8 @@ impl Into<[usize; 5]> for TicktimerScalar {
TicktimerScalar::UnlockMutex(cookie) => [7, cookie, 0, 0, 0],
TicktimerScalar::WaitForCondition(cookie, timeout_ms) => [8, cookie, timeout_ms, 0, 0],
TicktimerScalar::NotifyCondition(cookie, count) => [9, cookie, count, 0, 0],
TicktimerScalar::FreeMutex(cookie) => [10, cookie, 0, 0, 0],
TicktimerScalar::FreeCondition(cookie) => [11, cookie, 0, 0, 0],
}
}
}
Expand Down
111 changes: 111 additions & 0 deletions library/std/src/sys/xous/locks/condvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
use super::mutex::Mutex;
use crate::os::xous::ffi::{blocking_scalar, scalar};
use crate::os::xous::services::ticktimer_server;
use crate::sync::Mutex as StdMutex;
use crate::time::Duration;

// The implementation is inspired by Andrew D. Birrell's paper
// "Implementing Condition Variables with Semaphores"

pub struct Condvar {
counter: StdMutex<usize>,
}

unsafe impl Send for Condvar {}
unsafe impl Sync for Condvar {}

impl Condvar {
#[inline]
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
pub const fn new() -> Condvar {
Condvar { counter: StdMutex::new(0) }
}

pub fn notify_one(&self) {
let mut counter = self.counter.lock().unwrap();
if *counter <= 0 {
return;
} else {
*counter -= 1;
}
let result = blocking_scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), 1).into(),
);
drop(counter);
result.expect("failure to send NotifyCondition command");
}

pub fn notify_all(&self) {
let mut counter = self.counter.lock().unwrap();
if *counter <= 0 {
return;
}
let result = blocking_scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::NotifyCondition(self.index(), *counter)
.into(),
);
*counter = 0;
drop(counter);

result.expect("failure to send NotifyCondition command");
}

fn index(&self) -> usize {
self as *const Condvar as usize
}

pub unsafe fn wait(&self, mutex: &Mutex) {
let mut counter = self.counter.lock().unwrap();
*counter += 1;
unsafe { mutex.unlock() };
drop(counter);

let result = blocking_scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), 0).into(),
);
unsafe { mutex.lock() };

result.expect("Ticktimer: failure to send WaitForCondition command");
}

pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
let mut counter = self.counter.lock().unwrap();
*counter += 1;
unsafe { mutex.unlock() };
drop(counter);

let mut millis = dur.as_millis() as usize;
if millis == 0 {
millis = 1;
}

let result = blocking_scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::WaitForCondition(self.index(), millis)
.into(),
);
unsafe { mutex.lock() };

let result = result.expect("Ticktimer: failure to send WaitForCondition command")[0] == 0;

// If we awoke due to a timeout, decrement the wake count, as that would not have
// been done in the `notify()` call.
if !result {
*self.counter.lock().unwrap() -= 1;
}
result
}
}

impl Drop for Condvar {
fn drop(&mut self) {
scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::FreeCondition(self.index()).into(),
)
.ok();
}
}
7 changes: 7 additions & 0 deletions library/std/src/sys/xous/locks/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
mod condvar;
mod mutex;
mod rwlock;

pub use condvar::*;
pub use mutex::*;
pub use rwlock::*;
116 changes: 116 additions & 0 deletions library/std/src/sys/xous/locks/mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use crate::os::xous::ffi::{blocking_scalar, do_yield, scalar};
use crate::os::xous::services::ticktimer_server;
use crate::sync::atomic::{AtomicBool, AtomicUsize, Ordering::Relaxed, Ordering::SeqCst};

pub struct Mutex {
/// The "locked" value indicates how many threads are waiting on this
/// Mutex. Possible values are:
/// 0: The lock is unlocked
/// 1: The lock is locked and uncontended
/// >=2: The lock is locked and contended
///
/// A lock is "contended" when there is more than one thread waiting
/// for a lock, or it is locked for long periods of time. Rather than
/// spinning, these locks send a Message to the ticktimer server
/// requesting that they be woken up when a lock is unlocked.
locked: AtomicUsize,

/// Whether this Mutex ever was contended, and therefore made a trip
/// to the ticktimer server. If this was never set, then we were never
/// on the slow path and can skip deregistering the mutex.
contended: AtomicBool,
}

impl Mutex {
#[inline]
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
pub const fn new() -> Mutex {
Mutex { locked: AtomicUsize::new(0), contended: AtomicBool::new(false) }
}

fn index(&self) -> usize {
self as *const Mutex as usize
}

#[inline]
pub unsafe fn lock(&self) {
// Try multiple times to acquire the lock without resorting to the ticktimer
// server. For locks that are held for a short amount of time, this will
// result in the ticktimer server never getting invoked. The `locked` value
// will be either 0 or 1.
for _attempts in 0..3 {
if unsafe { self.try_lock() } {
return;
}
do_yield();
}

// Try one more time to lock. If the lock is released between the previous code and
// here, then the inner `locked` value will be 1 at the end of this. If it was not
// locked, then the value will be more than 1, for example if there are multiple other
// threads waiting on this lock.
if unsafe { self.try_lock_or_poison() } {
return;
}

// When this mutex is dropped, we will need to deregister it with the server.
self.contended.store(true, Relaxed);

// The lock is now "contended". When the lock is released, a Message will get sent to the
// ticktimer server to wake it up. Note that this may already have happened, so the actual
// value of `lock` may be anything (0, 1, 2, ...).
blocking_scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::LockMutex(self.index()).into(),
)
.expect("failure to send LockMutex command");
}

#[inline]
pub unsafe fn unlock(&self) {
let prev = self.locked.fetch_sub(1, SeqCst);

// If the previous value was 1, then this was a "fast path" unlock, so no
// need to involve the Ticktimer server
if prev == 1 {
return;
}

// If it was 0, then something has gone seriously wrong and the counter
// has just wrapped around.
if prev == 0 {
panic!("mutex lock count underflowed");
}

// Unblock one thread that is waiting on this message.
scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::UnlockMutex(self.index()).into(),
)
.expect("failure to send UnlockMutex command");
}

#[inline]
pub unsafe fn try_lock(&self) -> bool {
self.locked.compare_exchange(0, 1, SeqCst, SeqCst).is_ok()
}

#[inline]
pub unsafe fn try_lock_or_poison(&self) -> bool {
self.locked.fetch_add(1, SeqCst) == 0
}
}

impl Drop for Mutex {
fn drop(&mut self) {
// If there was Mutex contention, then we involved the ticktimer. Free
// the resources associated with this Mutex as it is deallocated.
if self.contended.load(Relaxed) {
scalar(
ticktimer_server(),
crate::os::xous::services::TicktimerScalar::FreeMutex(self.index()).into(),
)
.ok();
}
}
}
72 changes: 72 additions & 0 deletions library/std/src/sys/xous/locks/rwlock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use crate::os::xous::ffi::do_yield;
use crate::sync::atomic::{AtomicIsize, Ordering::SeqCst};

pub struct RwLock {
/// The "mode" value indicates how many threads are waiting on this
/// Mutex. Possible values are:
/// -1: The lock is locked for writing
/// 0: The lock is unlocked
/// >=1: The lock is locked for reading
///
/// This currently spins waiting for the lock to be freed. An
/// optimization would be to involve the ticktimer server to
/// coordinate unlocks.
mode: AtomicIsize,
}

unsafe impl Send for RwLock {}
unsafe impl Sync for RwLock {}

impl RwLock {
#[inline]
#[rustc_const_stable(feature = "const_locks", since = "1.63.0")]
pub const fn new() -> RwLock {
RwLock { mode: AtomicIsize::new(0) }
}

#[inline]
pub unsafe fn read(&self) {
while !unsafe { self.try_read() } {
do_yield();
}
}

#[inline]
pub unsafe fn try_read(&self) -> bool {
// Non-atomically determine the current value.
let current = self.mode.load(SeqCst);

// If it's currently locked for writing, then we cannot read.
if current < 0 {
return false;
}

// Attempt to lock. If the `current` value has changed, then this
// operation will fail and we will not obtain the lock even if we
// could potentially keep it.
let new = current + 1;
self.mode.compare_exchange(current, new, SeqCst, SeqCst).is_ok()
}

#[inline]
pub unsafe fn write(&self) {
while !unsafe { self.try_write() } {
do_yield();
}
}

#[inline]
pub unsafe fn try_write(&self) -> bool {
self.mode.compare_exchange(0, -1, SeqCst, SeqCst).is_ok()
}

#[inline]
pub unsafe fn read_unlock(&self) {
self.mode.fetch_sub(1, SeqCst);
}

#[inline]
pub unsafe fn write_unlock(&self) {
assert_eq!(self.mode.compare_exchange(-1, 0, SeqCst, SeqCst), Ok(-1));
}
}
1 change: 0 additions & 1 deletion library/std/src/sys/xous/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ pub mod env;
pub mod fs;
#[path = "../unsupported/io.rs"]
pub mod io;
#[path = "../unsupported/locks/mod.rs"]
pub mod locks;
#[path = "../unsupported/net.rs"]
pub mod net;
Expand Down

0 comments on commit 6413844

Please sign in to comment.