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 <sean@xobs.io>
  • Loading branch information
xobs committed Nov 8, 2022
1 parent d7c82e8 commit e0da598
Show file tree
Hide file tree
Showing 5 changed files with 235 additions and 1 deletion.
65 changes: 65 additions & 0 deletions library/std/src/sys/xous/locks/condvar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
use super::mutex::Mutex;
use crate::os::xous::ffi::{blocking_scalar, scalar};
use crate::os::xous::services::ticktimer_server;
use crate::sync::atomic::{AtomicUsize, Ordering::SeqCst};
use crate::time::Duration;

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

pub struct Condvar {
counter: AtomicUsize,
}

pub(crate) type MovableCondvar = Condvar;

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: AtomicUsize::new(0) }
}

pub unsafe fn notify_one(&self) {
if self.counter.load(SeqCst) > 0 {
self.counter.fetch_sub(1, SeqCst);
scalar(ticktimer_server(), [9 /* NotifyCondition */, self.index(), 1, 0, 0])
.expect("failure to send NotifyCondition command");
}
}

pub unsafe fn notify_all(&self) {
let counter = self.counter.swap(0, SeqCst);
scalar(ticktimer_server(), [9 /* NotifyCondition */, self.index(), counter, 0, 0])
.expect("failure to send NotifyCondition command");
}

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

pub unsafe fn wait(&self, mutex: &Mutex) {
self.counter.fetch_add(1, SeqCst);
unsafe { mutex.unlock() };
blocking_scalar(ticktimer_server(), [8 /* WaitForCondition */, self.index(), 0, 0, 0])
.expect("Ticktimer: failure to send WaitForCondition command");
unsafe { mutex.lock() };
}

pub unsafe fn wait_timeout(&self, mutex: &Mutex, dur: Duration) -> bool {
self.counter.fetch_add(1, SeqCst);
unsafe { mutex.unlock() };
let millis = dur.as_millis() as usize;
let result = blocking_scalar(
ticktimer_server(),
[8 /* WaitForCondition */, self.index(), millis, 0, 0],
)
.expect("Ticktimer: failure to send WaitForCondition command");
unsafe { mutex.lock() };

result[0] == 0
}
}
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::*;
89 changes: 89 additions & 0 deletions library/std/src/sys/xous/locks/mutex.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
use crate::os::xous::ffi::{blocking_scalar, do_yield, scalar};
use crate::os::xous::services::ticktimer_server;
use crate::sync::atomic::{AtomicUsize, 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,
}

pub type MovableMutex = Mutex;

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

#[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;
}

// 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(),
[6 /* LockMutex */, self as *const Mutex as usize, 0, 0, 0],
)
.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(), [7 /* UnlockMutex */, self as *const Mutex as usize, 0, 0, 0])
.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
}
}
74 changes: 74 additions & 0 deletions library/std/src/sys/xous/locks/rwlock.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
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,
}

pub type MovableRwLock = RwLock;

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(new, current, 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 e0da598

Please sign in to comment.