mirror of
https://github.com/rust-lang/rust.git
synced 2024-11-02 07:22:42 +00:00
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 <sean@xobs.io>
This commit is contained in:
parent
46a4ec3a71
commit
6413844a22
@ -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 {
|
||||
@ -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],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
111
library/std/src/sys/xous/locks/condvar.rs
Normal file
111
library/std/src/sys/xous/locks/condvar.rs
Normal file
@ -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
library/std/src/sys/xous/locks/mod.rs
Normal file
7
library/std/src/sys/xous/locks/mod.rs
Normal file
@ -0,0 +1,7 @@
|
||||
mod condvar;
|
||||
mod mutex;
|
||||
mod rwlock;
|
||||
|
||||
pub use condvar::*;
|
||||
pub use mutex::*;
|
||||
pub use rwlock::*;
|
116
library/std/src/sys/xous/locks/mutex.rs
Normal file
116
library/std/src/sys/xous/locks/mutex.rs
Normal file
@ -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
library/std/src/sys/xous/locks/rwlock.rs
Normal file
72
library/std/src/sys/xous/locks/rwlock.rs
Normal file
@ -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));
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
Loading…
Reference in New Issue
Block a user