add rust interfaces and examples

Co-Authored-By: Hugo van der Wijst <hugo@wij.st>
This commit is contained in:
Chris Copeland 2023-07-23 20:51:29 -07:00
parent 13208548e8
commit 95c584e414
Signed by: chrisnc
GPG Key ID: 14550DA72485DF30
37 changed files with 1930 additions and 0 deletions

3
.gitignore vendored
View File

@ -1,2 +1,5 @@
/build/
.sconsign.dblite
/target/
/Cargo.lock

2
.rustfmt.toml Normal file
View File

@ -0,0 +1,2 @@
group_imports = "StdExternalCrate"
imports_granularity = "Crate"

84
Cargo.toml Normal file
View File

@ -0,0 +1,84 @@
[package]
name = "rt"
version = "0.0.2"
authors = ["Chris Copeland <chris@chrisnc.net>"]
description = "A real-time operating system capable of full preemption"
license = "Apache-2.0"
repository = "https://git.rtng.org/rt/rt"
documentation = "https://docs.rs/rt"
keywords = ["real-time", "rtos"]
categories = ["concurrency", "embedded", "no-std"]
edition = "2021"
rust-version = "1.64.0"
build = "rust/build.rs"
autoexamples = false
include = ["/src/", "/include/", "/arch/", "/rust/", "!SCons*", "!Dockerfile", "!*.bash"]
[lib]
path = "rust/src/lib.rs"
[dependencies]
paste = "1.0.14"
[dev-dependencies]
atomic_float = "0.1.0"
[build-dependencies]
bindgen = "0.66.1"
cc = "1.0"
[profile.dev]
panic = "abort"
[profile.release]
panic = "abort"
lto = "on"
debug = true
[[example]]
name = "donate"
path = "rust/examples/donate.rs"
[[example]]
name = "empty"
path = "rust/examples/empty.rs"
[[example]]
name = "float"
path = "rust/examples/float.rs"
[[example]]
name = "mutex"
path = "rust/examples/mutex.rs"
[[example]]
name = "notify"
path = "rust/examples/notify.rs"
[[example]]
name = "once"
path = "rust/examples/once.rs"
[[example]]
name = "queue"
path = "rust/examples/queue.rs"
[[example]]
name = "rwlock"
path = "rust/examples/rwlock.rs"
[[example]]
name = "semaphore"
path = "rust/examples/semaphore.rs"
[[example]]
name = "simple"
path = "rust/examples/simple.rs"
[[example]]
name = "sleep"
path = "rust/examples/sleep.rs"
[[example]]
name = "water-semaphore"
path = "rust/examples/water/semaphore.rs"

View File

@ -223,6 +223,10 @@ __attribute__((noreturn)) void rt_idle(void)
void rt_start(void)
{
#ifdef __APPLE__
fprintf(stderr, "running rt on macOS is not supported due to a bug in pthread_kill\n");
exit(1);
#endif
block_all_signals(NULL);
rt_task_cycle_resume();

17
rust/Dockerfile Normal file
View File

@ -0,0 +1,17 @@
FROM rust:bookworm
ENV DEBIAN_FRONTEND=noninteractive
RUN apt-get update && \
apt-get install -y \
bear \
clang \
gcc-arm-none-eabi \
gdb \
less \
libc++-dev \
llvm \
scons
RUN useradd rt
USER rt

105
rust/build.rs Normal file
View File

@ -0,0 +1,105 @@
use std::{env, path::PathBuf};
static SOURCES: &[&str] = &[
"barrier.c",
"cond.c",
"list.c",
"mutex.c",
"notify.c",
"queue.c",
"rt.c",
"rwlock.c",
"sem.c",
"sleep.c",
];
struct Arch<'a> {
name: &'a str,
sources: &'a [&'a str],
flags: &'a [&'a str],
defs: &'a [(&'a str, &'a str)],
}
static PTHREAD_ARCH: Arch = Arch {
name: "pthread",
sources: &["pthread.c"],
flags: &["-pthread"],
defs: &[("_POSIX_C_SOURCE", "200809L")],
};
static ARM_ARCH: Arch = Arch {
name: "arm",
sources: &["arm.c", "syscall.S"],
flags: &["-fshort-enums"], // arm-none-eabi-gcc uses this by default, but clang/bindgen don't
defs: &[],
};
fn main() {
let root_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
let target_arch = env::var("CARGO_CFG_TARGET_ARCH").unwrap();
let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap();
let arch = match (target_arch.as_str(), target_os.as_str()) {
("arm", "none") => &ARM_ARCH,
(_, "linux") => &PTHREAD_ARCH,
(_, "macos") => &PTHREAD_ARCH,
_ => panic!("Unsupported (arch, os) combination: ({target_arch}, {target_os})"),
};
let src_dir = root_dir.join("src");
let include_dir = root_dir.join("include");
let arch_dir = root_dir.join("arch").join(arch.name);
let arch_include_dir = arch_dir.join("include");
let mut cc = cc::Build::new();
cc.files(SOURCES.iter().map(|s| src_dir.join(s)));
cc.files(arch.sources.iter().map(|s| arch_dir.join(s)));
cc.include(&include_dir);
cc.include(arch_dir.join("include"));
cc.flag("-std=c17");
for flag in arch.flags {
cc.flag(flag);
}
for &(name, val) in arch.defs {
cc.define(name, val);
}
// TODO: Enable LTO when rust options request it and the C compiler's LTO is compatible.
let target = env::var("TARGET").unwrap();
// TODO: Provide a better way to specify Cortex-R variants.
// For now armebv7r is always a Hercules.
if target.contains("armebv7r") {
cc.include(arch_dir.join("r/hercules"));
}
cc.compile("rt");
let wrapper = "rust/wrapper.h";
println!("cargo:rerun-if-changed={}", wrapper);
for d in [&src_dir, &include_dir, &arch_dir] {
println!("cargo:rerun-if-changed={}", d.display());
}
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
bindgen::builder()
.header(wrapper)
.clang_arg(format!("-I{}", include_dir.display()))
.clang_arg(format!("-I{}", arch_include_dir.display()))
.clang_args(arch.defs.iter().map(|&(m, v)| format!("-D{m}={v}")))
.clang_args(arch.flags)
.disable_nested_struct_naming()
.use_core()
.default_enum_style(bindgen::EnumVariation::NewType {
is_bitfield: false,
is_global: false,
})
.parse_callbacks(Box::new(bindgen::CargoCallbacks))
.generate()
.expect("Failed to generate bindings.")
.write_to_file(out_dir.join("bindings.rs"))
.expect("Failed to write bindings.");
}

