Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Proc-macro thread declaration #67

Draft
wants to merge 20 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions dt-rust.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
- "nordic,nrf52-flash-controller"
- "nordic,nrf51-flash-controller"
- "raspberrypi,pico-flash-controller"
- "st,stm32g4-flash-controller"
level: 0
actions:
- type: instance
Expand Down
2 changes: 2 additions & 0 deletions etc/platforms.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
-p qemu_cortex_m0
-p qemu_cortex_m3
-p qemu_riscv32
-p qemu_riscv32/qemu_virt_riscv32/smp
-p qemu_riscv64
-p qemu_riscv64/qemu_virt_riscv64/smp
-p m2gl025_miv
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(work_philosophers)
project(async_philosophers)

rust_cargo_application()
36 changes: 36 additions & 0 deletions samples/async-philosophers/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Copyright (c) 2024 Linaro LTD
# SPDX-License-Identifier: Apache-2.0

[package]
# This must be rustapp for now.
name = "rustapp"
version = "0.1.0"
edition = "2021"
description = "A sample hello world application in Rust"
license = "Apache-2.0 or MIT"

[lib]
crate-type = ["staticlib"]

[dependencies]
zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr"] }
static_cell = "2.1"

embassy-executor = { version = "0.7.0", features = ["log", "task-arena-size-2048"] }
embassy-sync = "0.6.2"

# For real builds, you should figure out your target's tick rate and set the appropriate feature,
# like in these examples. Without this, embassy-time will assume a 1Mhz tick rate, and every time
# operation will involve a conversion.
embassy-time = "0.4.0"
# embassy-time = { version = "0.4.0", features = ["tick-hz-10_000"] }
# embassy-time = { version = "0.4.0", features = ["tick-hz-100"] }

# Dependencies that are used by build.rs.
[build-dependencies]
zephyr-build = "0.1.0"

[profile.release]
debug-assertions = true
overflow-checks = true
debug = true
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CONFIG_MAIN_STACK_SIZE=8192
CONFIG_SYSTEM_WORKQUEUE_STACK_SIZE=4096

CONFIG_POLL=y
CONFIG_STACK_CANARIES=y

# CONFIG_DEBUG=y

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
sample:
description: Philosphers, in Rust
name: workq philosophers rust
description: Async Philosphers, in Rust
name: async philosophers rust
common:
harness: console
harness_config:
Expand All @@ -13,7 +13,9 @@ common:
- qemu_cortex_m0
- qemu_cortex_m3
- qemu_riscv32
- qemu_riscv32/qemu_virt_riscv32/smp
- qemu_riscv64
- qemu_riscv64/qemu_virt_riscv64/smp
- nrf52840dk/nrf52840
tests:
sample.rust.work-philosopher:
Expand Down
98 changes: 98 additions & 0 deletions samples/async-philosophers/src/async_sem.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
//! Async Semaphore based demo
//!
//! This implementation on the dining philosopher problem uses Zephyr semaphores to represent the
//! forks. Each philosopher dines as per the algorithm a number of times, and when the are all
//! finished, the test is considered successful. Deadlock will result in the primary thread not
//! completing.
//!
//! Notably, this uses Rc and RefCell along with spawn_local to demonstrate that multiple async
//! tasks run on the same worker do not need Send. It is just important that write operations on
//! the RefCell do not `.await` or a panic is likely.

use embassy_executor::Spawner;
use embassy_sync::{
blocking_mutex::raw::CriticalSectionRawMutex,
mutex::Mutex,
semaphore::{FairSemaphore, Semaphore},
};
use embassy_time::Timer;
use zephyr::{printkln, sync::Arc};

use crate::{get_random_delay, ResultSignal, Stats, NUM_PHIL};

type ESemaphore = FairSemaphore<CriticalSectionRawMutex, NUM_PHIL>;

/// The semaphores for the forks.
static FORKS: [ESemaphore; NUM_PHIL] = [const { ESemaphore::new(1) }; NUM_PHIL];

/// The semaphore to wait for them all to finish.
static DONE_SEM: ESemaphore = ESemaphore::new(0);

