Skip to content

Commit

Permalink
feat: start threads defined with a new thread macro
Browse files Browse the repository at this point in the history
  • Loading branch information
ROMemories authored and kaspar030 committed Feb 13, 2024
1 parent 4494df8 commit 199136c
Show file tree
Hide file tree
Showing 16 changed files with 273 additions and 6 deletions.
1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ members = [
"src/riot-rs-boards/nrf52840dk",
"src/riot-rs-boards/nucleo-f401re",
"src/riot-rs-chips",
"src/riot-rs-macros",
"src/lib/*",
"examples/*",
]
Expand Down
1 change: 1 addition & 0 deletions examples/laze.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,4 @@ subdirs:
- riot-app
- rust-gcoap
- riot-wrappers-mutex
- threading
10 changes: 10 additions & 0 deletions examples/threading/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
[package]
name = "threading"
version = "0.1.0"
authors.workspace = true
edition.workspace = true
publish = false

[dependencies]
riot-rs = { path = "../../src/riot-rs", features = ["threading"] }
riot-rs-boards = { path = "../../src/riot-rs-boards" }
13 changes: 13 additions & 0 deletions examples/threading/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
# threading

## About

This application demonstrates basic threading.

## How to run

In this folder, run

laze build -b nrf52840dk run

The application will start two threads and print a message from each thread.
4 changes: 4 additions & 0 deletions examples/threading/laze.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
apps:
- name: threading
selects:
- ?release
24 changes: 24 additions & 0 deletions examples/threading/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
#![no_main]
#![no_std]
#![feature(type_alias_impl_trait)]
#![feature(used_with_arg)]

use riot_rs::rt::debug::println;

#[riot_rs::thread]
fn thread0() {
println!("Hello from thread 0");
}

#[riot_rs::thread(stacksize = 4096, priority = 2)]
fn thread1() {
println!("Hello from thread 1");
}

#[no_mangle]
fn riot_main() {
println!(
"Hello from riot_main()! Running on a {} board.",
riot_rs::buildinfo::BOARD
);
}
16 changes: 16 additions & 0 deletions src/riot-rs-macros/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
[package]
name = "riot-rs-macros"
version.workspace = true
authors.workspace = true
edition.workspace = true
repository.workspace = true

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
proc-macro-crate = "3.1.0"
quote = "1.0.35"
syn = { version = "2.0.47", features = ["full"] }

[lib]
proc-macro = true
3 changes: 3 additions & 0 deletions src/riot-rs-macros/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#![deny(clippy::pedantic)]

include!("thread.rs");
164 changes: 164 additions & 0 deletions src/riot-rs-macros/src/thread.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use proc_macro::TokenStream;
use quote::{format_ident, quote};
use syn::{meta::ParseNestedMeta, ItemFn, LitInt};

const RIOT_RS_CRATE_NAME: &str = "riot-rs";