22
rust/docker.bash Executable file
View File

@ -0,0 +1,22 @@
#!/bin/bash
set -xe
name="rt-rust"
image="$name-builder"
rust_dir="$(dirname $0)"
rt_dir="$(realpath "$rust_dir"/..)"
docker build --tag "$image" "$rust_dir"
home_dir="/home/rt"
docker run \
--rm \
--tty \
--interactive \
--volume "$rt_dir:$home_dir" \
--workdir "$home_dir" \
--cap-add SYS_PTRACE \
"$image"

91
rust/examples/donate.rs Normal file
View File

@ -0,0 +1,91 @@
use core::sync::atomic::{AtomicBool, AtomicI32, Ordering};
const MAX_SEQ: i32 = 9;
static SEQ: AtomicI32 = AtomicI32::new(0);
static OUT_OF_ORDER: AtomicBool = AtomicBool::new(false);
rt::mutex!(MUTEX0);
rt::mutex!(MUTEX1);
rt::mutex!(MUTEX2);
fn sequence(s: i32) {
if SEQ.load(Ordering::Relaxed) == s {
SEQ.fetch_add(1, Ordering::Relaxed);
} else {
OUT_OF_ORDER.store(true, Ordering::Relaxed);
}
if OUT_OF_ORDER.load(Ordering::Relaxed) || (SEQ.load(Ordering::Relaxed) > MAX_SEQ) {
rt::stop();
}
}
fn locker0() {
rt::task::drop_privilege();
sequence(3);
{
let _guard0 = MUTEX0.lock();
let _guard1 = MUTEX1.lock();
sequence(5);
}
sequence(-1);
}
fn locker1() {
rt::task::drop_privilege();
sequence(2);
let guard1 = MUTEX1.lock();
rt::task::sleep(20);
sequence(4);
let _guard2 = MUTEX2.lock();
drop(guard1);
sequence(8);
rt::task::sleep(20);
sequence(-1);
}
fn spinner() {
rt::task::drop_privilege();
sequence(1);
rt::task::sleep(10);
loop {}
}
fn donator() {
rt::task::drop_privilege();
sequence(0);
rt::task::sleep(30);
{
let _guard0 = MUTEX0.lock();
sequence(6);
}
sequence(7);
if MUTEX2.timed_lock(10).is_none() {
sequence(9);
}
}
static TIMED_OUT: AtomicBool = AtomicBool::new(false);
fn timeout() {
rt::task::sleep(100);
TIMED_OUT.store(true, Ordering::Relaxed);
rt::stop();
}
fn main() {
rt::task!(locker0, rt::task::STACK_MIN, 1);
rt::task!(locker1, rt::task::STACK_MIN, 2);
rt::task!(spinner, rt::task::STACK_MIN, 3);
rt::task!(donator, rt::task::STACK_MIN, 4);
rt::task!(timeout, rt::task::STACK_MIN, 5);
rt::start();
if TIMED_OUT.load(Ordering::Relaxed) {
panic!("timed out");
}
if OUT_OF_ORDER.load(Ordering::Relaxed) {
panic!("out of order");
}
}

