From 95c584e414ef3c4acdc920f3361978959c04989f Mon Sep 17 00:00:00 2001 From: Chris Copeland Date: Sun, 23 Jul 2023 20:51:29 -0700 Subject: [PATCH] add rust interfaces and examples Co-Authored-By: Hugo van der Wijst --- .gitignore | 3 + .rustfmt.toml | 2 + Cargo.toml | 84 +++++++++++++++++ arch/pthread/pthread.c | 4 + rust/Dockerfile | 17 ++++ rust/build.rs | 105 +++++++++++++++++++++ rust/docker.bash | 22 +++++ rust/examples/donate.rs | 91 ++++++++++++++++++ rust/examples/empty.rs | 9 ++ rust/examples/float.rs | 31 ++++++ rust/examples/mutex.rs | 75 +++++++++++++++ rust/examples/notify.rs | 49 ++++++++++ rust/examples/once.rs | 34 +++++++ rust/examples/queue.rs | 51 ++++++++++ rust/examples/rwlock.rs | 49 ++++++++++ rust/examples/semaphore.rs | 40 ++++++++ rust/examples/simple.rs | 16 ++++ rust/examples/sleep.rs | 34 +++++++ rust/examples/water/semaphore.rs | 20 ++++ rust/examples/water/water.rs | 53 +++++++++++ rust/src/bindings.rs | 6 ++ rust/src/cycle.rs | 5 + rust/src/lib.rs | 21 +++++ rust/src/list.rs | 6 ++ rust/src/sync/barrier.rs | 72 ++++++++++++++ rust/src/sync/condvar.rs | 73 +++++++++++++++ rust/src/sync/mod.rs | 17 ++++ rust/src/sync/mutex.rs | 147 +++++++++++++++++++++++++++++ rust/src/sync/notify.rs | 138 +++++++++++++++++++++++++++ rust/src/sync/once.rs | 48 ++++++++++ rust/src/sync/queue.rs | 151 ++++++++++++++++++++++++++++++ rust/src/sync/rwlock.rs | 156 +++++++++++++++++++++++++++++++ rust/src/sync/semaphore.rs | 147 +++++++++++++++++++++++++++++ rust/src/task.rs | 121 ++++++++++++++++++++++++ rust/src/tick.rs | 7 ++ rust/test.bash | 13 +++ rust/wrapper.h | 13 +++ 37 files changed, 1930 insertions(+) create mode 100644 .rustfmt.toml create mode 100644 Cargo.toml create mode 100644 rust/Dockerfile create mode 100644 rust/build.rs create mode 100755 rust/docker.bash create mode 100644 rust/examples/donate.rs create mode 100644 rust/examples/empty.rs create mode 100644 rust/examples/float.rs create mode 100644 rust/examples/mutex.rs create mode 100644 rust/examples/notify.rs create mode 100644 rust/examples/once.rs create mode 100644 rust/examples/queue.rs create mode 100644 rust/examples/rwlock.rs create mode 100644 rust/examples/semaphore.rs create mode 100644 rust/examples/simple.rs create mode 100644 rust/examples/sleep.rs create mode 100644 rust/examples/water/semaphore.rs create mode 100644 rust/examples/water/water.rs create mode 100644 rust/src/bindings.rs create mode 100644 rust/src/cycle.rs create mode 100644 rust/src/lib.rs create mode 100644 rust/src/list.rs create mode 100644 rust/src/sync/barrier.rs create mode 100644 rust/src/sync/condvar.rs create mode 100644 rust/src/sync/mod.rs create mode 100644 rust/src/sync/mutex.rs create mode 100644 rust/src/sync/notify.rs create mode 100644 rust/src/sync/once.rs create mode 100644 rust/src/sync/queue.rs create mode 100644 rust/src/sync/rwlock.rs create mode 100644 rust/src/sync/semaphore.rs create mode 100644 rust/src/task.rs create mode 100644 rust/src/tick.rs create mode 100755 rust/test.bash create mode 100644 rust/wrapper.h diff --git a/.gitignore b/.gitignore index 8e32da8..209a52f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /build/ .sconsign.dblite + +/target/ +/Cargo.lock diff --git a/.rustfmt.toml b/.rustfmt.toml new file mode 100644 index 0000000..44b6aab --- /dev/null +++ b/.rustfmt.toml @@ -0,0 +1,2 @@ +group_imports = "StdExternalCrate" +imports_granularity = "Crate" diff --git a/Cargo.toml b/Cargo.toml new file mode 100644 index 0000000..1bfd5b7 --- /dev/null +++ b/Cargo.toml @@ -0,0 +1,84 @@ +[package] +name = "rt" +version = "0.0.2" +authors = ["Chris Copeland "] +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" diff --git a/arch/pthread/pthread.c b/arch/pthread/pthread.c index a61e2ce..1bb8bde 100644 --- a/arch/pthread/pthread.c +++ b/arch/pthread/pthread.c @@ -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(); diff --git a/rust/Dockerfile b/rust/Dockerfile new file mode 100644 index 0000000..84a02b6 --- /dev/null +++ b/rust/Dockerfile @@ -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 diff --git a/rust/build.rs b/rust/build.rs new file mode 100644 index 0000000..e6a8685 --- /dev/null +++ b/rust/build.rs @@ -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."); +} diff --git a/rust/docker.bash b/rust/docker.bash new file mode 100755 index 0000000..4d4ba8c --- /dev/null +++ b/rust/docker.bash @@ -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" diff --git a/rust/examples/donate.rs b/rust/examples/donate.rs new file mode 100644 index 0000000..399e1df --- /dev/null +++ b/rust/examples/donate.rs @@ -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"); + } +} diff --git a/rust/examples/empty.rs b/rust/examples/empty.rs new file mode 100644 index 0000000..dcf2691 --- /dev/null +++ b/rust/examples/empty.rs @@ -0,0 +1,9 @@ +fn empty() { + rt::task::drop_privilege(); + rt::stop(); +} + +fn main() { + rt::task!(empty, rt::task::STACK_MIN, 1); + rt::start(); +} diff --git a/rust/examples/float.rs b/rust/examples/float.rs new file mode 100644 index 0000000..2fc0a11 --- /dev/null +++ b/rust/examples/float.rs @@ -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(); +} diff --git a/rust/examples/mutex.rs b/rust/examples/mutex.rs new file mode 100644 index 0000000..8e31e51 --- /dev/null +++ b/rust/examples/mutex.rs @@ -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"); + } +} diff --git a/rust/examples/notify.rs b/rust/examples/notify.rs new file mode 100644 index 0000000..4d2eae7 --- /dev/null +++ b/rust/examples/notify.rs @@ -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"); + } +} diff --git a/rust/examples/once.rs b/rust/examples/once.rs new file mode 100644 index 0000000..cf9cbaf --- /dev/null +++ b/rust/examples/once.rs @@ -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"); + } +} diff --git a/rust/examples/queue.rs b/rust/examples/queue.rs new file mode 100644 index 0000000..9578b38 --- /dev/null +++ b/rust/examples/queue.rs @@ -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"); + } +} diff --git a/rust/examples/rwlock.rs b/rust/examples/rwlock.rs new file mode 100644 index 0000000..3e77dae --- /dev/null +++ b/rust/examples/rwlock.rs @@ -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"); + } +} diff --git a/rust/examples/semaphore.rs b/rust/examples/semaphore.rs new file mode 100644 index 0000000..f3b8479 --- /dev/null +++ b/rust/examples/semaphore.rs @@ -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"); + } +} diff --git a/rust/examples/simple.rs b/rust/examples/simple.rs new file mode 100644 index 0000000..1378339 --- /dev/null +++ b/rust/examples/simple.rs @@ -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(); +} diff --git a/rust/examples/sleep.rs b/rust/examples/sleep.rs new file mode 100644 index 0000000..cc9a8c3 --- /dev/null +++ b/rust/examples/sleep.rs @@ -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"); + } +} diff --git a/rust/examples/water/semaphore.rs b/rust/examples/water/semaphore.rs new file mode 100644 index 0000000..bb6a67d --- /dev/null +++ b/rust/examples/water/semaphore.rs @@ -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"); diff --git a/rust/examples/water/water.rs b/rust/examples/water/water.rs new file mode 100644 index 0000000..a9a6fcc --- /dev/null +++ b/rust/examples/water/water.rs @@ -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"); + } +} diff --git a/rust/src/bindings.rs b/rust/src/bindings.rs new file mode 100644 index 0000000..d3bb5c1 --- /dev/null +++ b/rust/src/bindings.rs @@ -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")); diff --git a/rust/src/cycle.rs b/rust/src/cycle.rs new file mode 100644 index 0000000..bad13fd --- /dev/null +++ b/rust/src/cycle.rs @@ -0,0 +1,5 @@ +use crate::bindings::rt_cycle; + +pub fn cycle() -> u32 { + unsafe { rt_cycle() } +} diff --git a/rust/src/lib.rs b/rust/src/lib.rs new file mode 100644 index 0000000..13d16f8 --- /dev/null +++ b/rust/src/lib.rs @@ -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() } +} diff --git a/rust/src/list.rs b/rust/src/list.rs new file mode 100644 index 0000000..be5b9f5 --- /dev/null +++ b/rust/src/list.rs @@ -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 } +} diff --git a/rust/src/sync/barrier.rs b/rust/src/sync/barrier.rs new file mode 100644 index 0000000..bbdfb11 --- /dev/null +++ b/rust/src/sync/barrier.rs @@ -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, + _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()); + } +} diff --git a/rust/src/sync/condvar.rs b/rust/src/sync/condvar.rs new file mode 100644 index 0000000..73bea3b --- /dev/null +++ b/rust/src/sync/condvar.rs @@ -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, + _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(&self, guard: &MutexGuard) { + unsafe { rt_cond_wait(self.c.get(), guard.ptr()) } + } + + pub fn timedwait<'a, T>( + &self, + guard: MutexGuard<'a, T>, + ticks: Utick, + ) -> Option> { + 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(); + } +} diff --git a/rust/src/sync/mod.rs b/rust/src/sync/mod.rs new file mode 100644 index 0000000..112bdad --- /dev/null +++ b/rust/src/sync/mod.rs @@ -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; diff --git a/rust/src/sync/mutex.rs b/rust/src/sync/mutex.rs new file mode 100644 index 0000000..6bcdc07 --- /dev/null +++ b/rust/src/sync/mutex.rs @@ -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 { + m: UnsafeCell, + _pin_marker: PhantomPinned, + data: UnsafeCell, +} + +unsafe impl Send for Mutex {} +unsafe impl Sync for Mutex {} +impl UnwindSafe for Mutex {} +impl RefUnwindSafe for Mutex {} + +impl Mutex { + pub const fn init(mutex: &'static Mutex, t: T) -> Mutex { + 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 Mutex { + pub fn lock(&self) -> MutexGuard<'_, T> { + unsafe { + rt_mutex_lock(self.m.get()); + } + MutexGuard::new(self) + } + + pub fn try_lock(&self) -> Option> { + if unsafe { rt_mutex_trylock(self.m.get()) } { + Some(MutexGuard::new(self)) + } else { + None + } + } + + pub fn timed_lock(&self, ticks: Utick) -> Option> { + 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, + _unsend_marker: PhantomData<*const ()>, +} + +impl MutexGuard<'_, T> { + fn new(lock: &Mutex) -> MutexGuard { + MutexGuard { + lock, + _unsend_marker: PhantomData, + } + } +} + +// Not supported yet, so using the _unsend_marker instead. +//impl !Send for MutexGuard<'_, T> {} + +unsafe impl Sync for MutexGuard<'_, T> {} + +impl 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 Deref for MutexGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.lock.data.get() } + } +} + +impl DerefMut for MutexGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.lock.data.get() } + } +} + +impl 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); + } +} diff --git a/rust/src/sync/notify.rs b/rust/src/sync/notify.rs new file mode 100644 index 0000000..1680778 --- /dev/null +++ b/rust/src/sync/notify.rs @@ -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, + _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 { + 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 { + 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 { + 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 { + 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); + } +} diff --git a/rust/src/sync/once.rs b/rust/src/sync/once.rs new file mode 100644 index 0000000..d3ad193 --- /dev/null +++ b/rust/src/sync/once.rs @@ -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(&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()); + } +} diff --git a/rust/src/sync/queue.rs b/rust/src/sync/queue.rs new file mode 100644 index 0000000..4c3bff3 --- /dev/null +++ b/rust/src/sync/queue.rs @@ -0,0 +1,151 @@ +use core::{ + cell::UnsafeCell, + ffi::{c_uchar, c_void}, + marker::{PhantomData, PhantomPinned}, + mem::{size_of, MaybeUninit}, + panic::{RefUnwindSafe, UnwindSafe}, +}; + +use crate::{ + bindings::{ + rt_queue, rt_queue_peek, rt_queue_pop, rt_queue_push, rt_queue_timedpeek, + rt_queue_timedpop, rt_queue_timedpush, rt_queue_trypeek, rt_queue_trypop, rt_queue_trypush, + }, + sync::semaphore::c_sem_init, + tick::Utick, +}; + +pub struct Queue { + q: UnsafeCell, + _phantom_elem: PhantomData, + _pin_marker: PhantomPinned, +} + +unsafe impl Send for Queue {} +unsafe impl Sync for Queue {} +impl UnwindSafe for Queue {} +impl RefUnwindSafe for Queue {} + +impl Queue { + pub const fn init( + queue: &'static Queue, + slots: &'static [c_uchar; N], + data: &'static [T; N], + ) -> Queue { + let q = unsafe { &*(queue.q.get() as *const rt_queue) }; + Queue { + q: UnsafeCell::new(rt_queue { + push_sem: c_sem_init(&q.push_sem, N as i32, i32::max_value()), + pop_sem: c_sem_init(&q.pop_sem, 0, i32::max_value()), + enq: 0, + deq: 0, + slots: slots.as_ptr() as *mut c_uchar, + data: data.as_ptr() as *mut c_void, + num_elems: N, + elem_size: size_of::(), + }), + _phantom_elem: PhantomData, + _pin_marker: PhantomPinned, + } + } + + pub fn push(&self, elem: T) { + unsafe { rt_queue_push(self.q.get(), &elem as *const T as *const c_void) } + } + + pub fn pop(&self) -> T { + let mut elem = MaybeUninit::::uninit(); + unsafe { + rt_queue_pop(self.q.get(), elem.as_mut_ptr() as *mut c_void); + elem.assume_init() + } + } + + pub fn peek(&self) -> T { + let mut elem = MaybeUninit::::uninit(); + unsafe { + rt_queue_peek(self.q.get(), elem.as_mut_ptr() as *mut c_void); + elem.assume_init() + } + } + + pub fn try_push(&self, elem: T) -> Result<(), T> { + if unsafe { rt_queue_trypush(self.q.get(), &elem as *const T as *const c_void) } { + Ok(()) + } else { + Err(elem) + } + } + + pub fn try_pop(&self) -> Option { + let mut elem = MaybeUninit::::uninit(); + if unsafe { rt_queue_trypop(self.q.get(), elem.as_mut_ptr() as *mut c_void) } { + Some(unsafe { elem.assume_init() }) + } else { + None + } + } + + pub fn try_peek(&self) -> Option { + let mut elem = MaybeUninit::::uninit(); + if unsafe { rt_queue_trypeek(self.q.get(), elem.as_mut_ptr() as *mut c_void) } { + Some(unsafe { elem.assume_init() }) + } else { + None + } + } + + pub fn timed_push(&self, elem: T, ticks: Utick) -> Result<(), T> { + if unsafe { rt_queue_timedpush(self.q.get(), &elem as *const T as *const c_void, ticks) } { + Ok(()) + } else { + Err(elem) + } + } + + pub fn timed_pop(&self, ticks: Utick) -> Option { + let mut elem = MaybeUninit::::uninit(); + if unsafe { rt_queue_timedpop(self.q.get(), elem.as_mut_ptr() as *mut c_void, ticks) } { + Some(unsafe { elem.assume_init() }) + } else { + None + } + } + + pub fn timed_peek(&self, ticks: Utick) -> Option { + let mut elem = MaybeUninit::::uninit(); + if unsafe { rt_queue_timedpeek(self.q.get(), elem.as_mut_ptr() as *mut c_void, ticks) } { + Some(unsafe { elem.assume_init() }) + } else { + None + } + } +} + +#[macro_export] +macro_rules! queue { + ($name: ident, $type: ty, $num: literal) => { + $crate::paste::paste! { + static mut [< $name _SLOTS >]: [core::ffi::c_uchar; $num] = [0u8; $num]; + static mut [< $name _DATA >]: [$type; $num] = + unsafe { core::mem::transmute([0u8; core::mem::size_of::<$type>() * $num]) }; + static $name: $crate::sync::Queue<$type> = $crate::sync::Queue::init( + &$name, + unsafe { &[< $name _SLOTS >] }, + unsafe { &[< $name _DATA >] }, + ); + } + }; +} + +#[cfg(test)] +mod tests { + queue!(QUEUE, i32, 10); + + #[test] + fn fast_path() { + QUEUE.push(1); + assert!(QUEUE.pop() == 1); + assert!(QUEUE.try_pop().is_none()); + } +} diff --git a/rust/src/sync/rwlock.rs b/rust/src/sync/rwlock.rs new file mode 100644 index 0000000..be1384e --- /dev/null +++ b/rust/src/sync/rwlock.rs @@ -0,0 +1,156 @@ +use core::{ + cell::UnsafeCell, + marker::{PhantomData, PhantomPinned}, + ops::{Deref, DerefMut}, + panic::{RefUnwindSafe, UnwindSafe}, +}; + +use crate::{ + bindings::{ + rt_cond, rt_mutex, rt_rwlock, rt_rwlock_rdlock, rt_rwlock_unlock, rt_rwlock_wrlock, + }, + list::list_init, + sync::semaphore::c_sem_init, +}; + +pub struct RwLock { + l: UnsafeCell, + _pin_marker: PhantomPinned, + data: UnsafeCell, +} + +unsafe impl Send for RwLock {} +unsafe impl Sync for RwLock {} +impl UnwindSafe for RwLock {} +impl RefUnwindSafe for RwLock {} + +impl RwLock { + pub const fn init(lock: &'static RwLock, t: T) -> RwLock { + let l = unsafe { &*(lock.l.get() as *const rt_rwlock) }; + RwLock { + l: UnsafeCell::new(rt_rwlock { + m: rt_mutex { + holder: 0, + wait_list: list_init(&l.m.wait_list), + list: list_init(&l.m.list), + }, + rcond: rt_cond { + sem: c_sem_init(&l.rcond.sem, 0, 0), + }, + wcond: rt_cond { + sem: c_sem_init(&l.wcond.sem, 0, 0), + }, + num_readers: 0, + num_writers: 0, + }), + _pin_marker: PhantomPinned, + data: UnsafeCell::new(t), + } + } +} + +impl RwLock { + pub fn read(&self) -> RwLockReadGuard<'_, T> { + unsafe { + rt_rwlock_rdlock(self.l.get()); + } + RwLockReadGuard::new(self) + } + + pub fn write(&self) -> RwLockWriteGuard<'_, T> { + unsafe { + rt_rwlock_wrlock(self.l.get()); + } + RwLockWriteGuard::new(self) + } + + // Probably not useful because this RwLock must be static. + pub fn get_mut(&mut self) -> &mut T { + self.data.get_mut() + } +} + +pub struct RwLockReadGuard<'a, T: ?Sized + 'a> { + lock: &'a RwLock, + _unsend_marker: PhantomData<*const ()>, +} + +impl RwLockReadGuard<'_, T> { + fn new(lock: &RwLock) -> RwLockReadGuard { + RwLockReadGuard { + lock, + _unsend_marker: PhantomData, + } + } +} + +unsafe impl Sync for RwLockReadGuard<'_, T> {} + +impl Deref for RwLockReadGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.lock.data.get() } + } +} + +impl Drop for RwLockReadGuard<'_, T> { + #[inline] + fn drop(&mut self) { + unsafe { rt_rwlock_unlock(self.lock.l.get()) } + } +} + +pub struct RwLockWriteGuard<'a, T: ?Sized + 'a> { + lock: &'a RwLock, + _unsend_marker: PhantomData<*const ()>, +} + +impl RwLockWriteGuard<'_, T> { + fn new(lock: &RwLock) -> RwLockWriteGuard { + RwLockWriteGuard { + lock, + _unsend_marker: PhantomData, + } + } +} + +unsafe impl Sync for RwLockWriteGuard<'_, T> {} + +impl Deref for RwLockWriteGuard<'_, T> { + type Target = T; + + fn deref(&self) -> &T { + unsafe { &*self.lock.data.get() } + } +} + +impl DerefMut for RwLockWriteGuard<'_, T> { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.lock.data.get() } + } +} + +impl Drop for RwLockWriteGuard<'_, T> { + #[inline] + fn drop(&mut self) { + unsafe { rt_rwlock_unlock(self.lock.l.get()) } + } +} + +#[macro_export] +macro_rules! rwlock { + ($name: ident, $type: ty, $data: expr) => { + static $name: $crate::sync::RwLock<$type> = $crate::sync::RwLock::init(&$name, $data); + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn fast_path() { + rwlock!(LOCK, i32, 0); + *LOCK.write() += 1; + assert!(*LOCK.read() == 1); + } +} diff --git a/rust/src/sync/semaphore.rs b/rust/src/sync/semaphore.rs new file mode 100644 index 0000000..22f0947 --- /dev/null +++ b/rust/src/sync/semaphore.rs @@ -0,0 +1,147 @@ +use core::{ + cell::UnsafeCell, + ffi::c_int, + marker::PhantomPinned, + mem::{size_of, transmute}, + panic::{RefUnwindSafe, UnwindSafe}, + ptr::null_mut, +}; + +use crate::{ + bindings::{ + rt_atomic_int, rt_sem, rt_sem_post, rt_sem_post_n, rt_sem_timedwait, rt_sem_trywait, + rt_sem_wait, rt_syscall, rt_syscall_args, rt_syscall_args_sem_post, rt_syscall_record, + }, + list::list_init, + tick::Utick, +}; + +pub struct Semaphore { + s: UnsafeCell, + _pin_marker: PhantomPinned, +} + +unsafe impl Send for Semaphore {} +unsafe impl Sync for Semaphore {} +impl UnwindSafe for Semaphore {} +impl RefUnwindSafe for Semaphore {} + +// Also used by cond and queue. +pub(crate) const fn c_sem_init(s: &'static rt_sem, value: i32, max_value: i32) -> rt_sem { + rt_sem { + value: value as rt_atomic_int, + max_value: max_value as c_int, + wait_list: list_init(&s.wait_list), + num_waiters: 0, + post_pending: 0, + post_record: rt_syscall_record { + next: null_mut(), + args: unsafe { + let mut x: rt_syscall_args = transmute([0u8; size_of::()]); + x.sem_post = rt_syscall_args_sem_post { + sem: s as *const rt_sem as *mut rt_sem, + n: 1, + }; + x + }, + syscall: rt_syscall::RT_SYSCALL_SEM_POST, + }, + } +} + +impl Semaphore { + pub const fn init(sem: &'static Semaphore, value: i32, max_value: i32) -> Semaphore { + let s = unsafe { &*(sem.s.get() as *const rt_sem) }; + Semaphore { + s: UnsafeCell::new(c_sem_init(s, value, max_value)), + _pin_marker: PhantomPinned, + } + } + + pub fn post(&self) { + unsafe { rt_sem_post(self.s.get()) } + } + + pub fn post_n(&self, n: i32) { + unsafe { rt_sem_post_n(self.s.get(), n) } + } + + pub fn wait(&self) { + unsafe { rt_sem_wait(self.s.get()) } + } + + pub fn try_wait(&self) -> bool { + unsafe { rt_sem_trywait(self.s.get()) } + } + + pub fn timed_wait(&self, ticks: Utick) -> bool { + unsafe { rt_sem_timedwait(self.s.get(), ticks) } + } + + pub fn acquire(&self) { + self.wait() + } + + pub fn release(&self) { + self.post() + } + + pub fn access(&self) -> SemaphoreGuard { + self.acquire(); + SemaphoreGuard { sem: self } + } +} + +pub struct SemaphoreGuard<'a> { + sem: &'a Semaphore, +} + +impl<'a> Drop for SemaphoreGuard<'a> { + fn drop(&mut self) { + self.sem.release(); + } +} + +#[macro_export] +macro_rules! semaphore { + ($name: ident, $count: expr, $max: expr) => { + static $name: $crate::sync::Semaphore = $crate::sync::Semaphore::init(&$name, $count, $max); + }; + + ($name: ident, $count: expr) => { + $crate::semaphore!($name, $count, i32::MAX); + }; + + ($name: ident) => { + $crate::semaphore!($name, 0, i32::MAX); + }; +} + +#[macro_export] +macro_rules! semaphore_binary { + ($name: ident) => { + $crate::semaphore!($name, 0, 1); + }; +} + +#[cfg(test)] +mod tests { + #[test] + fn fast_path() { + semaphore!(SEM); + SEM.post(); + SEM.post(); + assert!(SEM.try_wait()); + assert!(SEM.try_wait()); + assert!(!SEM.try_wait()); + } + + #[test] + fn binary_fast_path() { + semaphore_binary!(SEM); + SEM.post(); + SEM.post(); + assert!(SEM.try_wait()); + assert!(!SEM.try_wait()); + } +} diff --git a/rust/src/task.rs b/rust/src/task.rs new file mode 100644 index 0000000..992b531 --- /dev/null +++ b/rust/src/task.rs @@ -0,0 +1,121 @@ +use core::{ + cell::UnsafeCell, + ffi::{c_void, CStr}, + marker::PhantomPinned, + mem::{size_of, transmute}, + panic::{RefUnwindSafe, UnwindSafe}, + ptr::null_mut, + str, +}; + +use crate::{ + bindings::{ + rt_context_init, rt_syscall, rt_syscall_args, rt_syscall_pend, rt_syscall_push, + rt_syscall_record, rt_task, rt_task_drop_privilege, rt_task_enable_fp, rt_task_exit, + rt_task_name, rt_task_sleep, rt_task_sleep_periodic, rt_task_state, rt_task_yield, + RT_STACK_MIN, + }, + list::list_init, + tick::Utick, +}; + +pub const STACK_MIN: usize = RT_STACK_MIN as usize; + +pub struct Task { + t: UnsafeCell, + _pin_marker: PhantomPinned, +} + +unsafe impl Send for Task {} +unsafe impl Sync for Task {} +impl UnwindSafe for Task {} +impl RefUnwindSafe for Task {} + +pub fn yield_() { + unsafe { rt_task_yield() } +} + +pub fn sleep(ticks: Utick) { + unsafe { rt_task_sleep(ticks) } +} + +pub fn sleep_periodic(last_wake_tick: &mut Utick, period: Utick) { + unsafe { rt_task_sleep_periodic(last_wake_tick, period) } +} + +pub fn exit() -> ! { + unsafe { rt_task_exit() } +} + +pub fn name() -> &'static str { + unsafe { str::from_utf8_unchecked(CStr::from_ptr(rt_task_name()).to_bytes()) } +} + +pub fn enable_fp() { + unsafe { rt_task_enable_fp() } +} + +pub fn drop_privilege() { + unsafe { rt_task_drop_privilege() } +} + +impl Task { + pub const fn init(task: &'static Task, name: &'static CStr, priority: u32) -> Task { + let t = unsafe { &*(task.t.get() as *const rt_task) }; + Task { + t: UnsafeCell::new(rt_task { + list: list_init(&t.list), + sleep_list: list_init(&t.sleep_list), + ctx: null_mut(), + list_head: null_mut(), + record: rt_syscall_record { + next: null_mut(), + args: unsafe { transmute([0u8; size_of::()]) }, + syscall: rt_syscall::RT_SYSCALL_TASK_READY, + }, + priority, + base_priority: priority, + state: rt_task_state::RT_TASK_STATE_EXITED, + wake_tick: 0, + mutex_list: list_init(&t.mutex_list), + name: name.as_ptr(), + }), + _pin_marker: PhantomPinned, + } + } + + pub fn start(&mut self, f: extern "C" fn(), stack: &mut [u8]) { + let t = self.t.get_mut(); + unsafe { + t.ctx = rt_context_init(Some(f), stack.as_mut_ptr() as *mut c_void, stack.len()); + rt_syscall_push(&mut t.record); + rt_syscall_pend(); + } + } +} + +#[macro_export] +macro_rules! task { + ($fn: ident $params: tt, $stack_size: expr, $priority: expr) => { + $crate::paste::paste! { + { + static mut [< $fn:upper _TASK >]: rt::task::Task = rt::task::Task::init( + unsafe { &[< $fn:upper _TASK >] }, + unsafe { core::ffi::CStr::from_bytes_with_nul_unchecked(core::concat!(core::stringify!($fn), "\0").as_bytes()) }, + $priority, + ); + static mut [< $fn:upper _TASK_STACK >]: [u8; $stack_size] = [0u8; $stack_size]; + extern "C" fn [< $fn _c >]() { + $fn $params + } + unsafe { + [< $fn:upper _TASK >].start([< $fn _c >], [< $fn:upper _TASK_STACK >].as_mut_slice()); + } + } + } + }; + + ($fn: ident, $stack_size: expr, $priority: expr) => { + rt::task!($fn(), $stack_size, $priority); + }; +} diff --git a/rust/src/tick.rs b/rust/src/tick.rs new file mode 100644 index 0000000..178357b --- /dev/null +++ b/rust/src/tick.rs @@ -0,0 +1,7 @@ +use crate::bindings::rt_tick_count; + +pub type Utick = core::ffi::c_ulong; + +pub fn count() -> Utick { + unsafe { rt_tick_count() } +} diff --git a/rust/test.bash b/rust/test.bash new file mode 100755 index 0000000..f4e3dba --- /dev/null +++ b/rust/test.bash @@ -0,0 +1,13 @@ +#!/bin/bash + +set -e + +cargo build --release + +trap 'if [ "$?" != "0" ]; then echo "test failed!"; fi' EXIT + +examples=(donate empty float mutex notify once queue rwlock semaphore simple sleep water-semaphore) + +for e in "${examples[@]}"; do + cargo run --release --example "$e" +done diff --git a/rust/wrapper.h b/rust/wrapper.h new file mode 100644 index 0000000..4b62bc7 --- /dev/null +++ b/rust/wrapper.h @@ -0,0 +1,13 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include