// TODO: document default values (which may be platform-dependent)
// TODO: document valid values
/// Runs the function decorated with this attribute macro as a separate thread.
///
/// # Parameters
///
/// - `stacksize`: (*optional*) the size of the stack allocated to the thread (in bytes)
/// - `priority`: (*optional*) the thread's priority
///
/// # Examples
///
/// This starts a thread with default values:
///
/// ```ignore
/// #[riot_rs::thread]
/// fn print_hello_world() {
/// println!("Hello world!");
/// }
/// ```
///
/// This starts a thread with a stack size of 1024 bytes and a priority of 2:
///
/// ```ignore
/// #[riot_rs::thread(stacksize = 1024, priority = 2)]
/// fn print_hello_world() {
/// println!("Hello world!");
/// }
/// ```
///
/// # Panics
///
/// This macro panics when the `riot-rs` crate cannot be found as a dependency of the crate where
/// this macro is used.
#[proc_macro_attribute]
pub fn thread(args: TokenStream, item: TokenStream) -> TokenStream {
let mut attrs = ThreadAttributes::default();
let thread_parser = syn::meta::parser(|meta| attrs.parse(&meta));
syn::parse_macro_input!(args with thread_parser);

let thread_function = syn::parse_macro_input!(item as ItemFn);

let no_mangle_attr = if attrs.no_mangle {
quote! {#[no_mangle]}
} else {
quote! {}
};

let fn_name = thread_function.sig.ident.clone();
let slice_fn_name_ident = format_ident!("__start_thread_{fn_name}");
let ThreadParameters {
stack_size,
priority,
} = ThreadParameters::from(attrs);

let this_crate = proc_macro_crate::crate_name(RIOT_RS_CRATE_NAME)
.unwrap_or_else(|_| panic!("{RIOT_RS_CRATE_NAME} should be present in `Cargo.toml`"));
let this_crate = match this_crate {
proc_macro_crate::FoundCrate::Itself => {
panic!(
"{} cannot be used as a dependency of itself",
env!("CARGO_CRATE_NAME"),
);
}
proc_macro_crate::FoundCrate::Name(this_crate) => format_ident!("{}", this_crate),
};

let expanded = quote! {
#no_mangle_attr
#[inline(always)]
#thread_function

#[#this_crate::linkme::distributed_slice(#this_crate::thread::THREAD_FNS)]
#[linkme(crate = #this_crate::linkme)]
fn #slice_fn_name_ident() {
fn trampoline(_arg: ()) {
#fn_name();
}
let stack = #this_crate::static_cell::make_static!([0u8; #stack_size as usize]);
#this_crate::thread::thread_create(trampoline, (), stack, #priority);
}
};

TokenStream::from(expanded)
}

struct ThreadParameters {
stack_size: u64,
priority: u8,
}

impl Default for ThreadParameters {
fn default() -> Self {
// TODO: proper values
Self {
stack_size: 2048,
priority: 1,
}
}
}

impl From<ThreadAttributes> for ThreadParameters {
fn from(attrs: ThreadAttributes) -> Self {
let default = Self::default();

let stack_size = attrs.stack_size.map_or(default.stack_size, |l| {
parse_base10_or_panic(&l, "stack_size")
});

let priority = attrs
.priority
.map_or(default.priority, |l| parse_base10_or_panic(&l, "priority"));

Self {
stack_size,
priority,
}
}
}

fn parse_base10_or_panic<I>(lit_int: &LitInt, attr: &str) -> I
where
I: core::str::FromStr,
<I as core::str::FromStr>::Err: std::fmt::Display,
{
if let Ok(int) = lit_int.base10_parse() {
assert!(
lit_int.suffix().is_empty(),
"`{attr}` must be a base-10 integer without a suffix",
);
int
} else {
panic!("`{attr}` must be a base-10 integer");
}
}

#[derive(Default)]
struct ThreadAttributes {
stack_size: Option<LitInt>,
priority: Option<LitInt>,
no_mangle: bool,
}