9
rust/examples/empty.rs Normal file
View File

@ -0,0 +1,9 @@
fn empty() {
rt::task::drop_privilege();
rt::stop();
}
fn main() {
rt::task!(empty, rt::task::STACK_MIN, 1);
rt::start();
}

31
rust/examples/float.rs Normal file
View File

@ -0,0 +1,31 @@
use core::sync::atomic::Ordering;
use atomic_float::AtomicF32;
static V: AtomicF32 = AtomicF32::new(0.0);
fn f(arg: u32) {
rt::task::drop_privilege();
rt::task::enable_fp();
let mut x = 0.0f32;
let a = arg as f32;
loop {
x += a;
V.store(x, Ordering::Relaxed);
rt::task::yield_();
}
}
fn timeout() {
rt::task::drop_privilege();
rt::task::sleep(100);
rt::stop();
}
fn main() {
rt::task!(f(1), 2 * rt::task::STACK_MIN, 1);
rt::task!(f(2), 2 * rt::task::STACK_MIN, 1);
rt::task!(timeout, rt::task::STACK_MIN, 2);
rt::start();
}

75
rust/examples/mutex.rs Normal file
View File

@ -0,0 +1,75 @@
use core::sync::atomic::{AtomicBool, Ordering};
rt::mutex!(MUTEX, u32, 0);
const NUM_TASKS: u32 = 3;
const ITERATIONS: u32 = 10000;
fn stop_last() {
rt::semaphore!(STOP_SEM, NUM_TASKS as i32 - 1);
if !STOP_SEM.try_wait() {
rt::stop();
}
}
fn increment_lock() {
rt::task::drop_privilege();
for _ in 0..ITERATIONS {
*MUTEX.lock() += 1;
}
stop_last();
}
fn increment_trylock() {
rt::task::drop_privilege();
for _ in 0..ITERATIONS {
let mut guard = loop {
if let Some(guard) = MUTEX.try_lock() {
break guard;
} else {
rt::task::sleep(1);
}
};
*guard += 1;
}
stop_last();
}
fn increment_timedlock() {
rt::task::drop_privilege();
for _ in 0..ITERATIONS {
let mut guard = loop {
if let Some(guard) = MUTEX.timed_lock(1) {
break guard;
}
};
*guard += 1;
}
stop_last();
}
static TIMED_OUT: AtomicBool = AtomicBool::new(false);
fn timeout() {
rt::task::sleep(1000);
TIMED_OUT.store(true, Ordering::Relaxed);
rt::stop();
}
fn main() {
rt::task!(increment_lock, rt::task::STACK_MIN, 1);
rt::task!(increment_trylock, rt::task::STACK_MIN, 1);
rt::task!(increment_timedlock, rt::task::STACK_MIN, 1);
rt::task!(timeout, rt::task::STACK_MIN, 2);
rt::start();
if TIMED_OUT.load(Ordering::Relaxed) {
panic!("timed out");
}
let guard = MUTEX.try_lock().expect("mutex should be unlocked");
if *guard != ITERATIONS * NUM_TASKS {
panic!("the mutex did not contain the expected value");
}
}

49
rust/examples/notify.rs Normal file
View File

@ -0,0 +1,49 @@
use core::sync::atomic::{AtomicBool, Ordering};
const N: i32 = 10;
rt::notify!(NOTE);
fn notifier() {
rt::task::drop_privilege();
for _ in 0..N {
rt::task::sleep(5);
NOTE.or(1);
}
rt::task::sleep(15);
NOTE.post();
}
static WAIT_FAILED: AtomicBool = AtomicBool::new(false);
static WRONG_VALUE: AtomicBool = AtomicBool::new(false);
fn waiter() {
rt::task::drop_privilege();
for _ in 0..N {
match NOTE.timed_wait_clear(1, 10) {
Some(v) if v != 1 => WRONG_VALUE.store(true, Ordering::Relaxed),
None => WAIT_FAILED.store(true, Ordering::Relaxed),
_ => (),
};
}
if NOTE.timed_wait(10).is_some() {
WAIT_FAILED.store(true, Ordering::Relaxed);
}
rt::stop();
}
fn main() {
rt::task!(notifier, rt::task::STACK_MIN, 1);
rt::task!(waiter, rt::task::STACK_MIN, 1);
rt::start();
if WAIT_FAILED.load(Ordering::Relaxed) {
panic!("notify wait failed");
}
if WRONG_VALUE.load(Ordering::Relaxed) {
panic!("notify wait returned the wrong value");
}
}

34
rust/examples/once.rs Normal file
View File