/// Number of iterations of each philospher.
///
/// Should be long enough to exercise the test, but too
/// long and the test will timeout. The delay calculated will randomly be between 25 and 775, and
/// there are two waits, so typically, each "eat" will take about a second.
const EAT_COUNT: usize = 10;

#[embassy_executor::task]
pub async fn phil(spawner: Spawner, stats_sig: &'static ResultSignal) {
// Our overall stats.
let stats = Arc::new(Mutex::new(Stats::default()));

// Spawn off each philosopher.
for i in 0..NUM_PHIL {
let forks = if i == NUM_PHIL - 1 {
[&FORKS[0], &FORKS[i]]
} else {
[&FORKS[i], &FORKS[i + 1]]
};

spawner.spawn(one_phil(forks, i, stats.clone())).unwrap();
}

// Wait for them all to finish.
DONE_SEM.acquire(NUM_PHIL).await.unwrap();

// Send the stats back.
stats_sig.signal(stats);
}

/// Simulate a single philospher.
///
/// The forks must be ordered with the first fork having th lowest number, otherwise this will
/// likely deadlock.
///
/// This will run for EAT_COUNT times, and then return.
#[embassy_executor::task(pool_size = NUM_PHIL)]
async fn one_phil(
forks: [&'static ESemaphore; 2],
n: usize,
stats: Arc<Mutex<CriticalSectionRawMutex, Stats>>,
) {
for i in 0..EAT_COUNT {
// Acquire the forks.
// printkln!("Child {n} take left fork");
forks[0].acquire(1).await.unwrap().disarm();
// printkln!("Child {n} take right fork");
forks[1].acquire(1).await.unwrap().disarm();

// printkln!("Child {n} eating");
let delay = get_random_delay(n, 25);
Timer::after(delay).await;
stats.lock().await.record_eat(n, delay);

// Release the forks.
// printkln!("Child {n} giving up forks");
forks[1].release(1);
forks[0].release(1);

let delay = get_random_delay(n, 25);
Timer::after(delay).await;
stats.lock().await.record_think(n, delay);

printkln!("Philospher {n} finished eating time {i}");
}

DONE_SEM.release(1);
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,76 +9,59 @@

extern crate alloc;

use zephyr::{
kio::spawn,
kobj_define, printkln,
sync::Arc,
sys::uptime_get,
time::{Duration, Tick},
work::WorkQueueBuilder,
};
use embassy_executor::Spawner;
use embassy_sync::{blocking_mutex::raw::CriticalSectionRawMutex, mutex::Mutex, signal::Signal};
use embassy_time::Duration;
use static_cell::StaticCell;
use zephyr::{embassy::Executor, printkln, sync::Arc, sys::uptime_get};

mod async_sem;

/// How many philosophers. There will be the same number of forks.
const NUM_PHIL: usize = 6;

/// Size of the stack for the work queue.
const WORK_STACK_SIZE: usize = 2048;

// The dining philosophers problem is a simple example of cooperation between multiple threads.
// This implementation demonstrates a few ways that Zephyr's work-queues can be used to simulate
// this problem.

#[no_mangle]
extern "C" fn rust_main() {
printkln!(
"Async/work-queue dining philosophers{}",
zephyr::kconfig::CONFIG_BOARD
);
printkln!("Async dining philosophers{}", zephyr::kconfig::CONFIG_BOARD);
printkln!("Time tick: {}", zephyr::time::SYS_FREQUENCY);

// Create the work queue to run this.
let worker = Arc::new(
WorkQueueBuilder::new()
.set_priority(1)
.start(WORK_STACK.init_once(()).unwrap()),
);

// In addition, create a lower priority worker.
let lower_worker = Arc::new(
WorkQueueBuilder::new()
.set_priority(5)
.start(LOWER_WORK_STACK.init_once(()).unwrap()),
);

// It is important that work queues are not dropped, as they are persistent objects in the
// Zephyr world.
let _ = Arc::into_raw(lower_worker.clone());
let _ = Arc::into_raw(worker.clone());
let executor = EXECUTOR.init(Executor::new());
executor.run(|spawner| {
spawner.spawn(main(spawner)).unwrap();
})
}

static EXECUTOR: StaticCell<Executor> = StaticCell::new();

type ResultSignal = Signal<CriticalSectionRawMutex, Arc<Mutex<CriticalSectionRawMutex, Stats>>>;
static RESULT_SIGNAL: ResultSignal = Signal::new();

#[embassy_executor::task]
async fn main(spawner: Spawner) -> () {
// First run the async semaphore based one.
printkln!("Running 'async-sem' test");
let handle = spawn(async_sem::phil(), &worker, c"async-sem");
let stats = handle.join();
spawner
.spawn(async_sem::phil(spawner, &RESULT_SIGNAL))
.unwrap();

let stats = RESULT_SIGNAL.wait().await;
printkln!("Done with 'async-sem' test");
stats.show();
stats.lock().await.show();

printkln!("All threads done");
}

kobj_define! {
static WORK_STACK: ThreadStack<WORK_STACK_SIZE>;
static LOWER_WORK_STACK: ThreadStack<WORK_STACK_SIZE>;
}

/// Get a random delay, based on the ID of this user, and the current uptime.
fn get_random_delay(id: usize, period: usize) -> Duration {
let tick = (uptime_get() & (usize::MAX as i64)) as usize;
let delay = (tick / 100 * (id + 1)) & 0x1f;
let tick = (uptime_get() & (usize::MAX as i64)) as u64;
let delay = (tick / 100 * (id as u64 + 1)) & 0x1f;

// Use one greater to be sure to never get a delay of zero.
Duration::millis_at_least(((delay + 1) * period) as Tick)
Duration::from_millis((delay + 1) * (period as u64))
}

/// Instead of just printint out so much information that the data just scolls by, gather
Expand All @@ -95,11 +78,11 @@ struct Stats {

impl Stats {
fn record_eat(&mut self, index: usize, time: Duration) {
self.eating[index] += time.to_millis();
self.eating[index] += time.as_millis();
}

fn record_think(&mut self, index: usize, time: Duration) {
self.thinking[index] += time.to_millis();
self.thinking[index] += time.as_millis();
self.count[index] += 1;
}

Expand Down
9 changes: 8 additions & 1 deletion samples/bench/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,18 @@ license = "Apache-2.0 or MIT"
crate-type = ["staticlib"]

[dependencies]
zephyr = "0.1.0"
zephyr = { version = "0.1.0", features = ["time-driver", "executor-zephyr"] }
critical-section = "1.1.2"
heapless = "0.8"
static_cell = "2.1"

embassy-executor = { version = "0.7.0", features = ["log", "task-arena-size-2048"] }
embassy-sync = "0.6.2"
embassy-futures = "0.1.1"

# Hard code the tick rate.
embassy-time = { version = "0.4.0", features = ["tick-hz-10_000"] }

# Dependencies that are used by build.rs.
[build-dependencies]
zephyr-build = "0.1.0"
Expand Down
6 changes: 6 additions & 0 deletions samples/bench/prj.conf
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,16 @@ CONFIG_MAIN_STACK_SIZE=8192
CONFIG_POLL=y

# CONFIG_USERSPACE=y
# CONFIG_DEBUG=y
# CONFIG_ASSERT=y
# CONFIG_STACK_SENTINEL=y
# CONFIG_STACK_USAGE=y
# CONFIG_STACK_CANARIES=y

# Some debugging
CONFIG_THREAD_MONITOR=y
CONFIG_THREAD_ANALYZER=y
CONFIG_THREAD_ANALYZER_USE_PRINTK=y
CONFIG_THREAD_ANALYZER_AUTO=n
CONFIG_DEBUG_THREAD_INFO=y
# CONFIG_THREAD_ANALYZER_AUTO_INTERVAL=15
2 changes: 2 additions & 0 deletions samples/bench/sample.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ common:
- qemu_cortex_m0
- qemu_cortex_m3
- qemu_riscv32
- qemu_riscv32/qemu_virt_riscv32/smp
- qemu_riscv64
- qemu_riscv64/qemu_virt_riscv64/smp
- nrf52840dk/nrf52840
tests:
sample.rust/bench.plain:
Expand Down
Loading
Loading