impl ThreadAttributes {
fn parse(&mut self, meta: &ParseNestedMeta) -> syn::Result<()> {
if meta.path.is_ident("stacksize") {
self.stack_size = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("priority") {
self.priority = Some(meta.value()?.parse()?);
Ok(())
} else if meta.path.is_ident("no_mangle") {
self.no_mangle = true;
Ok(())
} else {
Err(meta.error("unsupported parameter"))
}
}
}
1 change: 1 addition & 0 deletions src/riot-rs-rt/linkme.x
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ SECTIONS {
linkm2_INIT_FUNCS : { *(linkm2_INIT_FUNCS) } > FLASH
linkme_EMBASSY_TASKS : { *(linkme_EMBASSY_TASKS) } > FLASH
linkm2_EMBASSY_TASKS : { *(linkm2_EMBASSY_TASKS) } > FLASH
linkm2_THREAD_FNS : { *(linkm2_THREAD_FNS) } > FLASH
}

INSERT AFTER .rodata
6 changes: 4 additions & 2 deletions src/riot-rs-rt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -76,8 +76,10 @@ fn startup() -> ! {

#[cfg(feature = "threading")]
{
// start threading
threading::init();
// SAFETY: this function must not be called more than once
unsafe {
threading::start();
}
}

#[cfg(not(feature = "threading"))]
Expand Down
20 changes: 16 additions & 4 deletions src/riot-rs-rt/src/threading.rs
Original file line number Diff line number Diff line change
@@ -1,21 +1,33 @@
use riot_rs_threads::{start_threading, thread_create};
use riot_rs_threads::{start_threading, thread_create, THREAD_FNS};

static mut MAIN_STACK: [u8; 2048] = [0; 2048];
const MAIN_STACK_SIZE: usize = 2048;

extern "Rust" {
fn riot_main();
}

fn main_trampoline(_arg: usize) {
// SAFETY: FFI call to a Rust function
unsafe {
riot_main();
}
}

pub(crate) fn init() -> ! {
/// # Safety
///
/// The caller must ensure that this function is only called once.
pub unsafe fn start() -> ! {
for thread_fn in THREAD_FNS {
thread_fn();
}

let mut main_stack: [u8; MAIN_STACK_SIZE] = [0; MAIN_STACK_SIZE];
thread_create(main_trampoline, 0, &mut main_stack, 0);

// SAFETY: this function must only be called once, enforced by caller
unsafe {
thread_create(main_trampoline, 0, &mut MAIN_STACK, 0);
start_threading();
}

loop {}
}
1 change: 1 addition & 0 deletions src/riot-rs-threads/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ repository.workspace = true
[dependencies]
cfg-if.workspace = true
critical-section.workspace = true
linkme = { workspace = true }
riot-rs-runqueue.workspace = true

[target.'cfg(context = "cortex-m")'.dependencies]
Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs-threads/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![cfg_attr(not(test), no_std)]
#![feature(inline_const)]
#![feature(naked_functions)]
#![feature(used_with_arg)]

use critical_section::CriticalSection;

Expand Down Expand Up @@ -31,6 +32,11 @@ pub const THREADS_NUMOF: usize = 16;

pub(crate) static THREADS: EnsureOnce<Threads> = EnsureOnce::new(Threads::new());

pub type ThreadFn = fn();

#[linkme::distributed_slice]
pub static THREAD_FNS: [ThreadFn] = [..];

/// Struct holding all scheduler state
pub struct Threads {
/// global thread runqueue
Expand Down
3 changes: 3 additions & 0 deletions src/riot-rs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,15 @@ authors.workspace = true
edition.workspace = true

[dependencies]
linkme = { workspace = true }
riot-build = { path = "../riot-build", features = [ "riot-rs-core"], optional = true }
riot-rs-rt = { path = "../riot-rs-rt" }
riot-rs-threads = { path = "../riot-rs-threads", optional = true }
riot-rs-boards = { path = "../riot-rs-boards" }
riot-rs-buildinfo = { path = "../riot-rs-buildinfo" }
riot-rs-embassy = { path = "../riot-rs-embassy" }
riot-rs-macros = { path = "../riot-rs-macros" }
static_cell = { workspace = true }

[features]
newlib = [ "riot-build", "riot-build/newlib" ]
Expand Down
6 changes: 6 additions & 0 deletions src/riot-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@ pub use riot_rs_buildinfo as buildinfo;
pub use riot_rs_embassy::{self as embassy, define_peripherals};
pub use riot_rs_rt as rt;

#[cfg(feature = "threading")]
pub use riot_rs_macros::thread;
#[cfg(feature = "threading")]
pub use riot_rs_threads as thread;

// These are used by proc-macros we provide
pub use linkme;
pub use static_cell;

// ensure this gets linked
use riot_rs_boards as _;

0 comments on commit 199136c

Please sign in to comment.