@ -0,0 +1,34 @@
use core::sync::atomic::{AtomicUsize, Ordering};
const ITERATIONS: usize = 100;
rt::once!(ONCE);
rt::semaphore!(SEM);
static X: AtomicUsize = AtomicUsize::new(0);
fn f() {
X.fetch_add(1, Ordering::Relaxed);
}
fn oncer() {
rt::task::drop_privilege();
for _ in 0..ITERATIONS {
ONCE.call_once(f);
}
}
fn oncer_stop() {
oncer();
rt::stop();
}
fn main() {
rt::task!(oncer, rt::task::STACK_MIN, 1);
rt::task!(oncer_stop, rt::task::STACK_MIN, 1);
rt::start();
if X.load(Ordering::Relaxed) != 1 {
panic!("X did not have the expected value");
}
}

51
rust/examples/queue.rs Normal file
View File

@ -0,0 +1,51 @@
use core::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
rt::queue!(QUEUE, u32, 10);
fn pusher(mut i: u32) {
rt::task::drop_privilege();
loop {
QUEUE.push(i);
i += 1;
}
}
const NPUSHERS: usize = 2;
const TASK_INC: u32 = 0x1000000;
static OUT_OF_ORDER: AtomicBool = AtomicBool::new(false);
static NUM_POPPED: AtomicUsize = AtomicUsize::new(0);
fn popper() {
rt::task::drop_privilege();
let mut x: u32;
let mut max_elem = [0u32; NPUSHERS];
loop {
x = QUEUE.pop();
NUM_POPPED.fetch_add(1, Ordering::Relaxed);
let task = x / TASK_INC;
let elem = x % TASK_INC;
if elem < max_elem[task as usize] {
OUT_OF_ORDER.store(true, Ordering::Release);
}
max_elem[task as usize] = elem;
}
}
fn timeout() {
rt::task::drop_privilege();
rt::task::sleep(1000);
rt::stop();
}
fn main() {
rt::task!(pusher(0), rt::task::STACK_MIN, 1);
rt::task!(pusher(TASK_INC), rt::task::STACK_MIN, 1);
rt::task!(popper, rt::task::STACK_MIN, 1);
rt::task!(timeout, rt::task::STACK_MIN, 2);
rt::start();
if OUT_OF_ORDER.load(Ordering::Acquire) {
panic!("queue elements were received out of order");
}
}

49
rust/examples/rwlock.rs Normal file
View File

@ -0,0 +1,49 @@
use core::sync::atomic::{AtomicBool, Ordering};
struct Point {
x: u32,
y: u32,
}
rt::rwlock!(POINT, Point, Point { x: 0, y: 0 });
static MISMATCH: AtomicBool = AtomicBool::new(false);
fn reader() {
rt::task::drop_privilege();
loop {
let p = POINT.read();
if p.x != p.y {
MISMATCH.store(true, Ordering::Relaxed);
}
}
}
fn writer() {
rt::task::drop_privilege();
loop {
let mut p = POINT.write();
p.x += 1;
p.y += 1;
drop(p);
rt::task::sleep(1);
}
}
fn timeout() {
rt::task::drop_privilege();
rt::task::sleep(50);
rt::stop();
}
fn main() {
rt::task!(reader, rt::task::STACK_MIN, 1);
rt::task!(reader, rt::task::STACK_MIN, 1);
rt::task!(writer, rt::task::STACK_MIN, 1);
rt::task!(timeout, rt::task::STACK_MIN, 2);
rt::start();
if MISMATCH.load(Ordering::Relaxed) {
panic!("point coordinates did not match");
}
}

View File

@ -0,0 +1,40 @@
use core::sync::atomic::{AtomicBool, Ordering};
const N: i32 = 10;
rt::semaphore!(SEM);
fn poster() {
rt::task::drop_privilege();
for _ in 0..N {
rt::task::sleep(5);
SEM.post();
}
rt::task::sleep(15);
SEM.post();
}
static WAIT_FAILED: AtomicBool = AtomicBool::new(false);
fn waiter() {
rt::task::drop_privilege();
for _ in 0..N {
if !SEM.timed_wait(10) {
WAIT_FAILED.store(true, Ordering::Relaxed);
}
}
if SEM.timed_wait(10) {
WAIT_FAILED.store(true, Ordering::Relaxed);
}
rt::stop();
}
fn main() {
rt::task!(poster, rt::task::STACK_MIN, 1);
rt::task!(waiter, rt::task::STACK_MIN, 1);
rt::start();
if WAIT_FAILED.load(Ordering::Relaxed) {
panic!("semaphore wait failed");
}
}

16
rust/examples/simple.rs Normal file
View File

@ -0,0 +1,16 @@
fn simple(arg: usize) {
rt::task::drop_privilege();
for _ in 0..100 {
rt::task::yield_();
}
if arg == 1 {
rt::stop();
}
}
fn main() {
rt::task!(simple(0), rt::task::STACK_MIN, 1);
rt::task!(simple(1), rt::task::STACK_MIN, 1);
rt::start();
}

34
rust/examples/sleep.rs Normal file
View File

@ -0,0 +1,34 @@
use core::sync::atomic::{AtomicBool, Ordering};
const NLOOPS: i32 = 5;
static WRONG_TICK: AtomicBool = AtomicBool::new(false);
fn sleep_periodic(period: rt::tick::Utick) {
rt::task::drop_privilege();
let mut last_wake_tick = 0;
for _ in 0..NLOOPS {
rt::task::sleep_periodic(&mut last_wake_tick, period);
let wake_tick = rt::tick::count();
if wake_tick != last_wake_tick {
WRONG_TICK.store(true, Ordering::Release);
}
}
// Only the second task to finish will call rt_stop.
rt::semaphore!(STOP_SEM, 1);
if !STOP_SEM.try_wait() {
rt::stop();
}
}
fn main() {
rt::task!(sleep_periodic(5), rt::task::STACK_MIN, 2);
rt::task!(sleep_periodic(10), rt::task::STACK_MIN, 1);
rt::start();
if WRONG_TICK.load(Ordering::Acquire) {
panic!("a task woke up at the wrong tick");
}
}

View File

@ -0,0 +1,20 @@
use core::sync::atomic::{AtomicU32, Ordering};
rt::semaphore!(H2READY);
rt::semaphore!(HDONE);
static H: AtomicU32 = AtomicU32::new(0);
fn hydrogen() {
if (H.fetch_add(1, Ordering::Relaxed) & 1) == 1 {
H2READY.post();
}
HDONE.wait();
}
fn oxygen() {
H2READY.wait();
make_water();
HDONE.post_n(2);
}
include!("water.rs");

View File

@ -0,0 +1,53 @@
static HYDROGEN_BONDED: AtomicU32 = AtomicU32::new(0);
static OXYGEN_BONDED: AtomicU32 = AtomicU32::new(0);
static WATER_FORMED: AtomicU32 = AtomicU32::new(0);
fn make_water() {
WATER_FORMED.fetch_add(1, Ordering::Relaxed);
}
fn timeout() {
rt::task::drop_privilege();
rt::task::sleep(1000);
rt::stop();
}
fn oxygen_loop() {
rt::task::drop_privilege();
loop {
oxygen();
OXYGEN_BONDED.fetch_add(1, Ordering::Relaxed);
}
}
fn hydrogen_loop() {
rt::task::drop_privilege();
loop {
hydrogen();
HYDROGEN_BONDED.fetch_add(1, Ordering::Relaxed);
}
}
fn main() {
rt::task!(timeout, rt::task::STACK_MIN, 3);
rt::task!(hydrogen_loop, rt::task::STACK_MIN, 1);
rt::task!(hydrogen_loop, rt::task::STACK_MIN, 1);
rt::task!(oxygen_loop, rt::task::STACK_MIN, 2);
rt::start();
let w = WATER_FORMED.load(Ordering::Relaxed);
let h = HYDROGEN_BONDED.load(Ordering::Relaxed);
let o = OXYGEN_BONDED.load(Ordering::Relaxed);
/* The oxygen or hydrogen may not have bonded by the time rt_stop is called
* after making a water molecule, so allow for o and h to be one molecule's
* worth below expected value or exactly equal to it. */
let o_lo = w - 1;
let o_hi = w;
let h_lo = (w - 1) * 2;
let h_hi = w * 2;
if (o < o_lo) || (o > o_hi) || (h < h_lo) || (h > h_hi) {
panic!("hydrogen and oxygen bonding counts are incorrect");
}
}

6
rust/src/bindings.rs Normal file
View File

@ -0,0 +1,6 @@
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(non_upper_case_globals)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));

5
rust/src/cycle.rs Normal file
View File

@ -0,0 +1,5 @@
use crate::bindings::rt_cycle;
pub fn cycle() -> u32 {
unsafe { rt_cycle() }
}

21
rust/src/lib.rs Normal file
View File

@ -0,0 +1,21 @@
#![no_std]
pub mod cycle;
pub mod sync;
pub mod task;
pub mod tick;
pub use paste;
mod bindings;
mod list;
use bindings::{rt_start, rt_stop};
pub fn start() {
unsafe { rt_start() }
}
pub fn stop() {
unsafe { rt_stop() }
}

6
rust/src/list.rs Normal file
View File

@ -0,0 +1,6 @@
use crate::bindings::rt_list;
pub const fn list_init(l: &'static rt_list) -> rt_list {
let lp = l as *const rt_list as *mut rt_list;
rt_list { prev: lp, next: lp }
}

72
rust/src/sync/barrier.rs Normal file
View File

@ -0,0 +1,72 @@
use core::{
cell::UnsafeCell,
marker::PhantomPinned,
panic::{RefUnwindSafe, UnwindSafe},
};
use crate::{
bindings::{rt_barrier, rt_barrier_wait, rt_cond, rt_mutex},
list::list_init,
sync::semaphore::c_sem_init,
};
pub struct Barrier {
b: UnsafeCell<rt_barrier>,
_pin_marker: PhantomPinned,
}
unsafe impl Send for Barrier {}
unsafe impl Sync for Barrier {}
impl UnwindSafe for Barrier {}
impl RefUnwindSafe for Barrier {}
pub struct BarrierWaitResult(bool);
impl Barrier {
pub const fn init(barrier: &'static Barrier, count: u32) -> Barrier {
let b = unsafe { &*(barrier.b.get() as *const rt_barrier) };
Barrier {
b: UnsafeCell::new(rt_barrier {
mutex: rt_mutex {
holder: 0,
wait_list: list_init(&b.mutex.wait_list),
list: list_init(&b.mutex.list),
},
cond: rt_cond {
sem: c_sem_init(&b.cond.sem, 0, 0),
},
level: 0,
threshold: count,
generation: 0,
}),
_pin_marker: PhantomPinned,
}
}
pub fn wait(&self) -> BarrierWaitResult {
BarrierWaitResult(unsafe { rt_barrier_wait(self.b.get()) })
}
}
impl BarrierWaitResult {
#[must_use]
pub fn is_leader(&self) -> bool {
self.0
}
}
#[macro_export]
macro_rules! barrier {
($name: ident, $count: expr) => {
static $name: $crate::sync::Barrier = $crate::sync::Barrier::init(&$name, $count);
};
}
#[cfg(test)]
mod tests {
#[test]
fn fast_path() {
barrier!(BARRIER, 1);
assert!(BARRIER.wait().is_leader());
}
}

73
rust/src/sync/condvar.rs Normal file
View File

@ -0,0 +1,73 @@
use core::{
cell::UnsafeCell,
marker::PhantomPinned,
panic::{RefUnwindSafe, UnwindSafe},
};
use crate::{
bindings::{rt_cond, rt_cond_broadcast, rt_cond_signal, rt_cond_timedwait, rt_cond_wait},
sync::{semaphore::c_sem_init, MutexGuard},
tick::Utick,
};
pub struct Condvar {
c: UnsafeCell<rt_cond>,
_pin_marker: PhantomPinned,
}
unsafe impl Send for Condvar {}
unsafe impl Sync for Condvar {}
impl UnwindSafe for Condvar {}
impl RefUnwindSafe for Condvar {}
impl Condvar {
pub const fn init(cond: &'static Condvar) -> Condvar {
let c = unsafe { &*(cond.c.get() as *const rt_cond) };
Condvar {
c: UnsafeCell::new(rt_cond {
sem: c_sem_init(&c.sem, 0, 0),
}),
_pin_marker: PhantomPinned,
}
}
pub fn signal(&self) {
unsafe { rt_cond_signal(self.c.get()) }
}
pub fn broadcast(&self) {
unsafe { rt_cond_broadcast(self.c.get()) }
}
pub fn wait<T>(&self, guard: &MutexGuard<T>) {
unsafe { rt_cond_wait(self.c.get(), guard.ptr()) }
}
pub fn timedwait<'a, T>(
&self,
guard: MutexGuard<'a, T>,
ticks: Utick,
) -> Option<MutexGuard<'a, T>> {
if unsafe { rt_cond_timedwait(self.c.get(), guard.ptr(), ticks) } {
Some(guard)
} else {
None
}
}
}
#[macro_export]
macro_rules! condvar {
($name: ident) => {
static $name: $crate::sync::Condvar = $crate::sync::Condvar::init(&$name);
};
}
#[cfg(test)]
mod tests {
#[test]
fn signal() {
condvar!(CONDVAR);
CONDVAR.signal();
}
}

17
rust/src/sync/mod.rs Normal file
View File

@ -0,0 +1,17 @@
pub use barrier::{Barrier, BarrierWaitResult};
pub use condvar::Condvar;
pub use mutex::{Mutex, MutexGuard};
pub use notify::Notify;
pub use once::Once;
pub use queue::Queue;
pub use rwlock::RwLock;
pub use semaphore::{Semaphore, SemaphoreGuard};
mod barrier;
mod condvar;
mod mutex;
mod notify;
mod once;
mod queue;
mod rwlock;
mod semaphore;

147
rust/src/sync/mutex.rs Normal file
View File

@ -0,0 +1,147 @@
use core::{
cell::UnsafeCell,
marker::{PhantomData, PhantomPinned},
ops::{Deref, DerefMut},
panic::{RefUnwindSafe, UnwindSafe},
};
use crate::{
bindings::{rt_mutex, rt_mutex_lock, rt_mutex_timedlock, rt_mutex_trylock, rt_mutex_unlock},
list::list_init,
tick::Utick,
};
pub struct Mutex<T: ?Sized> {
m: UnsafeCell<rt_mutex>,
_pin_marker: PhantomPinned,
data: UnsafeCell<T>,
}
unsafe impl<T: ?Sized + Send> Send for Mutex<T> {}
unsafe impl<T: ?Sized + Send> Sync for Mutex<T> {}
impl<T: ?Sized> UnwindSafe for Mutex<T> {}
impl<T: ?Sized> RefUnwindSafe for Mutex<T> {}
impl<T> Mutex<T> {
pub const fn init(mutex: &'static Mutex<T>, t: T) -> Mutex<T> {
let m = unsafe { &*(mutex.m.get() as *const rt_mutex) };
Mutex {
m: UnsafeCell::new(rt_mutex {
holder: 0,
wait_list: list_init(&m.wait_list),
list: list_init(&m.list),
}),
_pin_marker: PhantomPinned,
data: UnsafeCell::new(t),
}
}
}
impl<T: ?Sized> Mutex<T> {
pub fn lock(&self) -> MutexGuard<'_, T> {
unsafe {
rt_mutex_lock(self.m.get());
}
MutexGuard::new(self)
}
pub fn try_lock(&self) -> Option<MutexGuard<T>> {
if unsafe { rt_mutex_trylock(self.m.get()) } {
Some(MutexGuard::new(self))
} else {
None
}
}
pub fn timed_lock(&self, ticks: Utick) -> Option<MutexGuard<T>> {
if unsafe { rt_mutex_timedlock(self.m.get(), ticks) } {
Some(MutexGuard::new(self))
} else {
None
}
}
// Probably not useful because this Mutex must be static.
pub fn get_mut(&mut self) -> &mut T {
self.data.get_mut()
}
}
pub struct MutexGuard<'a, T: ?Sized + 'a> {
lock: &'a Mutex<T>,
_unsend_marker: PhantomData<*const ()>,
}
impl<T: ?Sized> MutexGuard<'_, T> {
fn new(lock: &Mutex<T>) -> MutexGuard<T> {
MutexGuard {
lock,
_unsend_marker: PhantomData,
}
}
}
// Not supported yet, so using the _unsend_marker instead.
//impl<T: ?Sized> !Send for MutexGuard<'_, T> {}
unsafe impl<T: ?Sized + Sync> Sync for MutexGuard<'_, T> {}
impl<T: ?Sized> MutexGuard<'_, T> {
// Needs to be pub(crate) so that cond can use it.
pub(crate) fn ptr(&self) -> *mut rt_mutex {
self.lock.m.get()
}
}
impl<T: ?Sized> Deref for MutexGuard<'_, T> {
type Target = T;
fn deref(&self) -> &T {
unsafe { &*self.lock.data.get() }
}
}
impl<T: ?Sized> DerefMut for MutexGuard<'_, T> {
fn deref_mut(&mut self) -> &mut T {
unsafe { &mut *self.lock.data.get() }
}
}
impl<T: ?Sized> Drop for MutexGuard<'_, T> {
#[inline]
fn drop(&mut self) {
unsafe { rt_mutex_unlock(self.lock.m.get()) }
}
}
#[macro_export]
macro_rules! mutex {
($name: ident, $type: ty, $data: expr) => {
static $name: $crate::sync::Mutex<$type> = $crate::sync::Mutex::init(&$name, $data);
};
($name: ident) => {
rt::mutex!($name, (), ());
};
}
#[cfg(test)]
mod tests {
#[test]
fn fast_path() {
mutex!(MUTEX, i32, 0);
*MUTEX.lock() += 1;
assert!(*MUTEX.lock() == 1);
}
#[test]
fn try_lock() {
mutex!(MUTEX, i32, 0);
{
let guard = MUTEX.try_lock();
assert!(MUTEX.try_lock().is_none());
guard.map(|mut x| *x += 1);
}
assert!(*MUTEX.lock() == 1);
}
}

138
rust/src/sync/notify.rs Normal file
View File

@ -0,0 +1,138 @@
use core::{
cell::UnsafeCell,
marker::PhantomPinned,
panic::{RefUnwindSafe, UnwindSafe},
};
use crate::{
bindings::{
rt_notify, rt_notify_add, rt_notify_or, rt_notify_post, rt_notify_set, rt_notify_timedwait,
rt_notify_timedwait_clear, rt_notify_trywait, rt_notify_trywait_clear, rt_notify_wait,
rt_notify_wait_clear,
},
sync::semaphore::c_sem_init,
tick::Utick,
};
pub struct Notify {
n: UnsafeCell<rt_notify>,
_pin_marker: PhantomPinned,
}
unsafe impl Send for Notify {}
unsafe impl Sync for Notify {}
impl UnwindSafe for Notify {}
impl RefUnwindSafe for Notify {}
impl Notify {
pub const fn init(note: &'static Notify, value: u32) -> Notify {
let n = unsafe { &*(note.n.get() as *const rt_notify) };
Notify {
n: UnsafeCell::new(rt_notify {
value,
sem: c_sem_init(&n.sem, 0, 0),
}),
_pin_marker: PhantomPinned,
}
}
pub fn post(&self) {
unsafe { rt_notify_post(self.n.get()) }
}
pub fn or(&self, v: u32) {
unsafe { rt_notify_or(self.n.get(), v) }
}
pub fn add(&self, v: u32) {
unsafe { rt_notify_add(self.n.get(), v) }
}
pub fn set(&self, v: u32) {
unsafe { rt_notify_set(self.n.get(), v) }
}
pub fn wait(&self) -> u32 {
unsafe { rt_notify_wait(self.n.get()) }
}
pub fn wait_clear(&self, c: u32) -> u32 {
unsafe { rt_notify_wait_clear(self.n.get(), c) }
}
pub fn try_wait(&self) -> Option<u32> {
let mut v = 0;
if unsafe { rt_notify_trywait(self.n.get(), &mut v) } {
Some(v)
} else {
None
}
}
pub fn try_wait_clear(&self, c: u32) -> Option<u32> {
let mut v = 0;
if unsafe { rt_notify_trywait_clear(self.n.get(), &mut v, c) } {
Some(v)
} else {
None
}
}
pub fn timed_wait(&self, ticks: Utick) -> Option<u32> {
let mut v = 0;
if unsafe { rt_notify_timedwait(self.n.get(), &mut v, ticks) } {
Some(v)
} else {
None
}
}
pub fn timed_wait_clear(&self, c: u32, ticks: Utick) -> Option<u32> {
let mut v = 0;
if unsafe { rt_notify_timedwait_clear(self.n.get(), &mut v, c, ticks) } {
Some(v)
} else {
None
}
}
}
#[macro_export]
macro_rules! notify {
($name: ident, $value: expr) => {
static $name: $crate::sync::Notify = $crate::sync::Notify::init(&$name, $value);
};
($name: ident) => {
$crate::notify!($name, 0);
};
}
#[cfg(test)]
mod tests {
#[test]
fn fast_path() {
notify!(NOTIFY);
NOTIFY.post();
assert!(NOTIFY.wait() == 0);
}
#[test]
fn ops() {
notify!(NOTIFY);
NOTIFY.or(1);
assert!(NOTIFY.wait() == 1);
NOTIFY.add(1);
assert!(NOTIFY.wait() == 2);
NOTIFY.set(3);
assert!(NOTIFY.wait() == 3);
}
#[test]
fn clear() {
notify!(NOTIFY);
NOTIFY.or(u32::MAX);
assert!(NOTIFY.wait_clear(0xFF) == u32::MAX);
assert!(NOTIFY.wait() == 0xFFFFFF00);
}
}

48
rust/src/sync/once.rs Normal file
View File

@ -0,0 +1,48 @@
use core::sync::atomic::{AtomicI32, Ordering};
use crate::sync::Mutex;
pub struct Once {
done: AtomicI32,
mutex: Mutex<()>,
}
impl Once {
pub const fn init(once: &'static Once) -> Once {
Once {
done: AtomicI32::new(0),
mutex: Mutex::init(&once.mutex, ()),
}
}
pub fn call_once<F: FnOnce()>(&self, f: F) {
if self.done.load(Ordering::Acquire) == 0 {
let _guard = self.mutex.lock();
if self.done.load(Ordering::Relaxed) == 0 {
f();
self.done.store(1, Ordering::Release);
}
}
}
pub fn is_completed(&self) -> bool {
self.done.load(Ordering::Acquire) != 0
}
}
#[macro_export]
macro_rules! once {
($name: ident) => {
static $name: $crate::sync::Once = $crate::sync::Once::init(&$name);
};
}
#[cfg(test)]
mod tests {
#[test]
fn fast_path() {
once!(ONCE);
ONCE.call_once(|| {});
assert!(ONCE.is_completed());
}
}

151
rust/src/sync/queue.rs Normal file
View File

@ -0,0 +1,151 @@
use core::{
cell::UnsafeCell,
ffi::{c_uchar, c_void},
marker::{PhantomData, PhantomPinned},
mem::{size_of, MaybeUninit},