rtfm-syntax refactor + heterogeneous multi-core support

This commit is contained in:
Jorge Aparicio 2019-06-13 23:56:59 +02:00
parent fafeeb2727
commit 81275bfa4f
127 changed files with 4072 additions and 5848 deletions

View file

@ -17,56 +17,72 @@ version = "0.5.0-alpha.1"
[lib]
name = "rtfm"
[[test]]
required-features = ["heterogeneous"]
name = "multi"
[[example]]
name = "baseline"
required-features = ["timer-queue"]
required-features = ["__v7"]
[[example]]
name = "periodic"
required-features = ["timer-queue"]
required-features = ["__v7"]
[[example]]
name = "pool"
# this example doesn't need this feature but only works on ARMv7-M
# specifying the feature here avoids compiling this for ARMv6-M
required-features = ["timer-queue"]
required-features = ["__v7"]
[[example]]
name = "schedule"
required-features = ["timer-queue"]
required-features = ["__v7"]
[[example]]
name = "t-cfg"
required-features = ["__v7"]
[[example]]
name = "t-schedule"
required-features = ["__v7"]
[[example]]
name = "types"
required-features = ["timer-queue"]
required-features = ["__v7"]
[dependencies]
cortex-m = "0.5.8"
cortex-m-rt = "0.6.7"
cortex-m-rtfm-macros = { path = "macros", version = "0.5.0-alpha.1" }
heapless = "0.5.0-alpha.1"
cortex-m = "0.6.0"
cortex-m-rtfm-macros = { path = "macros" }
rtfm-core = { git = "https://github.com/japaric/rtfm-core" }
cortex-m-rt = "0.6.8"
heapless = "0.5.0-alpha.2"
[dependencies.microamp]
optional = true
version = "0.1.0-alpha.1"
[dev-dependencies]
cortex-m-semihosting = "0.3.2"
lm3s6965 = "0.1.3"
panic-halt = "0.2.0"
cortex-m-semihosting = "0.3.3"
[dev-dependencies.panic-semihosting]
features = ["exit"]
version = "0.5.1"
[features]
timer-queue = ["cortex-m-rtfm-macros/timer-queue"]
version = "0.5.2"
[target.x86_64-unknown-linux-gnu.dev-dependencies]
compiletest_rs = "0.3.21"
tempdir = "0.3.7"
compiletest_rs = "0.3.22"
[package.metadata.docs.rs]
features = ["timer-queue"]
[features]
heterogeneous = ["cortex-m-rtfm-macros/heterogeneous", "microamp"]
# used for testing this crate; do not use in applications
__v7 =[]
[profile.release]
codegen-units = 1
lto = true
[workspace]
members = ["macros"]
members = [
"macros",
"mc",
]

View file

@ -1,10 +1,12 @@
set -euxo pipefail
main() {
if [ $TARGET != x86_64-unknown-linux-gnu ]; then
rustup target add $TARGET
if [ $TARGET = x86_64-unknown-linux-gnu ]; then
( cd .. && cargo install microamp-tools --version 0.1.0-alpha.2 -f )
fi
rustup target add $TARGET
mkdir qemu
curl -L https://github.com/japaric/qemu-bin/raw/master/14.04/qemu-system-arm-2.12.0 > qemu/qemu-system-arm
chmod +x qemu/qemu-system-arm

View file

@ -37,46 +37,58 @@ main() {
mkdir -p ci/builds
if [ $T = x86_64-unknown-linux-gnu ]; then
# compile-fail and compile-pass tests
if [ $TRAVIS_RUST_VERSION = nightly ]; then
# compile-fail tests
cargo test --test single --target $T
cargo test --test multi --features heterogeneous --target $T
# TODO how to run a subset of these tests when timer-queue is disabled?
cargo test --features "timer-queue" --test compiletest --target $T
# multi-core compile-pass tests
pushd mc
local exs=(
smallest
x-init-2
x-init
x-schedule
x-spawn
)
for ex in ${exs[@]}; do
cargo microamp --example $ex --target thumbv7m-none-eabi,thumbv6m-none-eabi --check
done
popd
else
if [ $TRAVIS_RUST_VERSION != nightly ]; then
rm -f .cargo/config
cargo doc
( cd book/en && mdbook build )
( cd book/ru && mdbook build )
local td=$(mktemp -d)
cp -r target/doc $td/api
mkdir $td/book
cp -r book/en/book $td/book/en
cp -r book/ru/book $td/book/ru
cp LICENSE-* $td/book/en
cp LICENSE-* $td/book/ru
linkchecker $td/book/en/
linkchecker $td/book/ru/
linkchecker $td/api/rtfm/
linkchecker $td/api/cortex_m_rtfm_macros/
fi
fi
cargo check --target $T
if [ $TARGET != thumbv6m-none-eabi ]; then
cargo check --features "timer-queue" --target $T
fi
if [ $TRAVIS_RUST_VERSION != nightly ]; then
rm -f .cargo/config
if [ $TARGET != thumbv6m-none-eabi ]; then
cargo doc --features timer-queue
else
cargo doc
fi
( cd book/en && mdbook build )
( cd book/ru && mdbook build )
local td=$(mktemp -d)
cp -r target/doc $td/api
mkdir $td/book
cp -r book/en/book $td/book/en
cp -r book/ru/book $td/book/ru
cp LICENSE-* $td/book/en
cp LICENSE-* $td/book/ru
linkchecker $td/book/en/
linkchecker $td/book/ru/
linkchecker $td/api/rtfm/
linkchecker $td/api/cortex_m_rtfm_macros/
fi
( cd macros && cargo test --target $T )
return
fi
cargo check --target $T --examples
if [ $TARGET != thumbv6m-none-eabi ]; then
cargo check --features "timer-queue" --target $T --examples
if [ $TARGET = thumbv6m-none-eabi ]; then
cargo check --target $T --examples
else
cargo check --target $T --examples --features __v7
fi
# run-pass tests
@ -108,74 +120,71 @@ main() {
)
for ex in ${exs[@]}; do
if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then
# LLD doesn't support this at the moment
continue
fi
if [ $ex = pool ]; then
if [ $TARGET != thumbv6m-none-eabi ]; then
local td=$(mktemp -d)
local features="timer-queue"
cargo run --example $ex --target $TARGET --features $features >\
$td/pool.run
grep 'foo(0x2' $td/pool.run
grep 'bar(0x2' $td/pool.run
arm-none-eabi-objcopy -O ihex target/$TARGET/debug/examples/$ex \
ci/builds/${ex}_${features/,/_}_debug_1.hex
cargo run --example $ex --target $TARGET --features $features --release >\
$td/pool.run
grep 'foo(0x2' $td/pool.run
grep 'bar(0x2' $td/pool.run
arm-none-eabi-objcopy -O ihex target/$TARGET/release/examples/$ex \
ci/builds/${ex}_${features/,/_}_release_1.hex
rm -rf $td
if [ $TARGET = thumbv6m-none-eabi ]; then
continue
fi
local td=$(mktemp -d)
cargo run --example $ex --target $TARGET --features __v7 >\
$td/pool.run
grep 'foo(0x2' $td/pool.run
grep 'bar(0x2' $td/pool.run
arm-none-eabi-objcopy -O ihex target/$TARGET/debug/examples/$ex \
ci/builds/${ex}___v7_debug_1.hex
cargo run --example $ex --target $TARGET --features __v7 --release >\
$td/pool.run
grep 'foo(0x2' $td/pool.run
grep 'bar(0x2' $td/pool.run
arm-none-eabi-objcopy -O ihex target/$TARGET/release/examples/$ex \
ci/builds/${ex}___v7_release_1.hex
rm -rf $td
continue
fi
if [ $ex != types ]; then
arm_example "run" $ex "debug" "" "1"
arm_example "run" $ex "release" "" "1"
if [ $ex = types ]; then
if [ $TARGET = thumbv6m-none-eabi ]; then
continue
fi
arm_example "run" $ex "debug" "__v7" "1"
arm_example "run" $ex "release" "__v7" "1"
continue
fi
if [ $TARGET != thumbv6m-none-eabi ]; then
arm_example "run" $ex "debug" "timer-queue" "1"
arm_example "run" $ex "release" "timer-queue" "1"
fi
arm_example "run" $ex "debug" "" "1"
arm_example "run" $ex "release" "" "1"
done
local built=()
cargo clean
for ex in ${exs[@]}; do
if [ $ex = ramfunc ] && [ $T = thumbv6m-none-eabi ]; then
# LLD doesn't support this at the moment
continue
fi
if [ $ex = types ] || [ $ex = pool ]; then
if [ $TARGET = thumbv6m-none-eabi ]; then
continue
fi
if [ $ex != types ] && [ $ex != pool ]; then
arm_example "build" $ex "debug" "__v7" "2"
cmp ci/builds/${ex}___v7_debug_1.hex \
ci/builds/${ex}___v7_debug_2.hex
arm_example "build" $ex "release" "__v7" "2"
cmp ci/builds/${ex}___v7_release_1.hex \
ci/builds/${ex}___v7_release_2.hex
else
arm_example "build" $ex "debug" "" "2"
cmp ci/builds/${ex}_debug_1.hex \
ci/builds/${ex}_debug_2.hex
arm_example "build" $ex "release" "" "2"
cmp ci/builds/${ex}_release_1.hex \
ci/builds/${ex}_release_2.hex
built+=( $ex )
fi
if [ $TARGET != thumbv6m-none-eabi ]; then
arm_example "build" $ex "debug" "timer-queue" "2"
cmp ci/builds/${ex}_timer-queue_debug_1.hex \
ci/builds/${ex}_timer-queue_debug_2.hex
arm_example "build" $ex "release" "timer-queue" "2"
cmp ci/builds/${ex}_timer-queue_release_1.hex \
ci/builds/${ex}_timer-queue_release_2.hex
fi
built+=( $ex )
done
( cd target/$TARGET/release/examples/ && size ${built[@]} )

View file

@ -5,13 +5,12 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
// NOTE: does NOT properly work on QEMU
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
#[init(spawn = [foo])]
fn init(c: init::Context) {

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
// `examples/interrupt.rs` rewritten to use `binds`
#[rtfm::app(device = lm3s6965)]

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
#[cfg(debug_assertions)]
use cortex_m_semihosting::hprintln;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
@ -21,12 +20,12 @@ const APP: () = {
}
#[task(priority = 3, resources = [COUNT], spawn = [log])]
fn foo(c: foo::Context) {
fn foo(_c: foo::Context) {
#[cfg(debug_assertions)]
{
*c.resources.COUNT += 1;
*_c.resources.COUNT += 1;
c.spawn.log(*c.resources.COUNT).ok();
_c.spawn.log(*_c.resources.COUNT).ok();
}
// this wouldn't compile in `release` mode

View file

@ -5,11 +5,10 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use rtfm::Mutex;
use panic_semihosting as _;
use rtfm::{Exclusive, Mutex};
#[rtfm::app(device = lm3s6965)]
const APP: () = {
@ -35,17 +34,15 @@ const APP: () = {
}
#[interrupt(priority = 2, resources = [SHARED])]
fn UART1(mut c: UART1::Context) {
fn UART1(c: UART1::Context) {
static mut STATE: u32 = 0;
hprintln!("UART1(STATE = {})", *STATE).unwrap();
// just to show that `SHARED` can be accessed directly and ..
// just to show that `SHARED` can be accessed directly
*c.resources.SHARED += 0;
// .. also through a (no-op) `lock`
c.resources.SHARED.lock(|shared| *shared += 0);
advance(STATE, c.resources.SHARED);
advance(STATE, Exclusive(c.resources.SHARED));
}
};

View file

@ -5,9 +5,8 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,18 +5,17 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, peripherals = true)]
const APP: () = {
#[init]
fn init(c: init::Context) {
static mut X: u32 = 0;
// Cortex-M peripherals
let _core: rtfm::Peripherals = c.core;
let _core: cortex_m::Peripherals = c.core;
// Device specific peripherals
let _device: lm3s6965::Peripherals = c.device;

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,20 +5,21 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use heapless::{
consts::*,
spsc::{Consumer, Producer, Queue},
};
use lm3s6965::Interrupt;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
// Late resources
static mut P: Producer<'static, u32, U4> = ();
static mut C: Consumer<'static, u32, U4> = ();
extern "C" {
static mut P: Producer<'static, u32, U4>;
static mut C: Consumer<'static, u32, U4>;
}
#[init]
fn init(_: init::Context) -> init::LateResources {

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
@ -46,7 +45,7 @@ const APP: () = {
}
#[interrupt(priority = 2, resources = [SHARED])]
fn GPIOB(mut c: GPIOB::Context) {
fn GPIOB(c: GPIOB::Context) {
// the higher priority task does *not* need a critical section
*c.resources.SHARED += 1;

View file

@ -5,9 +5,8 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,11 +5,10 @@
#![no_main]
#![no_std]
extern crate panic_halt;
use core::marker::PhantomData;
use cortex_m_semihosting::debug;
use panic_halt as _;
use rtfm::app;
pub struct NotSend {
@ -38,13 +37,13 @@ const APP: () = {
}
#[task(priority = 2, resources = [SHARED])]
fn baz(mut c: baz::Context) {
fn baz(c: baz::Context) {
// scenario 2: resource shared between tasks that run at the same priority
*c.resources.SHARED = Some(NotSend { _0: PhantomData });
}
#[task(priority = 2, resources = [SHARED])]
fn quux(mut c: quux::Context) {
fn quux(c: quux::Context) {
// scenario 2
let _not_send = c.resources.SHARED.take().unwrap();

View file

@ -5,11 +5,10 @@
#![no_main]
#![no_std]
extern crate panic_halt;
use core::marker::PhantomData;
use cortex_m_semihosting::debug;
use panic_halt as _;
pub struct NotSync {
_0: PhantomData<*const ()>,

View file

@ -5,15 +5,14 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::hprintln;
use rtfm::Instant;
use panic_semihosting as _;
use rtfm::cyccnt::{Instant, U32Ext};
const PERIOD: u32 = 8_000_000;
// NOTE: does NOT work on QEMU!
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
#[init(schedule = [foo])]
fn init(c: init::Context) {

View file

@ -5,14 +5,13 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use heapless::{
pool,
pool::singleton::{Box, Pool},
};
use lm3s6965::Interrupt;
use panic_semihosting as _;
use rtfm::app;
// Declare a pool of 128-byte memory blocks

View file

@ -5,9 +5,8 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
@ -33,7 +32,7 @@ const APP: () = {
// `SHARED` can be access from this context
#[interrupt(resources = [SHARED])]
fn UART0(mut c: UART0::Context) {
fn UART0(c: UART0::Context) {
*c.resources.SHARED += 1;
hprintln!("UART0: SHARED = {}", c.resources.SHARED).unwrap();
@ -41,7 +40,7 @@ const APP: () = {
// `SHARED` can be access from this context
#[interrupt(resources = [SHARED])]
fn UART1(mut c: UART1::Context) {
fn UART1(c: UART1::Context) {
*c.resources.SHARED += 1;
hprintln!("UART1: SHARED = {}", c.resources.SHARED).unwrap();

View file

@ -5,13 +5,12 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::hprintln;
use rtfm::Instant;
use panic_halt as _;
use rtfm::cyccnt::{Instant, U32Ext as _};
// NOTE: does NOT work on QEMU!
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
#[init(schedule = [foo, bar])]
fn init(c: init::Context) {

View file

@ -5,10 +5,9 @@
#![no_main]
#![no_std]
extern crate panic_halt;
use cortex_m_semihosting::debug;
use lm3s6965::Interrupt;
use panic_halt as _;
use rtfm::app;
pub struct MustBeSend;

View file

@ -5,13 +5,8 @@
#![no_main]
#![no_std]
// panic-handler crate
extern crate panic_semihosting;
use panic_semihosting as _; // panic handler
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
};
const APP: () = {};

View file

@ -5,14 +5,15 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use lm3s6965::Interrupt;
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
static KEY: u32 = ();
extern "C" {
static KEY: u32;
}
#[init]
fn init(_: init::Context) -> init::LateResources {

View file

@ -1,12 +1,11 @@
//! Check that `binds` works as advertised
//! [compile-pass] Check that `binds` works as advertised
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use panic_halt as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -1,15 +1,11 @@
//! Compile-pass test that checks that `#[cfg]` attributes are respected
//! [compile-pass] check that `#[cfg]` attributes are respected
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use panic_halt as _;
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
#[cfg(never)]
static mut FOO: u32 = 0;

View file

@ -1,25 +1,27 @@
#![deny(unsafe_code)]
#![deny(warnings)]
//! [compile-pass] late resources don't need to be `Send` if they are owned by `idle`
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use core::marker::PhantomData;
use panic_halt as _;
pub struct NotSend {
_0: PhantomData<*const ()>,
}
#[rtfm::app(device = lm3s6965)]
const APP: () = {
static mut X: NotSend = ();
extern "C" {
static mut X: NotSend;
}
static mut Y: Option<NotSend> = None;
#[init(resources = [Y])]
fn init(c: init::Context) -> init::LateResources {
// equivalent to late resource initialization
*c.resources.Y = Some(NotSend { _0: PhantomData });
init::LateResources {

View file

@ -1,15 +1,11 @@
//! Check code generation of resources
//! [compile-pass] Check code generation of resources
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::Exclusive;
use panic_halt as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
@ -61,10 +57,10 @@ const APP: () = {
let _: &mut u32 = c.resources.O3;
// no `Mutex` proxy when access from highest priority task
let _: Exclusive<u32> = c.resources.S1;
let _: &mut u32 = c.resources.S1;
// no `Mutex` proxy when co-owned by cooperative (same priority) tasks
let _: Exclusive<u32> = c.resources.S2;
let _: &mut u32 = c.resources.S2;
// `&` if read-only
let _: &u32 = c.resources.S3;
@ -76,6 +72,6 @@ const APP: () = {
let _: &u32 = c.resources.O5;
// no `Mutex` proxy when co-owned by cooperative (same priority) tasks
let _: Exclusive<u32> = c.resources.S2;
let _: &mut u32 = c.resources.S2;
}
};

View file

@ -1,15 +1,14 @@
//! [compile-pass] Check `schedule` code generation
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use panic_halt as _;
use rtfm::cyccnt::{Instant, U32Ext as _};
use rtfm::Instant;
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
#[init(schedule = [foo, bar, baz])]
fn init(c: init::Context) {

View file

@ -1,12 +1,11 @@
//! Check code generation of `spawn`
//! [compile-pass] Check code generation of `spawn`
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use panic_halt as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,9 +5,8 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::{debug, hprintln};
use panic_semihosting as _;
#[rtfm::app(device = lm3s6965)]
const APP: () = {

View file

@ -5,12 +5,11 @@
#![no_main]
#![no_std]
extern crate panic_semihosting;
use cortex_m_semihosting::debug;
use rtfm::{Exclusive, Instant};
use panic_semihosting as _;
use rtfm::cyccnt::Instant;
#[rtfm::app(device = lm3s6965)]
#[rtfm::app(device = lm3s6965, peripherals = true, monotonic = rtfm::cyccnt::CYCCNT)]
const APP: () = {
static mut SHARED: u32 = 0;
@ -43,7 +42,7 @@ const APP: () = {
#[task(priority = 2, resources = [SHARED], schedule = [foo], spawn = [foo])]
fn foo(c: foo::Context) {
let _: Instant = c.scheduled;
let _: Exclusive<u32> = c.resources.SHARED;
let _: &mut u32 = c.resources.SHARED;
let _: foo::Resources = c.resources;
let _: foo::Schedule = c.schedule;
let _: foo::Spawn = c.spawn;

View file

@ -15,12 +15,12 @@ version = "0.5.0-alpha.1"
proc-macro = true
[dependencies]
quote = "0.6.10"
proc-macro2 = "0.4.24"
proc-macro2 = "0.4.30"
quote = "0.6.12"
syn = "0.15.34"
[dependencies.syn]
features = ["extra-traits", "full"]
version = "0.15.23"
[dependencies.rtfm-syntax]
git = "https://github.com/japaric/rtfm-syntax"
[features]
timer-queue = []
heterogeneous = []

View file

@ -1,265 +1,59 @@
use std::{
cmp,
collections::{BTreeMap, HashMap, HashSet},
use core::ops;
use std::collections::{BTreeMap, BTreeSet};
use rtfm_syntax::{
analyze::{self, Priority},
ast::App,
Core, P,
};
use syn::Ident;
use syn::{Attribute, Ident, Type};
use crate::syntax::{App, Idents};
pub type Ownerships = HashMap<Ident, Ownership>;
/// Extend the upstream `Analysis` struct with our field
pub struct Analysis {
/// Capacities of free queues
pub capacities: Capacities,
pub dispatchers: Dispatchers,
// Ceilings of free queues
pub free_queues: HashMap<Ident, u8>,
pub resources_assert_send: HashSet<Box<Type>>,
pub tasks_assert_send: HashSet<Ident>,
/// Types of RO resources that need to be Sync
pub assert_sync: HashSet<Box<Type>>,
// Resource ownership
pub ownerships: Ownerships,
// Ceilings of ready queues
pub ready_queues: HashMap<u8, u8>,
pub timer_queue: TimerQueue,
parent: P<analyze::Analysis>,
pub interrupts: BTreeMap<Core, BTreeMap<Priority, Ident>>,
}
#[derive(Clone, Copy, PartialEq)]
pub enum Ownership {
// NOTE priorities and ceilings are "logical" (0 = lowest priority, 255 = highest priority)
Owned { priority: u8 },
CoOwned { priority: u8 },
Shared { ceiling: u8 },
}
impl ops::Deref for Analysis {
type Target = analyze::Analysis;
impl Ownership {
pub fn needs_lock(&self, priority: u8) -> bool {
match *self {
Ownership::Owned { .. } | Ownership::CoOwned { .. } => false,
Ownership::Shared { ceiling } => {
debug_assert!(ceiling >= priority);
priority < ceiling
}
}
}
pub fn is_owned(&self) -> bool {
match *self {
Ownership::Owned { .. } => true,
_ => false,
}
fn deref(&self) -> &Self::Target {
&self.parent
}
}
pub struct Dispatcher {
/// Attributes to apply to the dispatcher
pub attrs: Vec<Attribute>,
pub interrupt: Ident,
/// Tasks dispatched at this priority level
pub tasks: Vec<Ident>,
// Queue capacity
pub capacity: u8,
}
/// Priority -> Dispatcher
pub type Dispatchers = BTreeMap<u8, Dispatcher>;
pub type Capacities = HashMap<Ident, u8>;
pub fn app(app: &App) -> Analysis {
// Ceiling analysis of R/W resource and Sync analysis of RO resources
// (Resource shared by tasks that run at different priorities need to be `Sync`)
let mut ownerships = Ownerships::new();
let mut resources_assert_send = HashSet::new();
let mut tasks_assert_send = HashSet::new();
let mut assert_sync = HashSet::new();
for (priority, res) in app.resource_accesses() {
if let Some(ownership) = ownerships.get_mut(res) {
match *ownership {
Ownership::Owned { priority: ceiling }
| Ownership::CoOwned { priority: ceiling }
| Ownership::Shared { ceiling }
if priority != ceiling =>
{
*ownership = Ownership::Shared {
ceiling: cmp::max(ceiling, priority),
};
let res = &app.resources[res];
if res.mutability.is_none() {
assert_sync.insert(res.ty.clone());
}
// Assign an `extern` interrupt to each priority level
pub fn app(analysis: P<analyze::Analysis>, app: &App) -> P<Analysis> {
let mut interrupts = BTreeMap::new();
for core in 0..app.args.cores {
let priorities = app
.software_tasks
.values()
.filter_map(|task| {
if task.args.core == core {
Some(task.args.priority)
} else {
None
}
Ownership::Owned { priority: ceiling } if ceiling == priority => {
*ownership = Ownership::CoOwned { priority };
}
_ => {}
}
})
.chain(analysis.timer_queues.get(&core).map(|tq| tq.priority))
.collect::<BTreeSet<_>>();
continue;
}
ownerships.insert(res.clone(), Ownership::Owned { priority });
}
// Compute sizes of free queues
// We assume at most one message per `spawn` / `schedule`
let mut capacities: Capacities = app.tasks.keys().map(|task| (task.clone(), 0)).collect();
for (_, task) in app.spawn_calls().chain(app.schedule_calls()) {
*capacities.get_mut(task).expect("BUG: capacities.get_mut") += 1;
}
// Override computed capacities if user specified a capacity in `#[task]`
for (name, task) in &app.tasks {
if let Some(cap) = task.args.capacity {
*capacities.get_mut(name).expect("BUG: capacities.get_mut") = cap;
if !priorities.is_empty() {
interrupts.insert(
core,
priorities
.iter()
.cloned()
.rev()
.zip(app.extern_interrupts[&core].keys().cloned())
.collect(),
);
}
}
// Compute the size of the timer queue
// Compute the priority of the timer queue, which matches the priority of the highest
// `schedule`-able task
let mut tq_capacity = 0;
let mut tq_priority = 1;
let mut tq_tasks = Idents::new();
for (_, task) in app.schedule_calls() {
tq_capacity += capacities[task];
tq_priority = cmp::max(tq_priority, app.tasks[task].args.priority);
tq_tasks.insert(task.clone());
}
// Compute dispatchers capacities
// Determine which tasks are dispatched by which dispatcher
// Compute the timer queue priority which matches the priority of the highest priority
// dispatcher
let mut dispatchers = Dispatchers::new();
let mut free_interrupts = app.free_interrupts.iter();
let mut tasks = app.tasks.iter().collect::<Vec<_>>();
tasks.sort_by(|l, r| l.1.args.priority.cmp(&r.1.args.priority));
for (name, task) in tasks {
let dispatcher = dispatchers.entry(task.args.priority).or_insert_with(|| {
let (name, fi) = free_interrupts
.next()
.expect("BUG: not enough free_interrupts");
Dispatcher {
attrs: fi.attrs.clone(),
capacity: 0,
interrupt: name.clone(),
tasks: vec![],
}
});
dispatcher.capacity += capacities[name];
dispatcher.tasks.push(name.clone());
}
// All messages sent from `init` need to be `Send`
for task in app.init.args.spawn.iter().chain(&app.init.args.schedule) {
tasks_assert_send.insert(task.clone());
}
// All late resources need to be `Send`, unless they are owned by `idle`
for (name, res) in &app.resources {
let owned_by_idle = Ownership::Owned { priority: 0 };
if res.expr.is_none()
&& ownerships
.get(name)
.map(|ship| *ship != owned_by_idle)
.unwrap_or(false)
{
resources_assert_send.insert(res.ty.clone());
}
}
// All resources shared with init need to be `Send`, unless they are owned by `idle`
// This is equivalent to late initialization (e.g. `static mut LATE: Option<T> = None`)
for name in &app.init.args.resources {
let owned_by_idle = Ownership::Owned { priority: 0 };
if ownerships
.get(name)
.map(|ship| *ship != owned_by_idle)
.unwrap_or(false)
{
resources_assert_send.insert(app.resources[name].ty.clone());
}
}
// Ceiling analysis of free queues (consumer end point) -- first pass
// Ceiling analysis of ready queues (producer end point) -- first pass
// Also compute more Send-ness requirements
let mut free_queues = HashMap::new();
let mut ready_queues = HashMap::new();
for (priority, task) in app.spawn_calls() {
if let Some(priority) = priority {
// Users of `spawn` contend for the spawnee FREE_QUEUE
let c = free_queues.entry(task.clone()).or_default();
*c = cmp::max(*c, priority);
// Users of `spawn` contend for the spawnee's dispatcher READY_QUEUE
let c = ready_queues
.entry(app.tasks[task].args.priority)
.or_default();
*c = cmp::max(*c, priority);
// Send is required when sending messages from a task whose priority doesn't match the
// priority of the receiving task
if app.tasks[task].args.priority != priority {
tasks_assert_send.insert(task.clone());
}
} else {
// spawns from `init` are excluded from the ceiling analysis
}
}
// Ceiling analysis of ready queues (producer end point) -- second pass
// Ceiling analysis of free queues (consumer end point) -- second pass
// Ceiling analysis of the timer queue
let mut tq_ceiling = tq_priority;
for (priority, task) in app.schedule_calls() {
// the system timer handler contends for the spawnee's dispatcher READY_QUEUE
let c = ready_queues
.entry(app.tasks[task].args.priority)
.or_default();
*c = cmp::max(*c, tq_priority);
if let Some(priority) = priority {
// Users of `schedule` contend for the spawnee task FREE_QUEUE
let c = free_queues.entry(task.clone()).or_default();
*c = cmp::max(*c, priority);
// Users of `schedule` contend for the timer queue
tq_ceiling = cmp::max(tq_ceiling, priority);
} else {
// spawns from `init` are excluded from the ceiling analysis
}
}
Analysis {
capacities,
dispatchers,
free_queues,
tasks_assert_send,
resources_assert_send,
assert_sync,
ownerships,
ready_queues,
timer_queue: TimerQueue {
capacity: tq_capacity,
ceiling: tq_ceiling,
priority: tq_priority,
tasks: tq_tasks,
},
}
}
pub struct TimerQueue {
pub capacity: u8,
pub ceiling: u8,
pub priority: u8,
pub tasks: Idents,
P::new(Analysis {
parent: analysis,
interrupts,
})
}

View file

@ -1,122 +1,209 @@
use std::{collections::HashSet, iter};
use std::collections::HashSet;
use proc_macro2::Span;
use syn::parse;
use rtfm_syntax::{
analyze::Analysis,
ast::{App, CustomArg, HardwareTaskKind},
};
use syn::{parse, Path};
use crate::syntax::App;
pub struct Extra<'a> {
pub device: &'a Path,
pub monotonic: Option<&'a Path>,
pub peripherals: Option<u8>,
}
pub fn app(app: &App) -> parse::Result<()> {
// Check that all referenced resources have been declared
for res in app
.idle
.as_ref()
.map(|idle| -> Box<dyn Iterator<Item = _>> { Box::new(idle.args.resources.iter()) })
.unwrap_or_else(|| Box::new(iter::empty()))
.chain(&app.init.args.resources)
.chain(app.exceptions.values().flat_map(|e| &e.args.resources))
.chain(app.interrupts.values().flat_map(|i| &i.args.resources))
.chain(app.tasks.values().flat_map(|t| &t.args.resources))
impl<'a> Extra<'a> {
pub fn monotonic(&self) -> &'a Path {
self.monotonic.expect("UNREACHABLE")
}
}
pub fn app<'a>(app: &'a App, analysis: &Analysis) -> parse::Result<Extra<'a>> {
// check that all exceptions are valid; only exceptions with configurable priorities are
// accepted
for (name, task) in app
.hardware_tasks
.iter()
.filter(|(_, task)| task.kind == HardwareTaskKind::Exception)
{
if !app.resources.contains_key(res) {
return Err(parse::Error::new(
res.span(),
"this resource has NOT been declared",
));
let name_s = task.args.binds(name).to_string();
match &*name_s {
// NOTE that some of these don't exist on ARMv6-M but we don't check that here -- the
// code we generate will check that the exception actually exists on ARMv6-M
"MemoryManagement" | "BusFault" | "UsageFault" | "SecureFault" | "SVCall"
| "DebugMonitor" | "PendSV" => {} // OK
"SysTick" => {
if analysis.timer_queues.get(&task.args.core).is_some() {
return Err(parse::Error::new(
name.span(),
"this exception can't be used because it's being used by the runtime",
));
} else {
// OK
}
}
_ => {
return Err(parse::Error::new(
name.span(),
"only exceptions with configurable priority can be used as hardware tasks",
));
}
}
}
// Check that late resources have not been assigned to `init`
for res in &app.init.args.resources {
if app.resources.get(res).unwrap().expr.is_none() {
return Err(parse::Error::new(
res.span(),
"late resources can NOT be assigned to `init`",
));
}
}
if app.resources.iter().any(|(_, res)| res.expr.is_none()) {
// Check that `init` returns `LateResources` if there's any declared late resource
if !app.init.returns_late_resources {
return Err(parse::Error::new(
app.init.span,
"late resources have been specified so `init` must return `init::LateResources`",
));
}
} else if app.init.returns_late_resources {
// If there are no late resources the signature should be `fn(init::Context)`
if app.init.returns_late_resources {
return Err(parse::Error::new(
app.init.span,
"`init` signature must be `fn(init::Context)` if there are no late resources",
));
}
}
// Check that all referenced tasks have been declared
for task in app
.idle
.as_ref()
.map(|idle| -> Box<dyn Iterator<Item = _>> {
Box::new(idle.args.schedule.iter().chain(&idle.args.spawn))
})
.unwrap_or_else(|| Box::new(iter::empty()))
.chain(&app.init.args.schedule)
.chain(&app.init.args.spawn)
.chain(
app.exceptions
.values()
.flat_map(|e| e.args.schedule.iter().chain(&e.args.spawn)),
)
.chain(
app.interrupts
.values()
.flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)),
)
.chain(
app.tasks
.values()
.flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)),
)
// check that external (device-specific) interrupts are not named after known (Cortex-M)
// exceptions
for name in app
.extern_interrupts
.iter()
.flat_map(|(_, interrupts)| interrupts.keys())
{
if !app.tasks.contains_key(task) {
return Err(parse::Error::new(
task.span(),
"this task has NOT been declared",
));
let name_s = name.to_string();
match &*name_s {
"NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault"
| "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => {
return Err(parse::Error::new(
name.span(),
"Cortex-M exceptions can't be used as `extern` interrupts",
));
}
_ => {}
}
}
// Check that there are enough free interrupts to dispatch all tasks
let ndispatchers = app
.tasks
.values()
.map(|t| t.args.priority)
.collect::<HashSet<_>>()
.len();
if ndispatchers > app.free_interrupts.len() {
// check that there are enough external interrupts to dispatch the software tasks and the timer
// queue handler
for core in 0..app.args.cores {
let mut first = None;
let priorities = app
.software_tasks
.iter()
.filter_map(|(name, task)| {
if task.args.core == core {
first = Some(name);
Some(task.args.priority)
} else {
None
}
})
.chain(analysis.timer_queues.get(&core).map(|tq| tq.priority))
.collect::<HashSet<_>>();
let need = priorities.len();
let given = app
.extern_interrupts
.get(&core)
.map(|ei| ei.len())
.unwrap_or(0);
if need > given {
let s = if app.args.cores == 1 {
format!(
"not enough `extern` interrupts to dispatch \
all software tasks (need: {}; given: {})",
need, given
)
} else {
format!(
"not enough `extern` interrupts to dispatch \
all software tasks on this core (need: {}; given: {})",
need, given
)
};
return Err(parse::Error::new(first.unwrap().span(), &s));
}
}
let mut device = None;
let mut monotonic = None;
let mut peripherals = None;
for (k, v) in &app.args.custom {
let ks = k.to_string();
match &*ks {
"device" => match v {
CustomArg::Path(p) => device = Some(p),
_ => {
return Err(parse::Error::new(
k.span(),
"unexpected argument value; this should be a path",
));
}
},
"monotonic" => match v {
CustomArg::Path(p) => monotonic = Some(p),
_ => {
return Err(parse::Error::new(
k.span(),
"unexpected argument value; this should be a path",
));
}
},
"peripherals" => match v {
CustomArg::Bool(x) if app.args.cores == 1 => {
peripherals = if *x { Some(0) } else { None }
}
CustomArg::UInt(x) if app.args.cores != 1 => {
peripherals = if *x < u64::from(app.args.cores) {
Some(*x as u8)
} else {
return Err(parse::Error::new(
k.span(),
&format!(
"unexpected argument value; \
this should be an integer in the range 0..={}",
app.args.cores
),
));
}
}
_ => {
return Err(parse::Error::new(
k.span(),
if app.args.cores == 1 {
"unexpected argument value; this should be a boolean"
} else {
"unexpected argument value; this should be an integer"
},
));
}
},
_ => {
return Err(parse::Error::new(k.span(), "unexpected argument"));
}
}
}
if !analysis.timer_queues.is_empty() && monotonic.is_none() {
return Err(parse::Error::new(
Span::call_site(),
&*format!(
"{} free interrupt{} (`extern {{ .. }}`) {} required to dispatch all soft tasks",
ndispatchers,
if ndispatchers > 1 { "s" } else { "" },
if ndispatchers > 1 { "are" } else { "is" },
),
"a `monotonic` timer must be specified to use the `schedule` API",
));
}
// Check that free interrupts are not being used
for (handler, interrupt) in &app.interrupts {
let name = interrupt.args.binds(handler);
if app.free_interrupts.contains_key(name) {
return Err(parse::Error::new(
name.span(),
"free interrupts (`extern { .. }`) can't be used as interrupt handlers",
));
}
if let Some(device) = device {
Ok(Extra {
device,
monotonic,
peripherals,
})
} else {
Err(parse::Error::new(
Span::call_site(),
"a `device` argument must be specified in `#[rtfm::app]`",
))
}
Ok(())
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,26 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::analyze::Analysis;
/// Generates compile-time assertions that check that types implement the `Send` / `Sync` traits
pub fn codegen(core: u8, analysis: &Analysis) -> Vec<TokenStream2> {
let mut stmts = vec![];
// we don't generate *all* assertions on all cores because the user could conditionally import a
// type only on some core (e.g. `#[cfg(core = "0")] use some::Type;`)
if let Some(types) = analysis.send_types.get(&core) {
for ty in types {
stmts.push(quote!(rtfm::export::assert_send::<#ty>();));
}
}
if let Some(types) = analysis.sync_types.get(&core) {
for ty in types {
stmts.push(quote!(rtfm::export::assert_sync::<#ty>();));
}
}
stmts
}

View file

@ -0,0 +1,178 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::ast::App;
use crate::{analyze::Analysis, check::Extra, codegen::util};
/// Generates task dispatchers
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
let mut items = vec![];
for (&receiver, dispatchers) in &analysis.channels {
let interrupts = &analysis.interrupts[&receiver];
for (&level, channels) in dispatchers {
let mut stmts = vec![];
for (&sender, channel) in channels {
let cfg_sender = util::cfg_core(sender, app.args.cores);
let variants = channel
.tasks
.iter()
.map(|name| {
let cfgs = &app.software_tasks[name].cfgs;
quote!(
#(#cfgs)*
#name
)
})
.collect::<Vec<_>>();
let doc = format!(
"Software tasks spawned from core #{} to be dispatched at priority level {} by core #{}",
sender, level, receiver,
);
let t = util::spawn_t_ident(receiver, level, sender);
items.push(quote!(
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
#[doc = #doc]
enum #t {
#(#variants,)*
}
));
let n = util::capacity_typenum(channel.capacity, true);
let rq = util::rq_ident(receiver, level, sender);
let (rq_attr, rq_ty, rq_expr) = if sender == receiver {
(
cfg_sender.clone(),
quote!(rtfm::export::SCRQ<#t, #n>),
quote!(rtfm::export::Queue(unsafe {
rtfm::export::iQueue::u8_sc()
})),
)
} else {
(
Some(quote!(#[rtfm::export::shared])),
quote!(rtfm::export::MCRQ<#t, #n>),
quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())),
)
};
let doc = format!(
"Queue of tasks sent by core #{} ready to be dispatched by core #{} at priority level {}",
sender,
receiver,
level
);
items.push(quote!(
#[doc = #doc]
#rq_attr
static mut #rq: #rq_ty = #rq_expr;
));
if let Some(ceiling) = channel.ceiling {
items.push(quote!(
#cfg_sender
struct #rq<'a> {
priority: &'a rtfm::export::Priority,
}
));
items.push(util::impl_mutex(
extra,
&[],
cfg_sender.as_ref(),
false,
&rq,
rq_ty,
ceiling,
quote!(&mut #rq),
));
}
let arms = channel
.tasks
.iter()
.map(|name| {
let task = &app.software_tasks[name];
let cfgs = &task.cfgs;
let fq = util::fq_ident(name, sender);
let inputs = util::inputs_ident(name, sender);
let (_, tupled, pats, _) = util::regroup_inputs(&task.inputs);
let (let_instant, instant) = if app.uses_schedule(receiver) {
let instants = util::instants_ident(name, sender);
(
quote!(
let instant =
#instants.get_unchecked(usize::from(index)).as_ptr().read();
),
quote!(, instant),
)
} else {
(quote!(), quote!())
};
let locals_new = if task.locals.is_empty() {
quote!()
} else {
quote!(#name::Locals::new(),)
};
quote!(
#(#cfgs)*
#t::#name => {
let #tupled =
#inputs.get_unchecked(usize::from(index)).as_ptr().read();
#let_instant
#fq.split().0.enqueue_unchecked(index);
let priority = &rtfm::export::Priority::new(PRIORITY);
#name(
#locals_new
#name::Context::new(priority #instant)
#(,#pats)*
)
}
)
})
.collect::<Vec<_>>();
stmts.push(quote!(
while let Some((task, index)) = #rq.split().1.dequeue() {
match task {
#(#arms)*
}
}
));
}
let doc = format!(
"Interrupt handler used by core #{} to dispatch tasks at priority {}",
receiver, level
);
let cfg_receiver = util::cfg_core(receiver, app.args.cores);
let interrupt = &interrupts[&level];
items.push(quote!(
#[allow(non_snake_case)]
#[doc = #doc]
#[no_mangle]
#cfg_receiver
unsafe fn #interrupt() {
/// The priority of this interrupt handler
const PRIORITY: u8 = #level;
rtfm::export::run(PRIORITY, || {
#(#stmts)*
});
}
));
}
}
items
}

View file

@ -0,0 +1,121 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
check::Extra,
codegen::{locals, module, resources_struct, util},
};
/// Generate support code for hardware tasks (`#[exception]`s and `#[interrupt]`s)
pub fn codegen(
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> (
// const_app_hardware_tasks -- interrupt handlers and `${task}Resources` constructors
Vec<TokenStream2>,
// root_hardware_tasks -- items that must be placed in the root of the crate:
// - `${task}Locals` structs
// - `${task}Resources` structs
// - `${task}` modules
Vec<TokenStream2>,
// user_hardware_tasks -- the `#[task]` functions written by the user
Vec<TokenStream2>,
) {
let mut const_app = vec![];
let mut root = vec![];
let mut user_tasks = vec![];
for (name, task) in &app.hardware_tasks {
let core = task.args.core;
let cfg_core = util::cfg_core(core, app.args.cores);
let (let_instant, instant) = if app.uses_schedule(core) {
let m = extra.monotonic();
(
Some(quote!(let instant = <#m as rtfm::Monotonic>::now();)),
Some(quote!(, instant)),
)
} else {
(None, None)
};
let locals_new = if task.locals.is_empty() {
quote!()
} else {
quote!(#name::Locals::new(),)
};
let symbol = task.args.binds(name);
let priority = task.args.priority;
const_app.push(quote!(
#[allow(non_snake_case)]
#[no_mangle]
#cfg_core
unsafe fn #symbol() {
const PRIORITY: u8 = #priority;
#let_instant
rtfm::export::run(PRIORITY, || {
crate::#name(
#locals_new
#name::Context::new(&rtfm::export::Priority::new(PRIORITY) #instant)
)
});
}
));
let mut needs_lt = false;
// `${task}Resources`
if !task.args.resources.is_empty() {
let (item, constructor) = resources_struct::codegen(
Context::HardwareTask(name),
priority,
&mut needs_lt,
app,
analysis,
);
root.push(item);
const_app.push(constructor);
}
root.push(module::codegen(
Context::HardwareTask(name),
needs_lt,
app,
extra,
));
// `${task}Locals`
let mut locals_pat = None;
if !task.locals.is_empty() {
let (struct_, pat) = locals::codegen(Context::HardwareTask(name), &task.locals, app);
root.push(struct_);
locals_pat = Some(pat);
}
let attrs = &task.attrs;
let context = &task.context;
let stmts = &task.stmts;
user_tasks.push(quote!(
#(#attrs)*
#[allow(non_snake_case)]
fn #name(#(#locals_pat,)* #context: #name::Context) {
use rtfm::Mutex as _;
#(#stmts)*
}
));
}
(const_app, root, user_tasks)
}

View file

@ -0,0 +1,92 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
check::Extra,
codegen::{locals, module, resources_struct, util},
};
/// Generates support code for `#[idle]` functions
pub fn codegen(
core: u8,
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> (
// const_app_idle -- the `${idle}Resources` constructor
Option<TokenStream2>,
// root_idle -- items that must be placed in the root of the crate:
// - the `${idle}Locals` struct
// - the `${idle}Resources` struct
// - the `${idle}` module, which contains types like `${idle}::Context`
Vec<TokenStream2>,
// user_idle
Option<TokenStream2>,
// call_idle
TokenStream2,
) {
if let Some(idle) = app.idles.get(&core) {
let mut needs_lt = false;
let mut const_app = None;
let mut root_idle = vec![];
let mut locals_pat = None;
let mut locals_new = None;
if !idle.args.resources.is_empty() {
let (item, constructor) =
resources_struct::codegen(Context::Idle(core), 0, &mut needs_lt, app, analysis);
root_idle.push(item);
const_app = Some(constructor);
}
let name = &idle.name;
if !idle.locals.is_empty() {
let (locals, pat) = locals::codegen(Context::Idle(core), &idle.locals, app);
locals_new = Some(quote!(#name::Locals::new()));
locals_pat = Some(pat);
root_idle.push(locals);
}
root_idle.push(module::codegen(Context::Idle(core), needs_lt, app, extra));
let cfg_core = util::cfg_core(core, app.args.cores);
let attrs = &idle.attrs;
let context = &idle.context;
let stmts = &idle.stmts;
let user_idle = Some(quote!(
#cfg_core
#(#attrs)*
#[allow(non_snake_case)]
fn #name(#(#locals_pat,)* #context: #name::Context) -> ! {
use rtfm::Mutex as _;
#(#stmts)*
}
));
let call_idle = quote!(#name(
#(#locals_new,)*
#name::Context::new(&rtfm::export::Priority::new(0))
));
(
const_app,
root_idle,
user_idle,
call_idle,
)
} else {
(
None,
vec![],
None,
quote!(loop {
rtfm::export::wfi()
}),
)
}
}

112
macros/src/codegen/init.rs Normal file
View file

@ -0,0 +1,112 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
check::Extra,
codegen::{locals, module, resources_struct, util},
};
/// Generates support code for `#[init]` functions
pub fn codegen(
core: u8,
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> (
// const_app_idle -- the `${init}Resources` constructor
Option<TokenStream2>,
// root_init -- items that must be placed in the root of the crate:
// - the `${init}Locals` struct
// - the `${init}Resources` struct
// - the `${init}LateResources` struct
// - the `${init}` module, which contains types like `${init}::Context`
Vec<TokenStream2>,
// user_init -- the `#[init]` function written by the user
Option<TokenStream2>,
// call_init -- the call to the user `#[init]` if there's one
Option<TokenStream2>,
) {
if let Some(init) = app.inits.get(&core) {
let cfg_core = util::cfg_core(core, app.args.cores);
let mut needs_lt = false;
let name = &init.name;
let mut root_init = vec![];
let ret = {
let late_fields = analysis
.late_resources
.get(&core)
.map(|resources| {
resources
.iter()
.map(|name| {
let ty = &app.late_resources[name].ty;
quote!(pub #name: #ty)
})
.collect::<Vec<_>>()
})
.unwrap_or(vec![]);
if !late_fields.is_empty() {
let late_resources = util::late_resources_ident(&name);
root_init.push(quote!(
/// Resources initialized at runtime
#cfg_core
#[allow(non_snake_case)]
pub struct #late_resources {
#(#late_fields),*
}
));
Some(quote!(-> #name::LateResources))
} else {
None
}
};
let mut locals_pat = None;
let mut locals_new = None;
if !init.locals.is_empty() {
let (struct_, pat) = locals::codegen(Context::Init(core), &init.locals, app);
locals_new = Some(quote!(#name::Locals::new()));
locals_pat = Some(pat);
root_init.push(struct_);
}
let context = &init.context;
let attrs = &init.attrs;
let stmts = &init.stmts;
let user_init = Some(quote!(
#(#attrs)*
#cfg_core
#[allow(non_snake_case)]
fn #name(#(#locals_pat,)* #context: #name::Context) #ret {
#(#stmts)*
}
));
let mut const_app = None;
if !init.args.resources.is_empty() {
let (item, constructor) =
resources_struct::codegen(Context::Init(core), 0, &mut needs_lt, app, analysis);
root_init.push(item);
const_app = Some(constructor);
}
let call_init =
Some(quote!(let late = #name(#(#locals_new,)* #name::Context::new(core.into()));));
root_init.push(module::codegen(Context::Init(core), needs_lt, app, extra));
(const_app, root_init, user_init, call_init)
} else {
(None, vec![], None, None)
}
}

View file

@ -0,0 +1,94 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{
ast::{App, Local},
Context, Map,
};
use crate::codegen::util;
pub fn codegen(
ctxt: Context,
locals: &Map<Local>,
app: &App,
) -> (
// locals
TokenStream2,
// pat
TokenStream2,
) {
assert!(!locals.is_empty());
let runs_once = ctxt.runs_once();
let ident = util::locals_ident(ctxt, app);
let mut lt = None;
let mut fields = vec![];
let mut items = vec![];
let mut names = vec![];
let mut values = vec![];
let mut pats = vec![];
let mut has_cfgs = false;
for (name, local) in locals {
let lt = if runs_once {
quote!('static)
} else {
lt = Some(quote!('a));
quote!('a)
};
let cfgs = &local.cfgs;
has_cfgs |= !cfgs.is_empty();
let expr = &local.expr;
let ty = &local.ty;
fields.push(quote!(
#(#cfgs)*
#name: &#lt mut #ty
));
items.push(quote!(
#(#cfgs)*
static mut #name: #ty = #expr
));
values.push(quote!(
#(#cfgs)*
#name: &mut #name
));
names.push(name);
pats.push(quote!(
#(#cfgs)*
#name
));
}
if lt.is_some() && has_cfgs {
fields.push(quote!(__marker__: core::marker::PhantomData<&'a mut ()>));
values.push(quote!(__marker__: core::marker::PhantomData));
}
let locals = quote!(
#[allow(non_snake_case)]
#[doc(hidden)]
pub struct #ident<#lt> {
#(#fields),*
}
impl<#lt> #ident<#lt> {
#[inline(always)]
unsafe fn new() -> Self {
#(#items;)*
#ident {
#(#values),*
}
}
}
);
let ident = ctxt.ident(app);
(
locals,
quote!(#ident::Locals { #(#pats,)* .. }: #ident::Locals),
)
}

View file

@ -0,0 +1,328 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use crate::{check::Extra, codegen::util};
pub fn codegen(ctxt: Context, resources_tick: bool, app: &App, extra: &Extra) -> TokenStream2 {
let mut items = vec![];
let mut fields = vec![];
let mut values = vec![];
let name = ctxt.ident(app);
let core = ctxt.core(app);
let mut needs_instant = false;
let mut lt = None;
match ctxt {
Context::Init(core) => {
if app.uses_schedule(core) {
let m = extra.monotonic();
fields.push(quote!(
/// System start time = `Instant(0 /* cycles */)`
pub start: <#m as rtfm::Monotonic>::Instant
));
values.push(quote!(start: <#m as rtfm::Monotonic>::zero()));
fields.push(quote!(
/// Core (Cortex-M) peripherals minus the SysTick
pub core: rtfm::Peripherals
));
} else {
fields.push(quote!(
/// Core (Cortex-M) peripherals
pub core: rtfm::export::Peripherals
));
}
if extra.peripherals == Some(core) {
let device = extra.device;
fields.push(quote!(
/// Device peripherals
pub device: #device::Peripherals
));
values.push(quote!(device: #device::Peripherals::steal()));
}
values.push(quote!(core));
}
Context::Idle(..) => {}
Context::HardwareTask(..) => {
if app.uses_schedule(core) {
let m = extra.monotonic();
fields.push(quote!(
/// Time at which this handler started executing
pub start: <#m as rtfm::Monotonic>::Instant
));
values.push(quote!(start: instant));
needs_instant = true;
}
}
Context::SoftwareTask(..) => {
if app.uses_schedule(core) {
let m = extra.monotonic();
fields.push(quote!(
/// The time at which this task was scheduled to run
pub scheduled: <#m as rtfm::Monotonic>::Instant
));
values.push(quote!(scheduled: instant));
needs_instant = true;
}
}
}
if ctxt.has_locals(app) {
let ident = util::locals_ident(ctxt, app);
items.push(quote!(
#[doc(inline)]
pub use super::#ident as Locals;
));
}
if ctxt.has_resources(app) {
let ident = util::resources_ident(ctxt, app);
let lt = if resources_tick {
lt = Some(quote!('a));
Some(quote!('a))
} else {
None
};
items.push(quote!(
#[doc(inline)]
pub use super::#ident as Resources;
));
fields.push(quote!(
/// Resources this task has access to
pub resources: Resources<#lt>
));
let priority = if ctxt.is_init() {
None
} else {
Some(quote!(priority))
};
values.push(quote!(resources: Resources::new(#priority)));
}
if ctxt.uses_schedule(app) {
let doc = "Tasks that can be `schedule`-d from this context";
if ctxt.is_init() {
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Schedule {
_not_send: core::marker::PhantomData<*mut ()>,
}
));
fields.push(quote!(
#[doc = #doc]
pub schedule: Schedule
));
values.push(quote!(
schedule: Schedule { _not_send: core::marker::PhantomData }
));
} else {
lt = Some(quote!('a));
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Schedule<'a> {
priority: &'a rtfm::export::Priority,
}
impl<'a> Schedule<'a> {
#[doc(hidden)]
#[inline(always)]
pub unsafe fn priority(&self) -> &rtfm::export::Priority {
&self.priority
}
}
));
fields.push(quote!(
#[doc = #doc]
pub schedule: Schedule<'a>
));
values.push(quote!(
schedule: Schedule { priority }
));
}
}
if ctxt.uses_spawn(app) {
let doc = "Tasks that can be `spawn`-ed from this context";
if ctxt.is_init() {
fields.push(quote!(
#[doc = #doc]
pub spawn: Spawn
));
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Spawn {
_not_send: core::marker::PhantomData<*mut ()>,
}
));
values.push(quote!(spawn: Spawn { _not_send: core::marker::PhantomData }));
} else {
lt = Some(quote!('a));
fields.push(quote!(
#[doc = #doc]
pub spawn: Spawn<'a>
));
let mut instant_method = None;
if ctxt.is_idle() {
items.push(quote!(
#[doc = #doc]
#[derive(Clone, Copy)]
pub struct Spawn<'a> {
priority: &'a rtfm::export::Priority,
}
));
values.push(quote!(spawn: Spawn { priority }));
} else {
let instant_field = if app.uses_schedule(core) {
let m = extra.monotonic();
needs_instant = true;
instant_method = Some(quote!(
pub unsafe fn instant(&self) -> <#m as rtfm::Monotonic>::Instant {
self.instant
}
));
Some(quote!(instant: <#m as rtfm::Monotonic>::Instant,))
} else {
None
};
items.push(quote!(
/// Tasks that can be spawned from this context
#[derive(Clone, Copy)]
pub struct Spawn<'a> {
#instant_field
priority: &'a rtfm::export::Priority,
}
));
let _instant = if needs_instant {
Some(quote!(, instant))
} else {
None
};
values.push(quote!(
spawn: Spawn { priority #_instant }
));
}
items.push(quote!(
impl<'a> Spawn<'a> {
#[doc(hidden)]
#[inline(always)]
pub unsafe fn priority(&self) -> &rtfm::export::Priority {
self.priority
}
#instant_method
}
));
}
}
if let Context::Init(core) = ctxt {
let init = &app.inits[&core];
if init.returns_late_resources {
let late_resources = util::late_resources_ident(&init.name);
items.push(quote!(
#[doc(inline)]
pub use super::#late_resources as LateResources;
));
}
}
let doc = match ctxt {
Context::Idle(_) => "Idle loop",
Context::Init(_) => "Initialization function",
Context::HardwareTask(_) => "Hardware task",
Context::SoftwareTask(_) => "Software task",
};
let core = if ctxt.is_init() {
if app.uses_schedule(core) {
Some(quote!(core: rtfm::Peripherals,))
} else {
Some(quote!(core: rtfm::export::Peripherals,))
}
} else {
None
};
let priority = if ctxt.is_init() {
None
} else {
Some(quote!(priority: &#lt rtfm::export::Priority))
};
let instant = if needs_instant {
let m = extra.monotonic();
Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant))
} else {
None
};
items.push(quote!(
/// Execution context
pub struct Context<#lt> {
#(#fields,)*
}
impl<#lt> Context<#lt> {
#[inline(always)]
pub unsafe fn new(#core #priority #instant) -> Self {
Context {
#(#values,)*
}
}
}
));
if !items.is_empty() {
let cfg_core = util::cfg_core(ctxt.core(app), app.args.cores);
quote!(
#[allow(non_snake_case)]
#[doc = #doc]
#cfg_core
pub mod #name {
#(#items)*
}
)
} else {
quote!()
}
}

View file

@ -0,0 +1,139 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use crate::{analyze::Analysis, check::Extra, codegen::util};
/// Generates code that runs after `#[init]` returns
pub fn codegen(
core: u8,
analysis: &Analysis,
extra: &Extra,
) -> (Vec<TokenStream2>, Vec<TokenStream2>) {
let mut const_app = vec![];
let mut stmts = vec![];
// initialize late resources
if let Some(late_resources) = analysis.late_resources.get(&core) {
for name in late_resources {
// if it's live
if analysis.locations.get(name).is_some() {
stmts.push(quote!(#name.as_mut_ptr().write(late.#name);));
}
}
}
if analysis.timer_queues.is_empty() {
// cross-initialization barriers -- notify *other* cores that their resources have been
// initialized
if analysis.initialization_barriers.contains_key(&core) {
let ib = util::init_barrier(core);
const_app.push(quote!(
#[rtfm::export::shared]
static #ib: rtfm::export::Barrier = rtfm::export::Barrier::new();
));
stmts.push(quote!(
#ib.release();
));
}
// then wait until the other cores have initialized *our* resources
for (&initializer, users) in &analysis.initialization_barriers {
if users.contains(&core) {
let ib = util::init_barrier(initializer);
stmts.push(quote!(
#ib.wait();
));
}
}
// cross-spawn barriers: wait until other cores are ready to receive messages
for (&receiver, senders) in &analysis.spawn_barriers {
if senders.get(&core) == Some(&false) {
let sb = util::spawn_barrier(receiver);
stmts.push(quote!(
#sb.wait();
));
}
}
} else {
// if the `schedule` API is used then we'll synchronize all cores to leave the
// `init`-ialization phase at the same time. In this case the rendezvous barrier makes the
// cross-initialization and spawn barriers unnecessary
let m = extra.monotonic();
if analysis.timer_queues.len() == 1 {
// reset the monotonic timer / counter
stmts.push(quote!(
<#m as rtfm::Monotonic>::reset();
));
} else {
// in the multi-core case we need a rendezvous (RV) barrier between *all* the cores that
// use the `schedule` API; otherwise one of the cores could observe the before-reset
// value of the monotonic counter
// (this may be easier to implement with `AtomicU8.fetch_sub` but that API is not
// available on ARMv6-M)
// this core will reset the monotonic counter
const FIRST: u8 = 0;
if core == FIRST {
for &i in analysis.timer_queues.keys() {
let rv = util::rendezvous_ident(i);
const_app.push(quote!(
#[rtfm::export::shared]
static #rv: rtfm::export::Barrier = rtfm::export::Barrier::new();
));
// wait until all the other cores have reached the RV point
if i != FIRST {
stmts.push(quote!(
#rv.wait();
));
}
}
let rv = util::rendezvous_ident(core);
stmts.push(quote!(
// the compiler fences are used to prevent `reset` from being re-ordering wrt to
// the atomic operations -- we don't know if `reset` contains load or store
// operations
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
// reset the counter
<#m as rtfm::Monotonic>::reset();
core::sync::atomic::compiler_fence(core::sync::atomic::Ordering::SeqCst);
// now unblock all the other cores
#rv.release();
));
} else {
let rv = util::rendezvous_ident(core);
// let the first core know that we have reached the RV point
stmts.push(quote!(
#rv.release();
));
let rv = util::rendezvous_ident(FIRST);
// wait until the first core has reset the monotonic timer
stmts.push(quote!(
#rv.wait();
));
}
}
}
// enable the interrupts -- this completes the `init`-ialization phase
stmts.push(quote!(rtfm::export::interrupt::enable();));
(const_app, stmts)
}

View file

@ -0,0 +1,150 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::ast::{App, HardwareTaskKind};
use crate::{analyze::Analysis, check::Extra, codegen::util};
/// Generates code that runs before `#[init]`
pub fn codegen(
core: u8,
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> (
// `const_app_pre_init` -- `static` variables for barriers
Vec<TokenStream2>,
// `pre_init_stmts`
Vec<TokenStream2>,
) {
let mut const_app = vec![];
let mut stmts = vec![];
// disable interrupts -- `init` must run with interrupts disabled
stmts.push(quote!(rtfm::export::interrupt::disable();));
// populate this core `FreeQueue`s
for (name, senders) in &analysis.free_queues {
let task = &app.software_tasks[name];
let cap = task.args.capacity;
for &sender in senders.keys() {
if sender == core {
let fq = util::fq_ident(name, sender);
stmts.push(quote!(
(0..#cap).for_each(|i| #fq.enqueue_unchecked(i));
));
}
}
}
stmts.push(quote!(
let mut core = rtfm::export::Peripherals::steal();
));
let device = extra.device;
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
// unmask interrupts and set their priorities
for (&priority, name) in analysis
.interrupts
.get(&core)
.iter()
.flat_map(|interrupts| *interrupts)
.chain(app.hardware_tasks.iter().flat_map(|(name, task)| {
if task.kind == HardwareTaskKind::Interrupt {
Some((&task.args.priority, task.args.binds(name)))
} else {
// we do exceptions in another pass
None
}
}))
{
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
// NOTE this also checks that the interrupt exists in the `Interrupt` enumeration
stmts.push(quote!(
core.NVIC.set_priority(
#device::Interrupt::#name,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);
));
// NOTE unmask the interrupt *after* setting its priority: changing the priority of a pended
// interrupt is implementation defined
stmts.push(quote!(core.NVIC.enable(#device::Interrupt::#name);));
}
// cross-spawn barriers: now that priorities have been set and the interrupts have been unmasked
// we are ready to receive messages from *other* cores
if analysis.spawn_barriers.contains_key(&core) {
let sb = util::spawn_barrier(core);
const_app.push(quote!(
#[rtfm::export::shared]
static #sb: rtfm::export::Barrier = rtfm::export::Barrier::new();
));
// unblock cores that may send us a message
stmts.push(quote!(
#sb.release();
));
}
// set exception priorities
for (name, priority) in app.hardware_tasks.iter().filter_map(|(name, task)| {
if task.kind == HardwareTaskKind::Exception {
Some((task.args.binds(name), task.args.priority))
} else {
None
}
}) {
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
stmts.push(quote!(core.SCB.set_priority(
rtfm::export::SystemHandler::#name,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);));
}
// initialize the SysTick
if let Some(tq) = analysis.timer_queues.get(&core) {
let priority = tq.priority;
// compile time assert that this priority is supported by the device
stmts.push(quote!(let _ = [(); ((1 << #nvic_prio_bits) - #priority as usize)];));
stmts.push(quote!(core.SCB.set_priority(
rtfm::export::SystemHandler::SysTick,
rtfm::export::logical2hw(#priority, #nvic_prio_bits),
);));
stmts.push(quote!(
core.SYST.set_clock_source(rtfm::export::SystClkSource::Core);
core.SYST.enable_counter();
core.DCB.enable_trace();
));
}
// if there's no user `#[idle]` then optimize returning from interrupt handlers
if app.idles.get(&core).is_none() {
// Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
stmts.push(quote!(core.SCB.scr.modify(|r| r | 1 << 1);));
}
// cross-spawn barriers: wait until other cores are ready to receive messages
for (&receiver, senders) in &analysis.spawn_barriers {
// only block here if `init` can send messages to `receiver`
if senders.get(&core) == Some(&true) {
let sb = util::spawn_barrier(receiver);
stmts.push(quote!(
#sb.wait();
));
}
}
(const_app, stmts)
}

View file

@ -0,0 +1,115 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{
analyze::{Location, Ownership},
ast::App,
};
use crate::{analyze::Analysis, check::Extra, codegen::util};
/// Generates `static [mut]` variables and resource proxies
pub fn codegen(
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> (
// const_app -- the `static [mut]` variables behind the proxies
Vec<TokenStream2>,
// mod_resources -- the `resources` module
TokenStream2,
) {
let mut const_app = vec![];
let mut mod_resources = vec![];
for (name, res, expr, loc) in app.resources(analysis) {
let cfgs = &res.cfgs;
let ty = &res.ty;
{
let loc_attr = match loc {
Location::Owned {
core,
cross_initialized: false,
} => util::cfg_core(*core, app.args.cores),
// shared `static`s and cross-initialized resources need to be in `.shared` memory
_ => Some(quote!(#[rtfm::export::shared])),
};
let (ty, expr) = if let Some(expr) = expr {
(quote!(#ty), quote!(#expr))
} else {
(
quote!(core::mem::MaybeUninit<#ty>),
quote!(core::mem::MaybeUninit::uninit()),
)
};
let attrs = &res.attrs;
const_app.push(quote!(
#loc_attr
#(#attrs)*
#(#cfgs)*
static mut #name: #ty = #expr;
));
}
// generate a resource proxy if needed
if res.mutability.is_some() {
if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
let cfg_core = util::cfg_core(loc.core().expect("UNREACHABLE"), app.args.cores);
mod_resources.push(quote!(
#(#cfgs)*
#cfg_core
pub struct #name<'a> {
priority: &'a Priority,
}
#(#cfgs)*
#cfg_core
impl<'a> #name<'a> {
#[inline(always)]
pub unsafe fn new(priority: &'a Priority) -> Self {
#name { priority }
}
#[inline(always)]
pub unsafe fn priority(&self) -> &Priority {
self.priority
}
}
));
let ptr = if expr.is_none() {
quote!(#name.as_mut_ptr())
} else {
quote!(&mut #name)
};
const_app.push(util::impl_mutex(
extra,
cfgs,
cfg_core.as_ref(),
true,
name,
quote!(#ty),
*ceiling,
ptr,
));
}
}
}
let mod_resources = if mod_resources.is_empty() {
quote!()
} else {
quote!(mod resources {
use rtfm::export::Priority;
#(#mod_resources)*
})
};
(const_app, mod_resources)
}

View file

@ -0,0 +1,178 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use crate::{analyze::Analysis, codegen::util};
pub fn codegen(
ctxt: Context,
priority: u8,
needs_lt: &mut bool,
app: &App,
analysis: &Analysis,
) -> (TokenStream2, TokenStream2) {
let mut lt = None;
let resources = match ctxt {
Context::Init(core) => &app.inits[&core].args.resources,
Context::Idle(core) => &app.idles[&core].args.resources,
Context::HardwareTask(name) => &app.hardware_tasks[name].args.resources,
Context::SoftwareTask(name) => &app.software_tasks[name].args.resources,
};
let mut fields = vec![];
let mut values = vec![];
let mut has_cfgs = false;
for name in resources {
let (res, expr) = app.resource(name).expect("UNREACHABLE");
let cfgs = &res.cfgs;
has_cfgs |= !cfgs.is_empty();
let mut_ = res.mutability;
let ty = &res.ty;
if ctxt.is_init() {
if !analysis.ownerships.contains_key(name) {
// owned by `init`
fields.push(quote!(
#(#cfgs)*
pub #name: &'static #mut_ #ty
));
values.push(quote!(
#(#cfgs)*
#name: &#mut_ #name
));
} else {
// owned by someone else
lt = Some(quote!('a));
fields.push(quote!(
#(#cfgs)*
pub #name: &'a mut #ty
));
values.push(quote!(
#(#cfgs)*
#name: &mut #name
));
}
} else {
let ownership = &analysis.ownerships[name];
if ownership.needs_lock(priority) {
if mut_.is_none() {
lt = Some(quote!('a));
fields.push(quote!(
#(#cfgs)*
pub #name: &'a #ty
));
} else {
// resource proxy
lt = Some(quote!('a));
fields.push(quote!(
#(#cfgs)*
pub #name: resources::#name<'a>
));
values.push(quote!(
#(#cfgs)*
#name: resources::#name::new(priority)
));
continue;
}
} else {
let lt = if ctxt.runs_once() {
quote!('static)
} else {
lt = Some(quote!('a));
quote!('a)
};
if ownership.is_owned() || mut_.is_none() {
fields.push(quote!(
#(#cfgs)*
pub #name: &#lt #mut_ #ty
));
} else {
fields.push(quote!(
#(#cfgs)*
pub #name: &#lt mut #ty
));
}
}
let is_late = expr.is_none();
if is_late {
let expr = if mut_.is_some() {
quote!(&mut *#name.as_mut_ptr())
} else {
quote!(&*#name.as_ptr())
};
values.push(quote!(
#(#cfgs)*
#name: #expr
));
} else {
values.push(quote!(
#(#cfgs)*
#name: &#mut_ #name
));
}
}
}
if lt.is_some() {
*needs_lt = true;
// the struct could end up empty due to `cfg`s leading to an error due to `'a` being unused
if has_cfgs {
fields.push(quote!(
#[doc(hidden)]
pub __marker__: core::marker::PhantomData<&'a ()>
));
values.push(quote!(__marker__: core::marker::PhantomData))
}
}
let core = ctxt.core(app);
let cores = app.args.cores;
let cfg_core = util::cfg_core(core, cores);
let doc = format!("Resources `{}` has access to", ctxt.ident(app));
let ident = util::resources_ident(ctxt, app);
let item = quote!(
#cfg_core
#[allow(non_snake_case)]
#[doc = #doc]
pub struct #ident<#lt> {
#(#fields,)*
}
);
let arg = if ctxt.is_init() {
None
} else {
Some(quote!(priority: &#lt rtfm::export::Priority))
};
let constructor = quote!(
#cfg_core
impl<#lt> #ident<#lt> {
#[inline(always)]
unsafe fn new(#arg) -> Self {
#ident {
#(#values,)*
}
}
}
);
(item, constructor)
}

View file

@ -0,0 +1,95 @@
use std::collections::{BTreeMap, HashSet};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::ast::App;
use crate::{
check::Extra,
codegen::{schedule_body, util},
};
/// Generates all `${ctxt}::Schedule` methods
pub fn codegen(app: &App, extra: &Extra) -> Vec<TokenStream2> {
let mut items = vec![];
let mut seen = BTreeMap::<u8, HashSet<_>>::new();
for (scheduler, schedulees) in app.schedule_callers() {
let m = extra.monotonic();
let instant = quote!(<#m as rtfm::Monotonic>::Instant);
let sender = scheduler.core(app);
let cfg_sender = util::cfg_core(sender, app.args.cores);
let seen = seen.entry(sender).or_default();
let mut methods = vec![];
for name in schedulees {
let schedulee = &app.software_tasks[name];
let cfgs = &schedulee.cfgs;
let (args, _, untupled, ty) = util::regroup_inputs(&schedulee.inputs);
let args = &args;
if scheduler.is_init() {
// `init` uses a special `schedule` implementation; it doesn't use the
// `schedule_${name}` functions which are shared by other contexts
let body = schedule_body::codegen(scheduler, &name, app);
methods.push(quote!(
#(#cfgs)*
fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
#body
}
));
} else {
let schedule = util::schedule_ident(name, sender);
if !seen.contains(name) {
// generate a `schedule_${name}_S${sender}` function
seen.insert(name);
let body = schedule_body::codegen(scheduler, &name, app);
items.push(quote!(
#cfg_sender
#(#cfgs)*
unsafe fn #schedule(
priority: &rtfm::export::Priority,
instant: #instant
#(,#args)*
) -> Result<(), #ty> {
#body
}
));
}
methods.push(quote!(
#(#cfgs)*
#[inline(always)]
fn #name(&self, instant: #instant #(,#args)*) -> Result<(), #ty> {
unsafe {
#schedule(self.priority(), instant #(,#untupled)*)
}
}
));
}
}
let lt = if scheduler.is_init() {
None
} else {
Some(quote!('a))
};
let scheduler = scheduler.ident(app);
debug_assert!(!methods.is_empty());
items.push(quote!(
#cfg_sender
impl<#lt> #scheduler::Schedule<#lt> {
#(#methods)*
}
));
}
items
}

View file

@ -0,0 +1,61 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use syn::Ident;
use crate::codegen::util;
pub fn codegen(scheduler: Context, name: &Ident, app: &App) -> TokenStream2 {
let sender = scheduler.core(app);
let schedulee = &app.software_tasks[name];
let receiver = schedulee.args.core;
let fq = util::fq_ident(name, sender);
let tq = util::tq_ident(sender);
let (dequeue, enqueue) = if scheduler.is_init() {
(quote!(#fq.dequeue()), quote!(#tq.enqueue_unchecked(nr);))
} else {
(
quote!((#fq { priority }).lock(|fq| fq.split().1.dequeue())),
quote!((#tq { priority }).lock(|tq| tq.enqueue_unchecked(nr));),
)
};
let write_instant = if app.uses_schedule(receiver) {
let instants = util::instants_ident(name, sender);
Some(quote!(
#instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
))
} else {
None
};
let (_, tupled, _, _) = util::regroup_inputs(&schedulee.inputs);
let inputs = util::inputs_ident(name, sender);
let t = util::schedule_t_ident(sender);
quote!(
unsafe {
use rtfm::Mutex as _;
let input = #tupled;
if let Some(index) = #dequeue {
#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
#write_instant
let nr = rtfm::export::NotReady {
instant,
index,
task: #t::#name,
};
#enqueue
Ok(())
} else {
Err(input)
}
}
)
}

View file

@ -0,0 +1,169 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
check::Extra,
codegen::{locals, module, resources_struct, util},
};
pub fn codegen(
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> (
// const_app_software_tasks -- free queues, buffers and `${task}Resources` constructors
Vec<TokenStream2>,
// root_software_tasks -- items that must be placed in the root of the crate:
// - `${task}Locals` structs
// - `${task}Resources` structs
// - `${task}` modules
Vec<TokenStream2>,
// user_software_tasks -- the `#[task]` functions written by the user
Vec<TokenStream2>,
) {
let mut const_app = vec![];
let mut root = vec![];
let mut user_tasks = vec![];
for (name, task) in &app.software_tasks {
let receiver = task.args.core;
let inputs = &task.inputs;
let (_, _, _, input_ty) = util::regroup_inputs(inputs);
let cap = task.args.capacity;
let cap_lit = util::capacity_literal(cap);
let cap_ty = util::capacity_typenum(cap, true);
// create free queues and inputs / instants buffers
if let Some(free_queues) = analysis.free_queues.get(name) {
for (&sender, &ceiling) in free_queues {
let cfg_sender = util::cfg_core(sender, app.args.cores);
let fq = util::fq_ident(name, sender);
let (loc, fq_ty, fq_expr) = if receiver == sender {
(
cfg_sender.clone(),
quote!(rtfm::export::SCFQ<#cap_ty>),
quote!(rtfm::export::Queue(unsafe {
rtfm::export::iQueue::u8_sc()
})),
)
} else {
(
Some(quote!(#[rtfm::export::shared])),
quote!(rtfm::export::MCFQ<#cap_ty>),
quote!(rtfm::export::Queue(rtfm::export::iQueue::u8())),
)
};
let loc = &loc;
const_app.push(quote!(
/// Queue version of a free-list that keeps track of empty slots in
/// the following buffers
#loc
static mut #fq: #fq_ty = #fq_expr;
));
// Generate a resource proxy if needed
if let Some(ceiling) = ceiling {
const_app.push(quote!(
#cfg_sender
struct #fq<'a> {
priority: &'a rtfm::export::Priority,
}
));
const_app.push(util::impl_mutex(
extra,
&[],
cfg_sender.as_ref(),
false,
&fq,
fq_ty,
ceiling,
quote!(&mut #fq),
));
}
let ref elems = (0..cap)
.map(|_| quote!(core::mem::MaybeUninit::uninit()))
.collect::<Vec<_>>();
if app.uses_schedule(receiver) {
let m = extra.monotonic();
let instants = util::instants_ident(name, sender);
const_app.push(quote!(
#loc
/// Buffer that holds the instants associated to the inputs of a task
static mut #instants:
[core::mem::MaybeUninit<<#m as rtfm::Monotonic>::Instant>; #cap_lit] =
[#(#elems,)*];
));
}
let inputs = util::inputs_ident(name, sender);
const_app.push(quote!(
#loc
/// Buffer that holds the inputs of a task
static mut #inputs: [core::mem::MaybeUninit<#input_ty>; #cap_lit] =
[#(#elems,)*];
));
}
}
// `${task}Resources`
let mut needs_lt = false;
if !task.args.resources.is_empty() {
let (item, constructor) = resources_struct::codegen(
Context::SoftwareTask(name),
task.args.priority,
&mut needs_lt,
app,
analysis,
);
root.push(item);
const_app.push(constructor);
}
// `${task}Locals`
let mut locals_pat = None;
if !task.locals.is_empty() {
let (struct_, pat) = locals::codegen(Context::SoftwareTask(name), &task.locals, app);
locals_pat = Some(pat);
root.push(struct_);
}
let cfg_receiver = util::cfg_core(receiver, app.args.cores);
let context = &task.context;
let attrs = &task.attrs;
let cfgs = &task.cfgs;
let stmts = &task.stmts;
user_tasks.push(quote!(
#(#attrs)*
#(#cfgs)*
#cfg_receiver
#[allow(non_snake_case)]
fn #name(#(#locals_pat,)* #context: #name::Context #(,#inputs)*) {
use rtfm::Mutex as _;
#(#stmts)*
}
));
root.push(module::codegen(
Context::SoftwareTask(name),
needs_lt,
app,
extra,
));
}
(const_app, root, user_tasks)
}

127
macros/src/codegen/spawn.rs Normal file
View file

@ -0,0 +1,127 @@
use std::collections::{BTreeMap, HashSet};
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::ast::App;
use crate::{
analyze::Analysis,
check::Extra,
codegen::{spawn_body, util},
};
/// Generates all `${ctxt}::Spawn` methods
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
let mut items = vec![];
let mut seen = BTreeMap::<u8, HashSet<_>>::new();
for (spawner, spawnees) in app.spawn_callers() {
let sender = spawner.core(app);
let cfg_sender = util::cfg_core(sender, app.args.cores);
let seen = seen.entry(sender).or_default();
let mut methods = vec![];
for name in spawnees {
let spawnee = &app.software_tasks[name];
let receiver = spawnee.args.core;
let cfgs = &spawnee.cfgs;
let (args, _, untupled, ty) = util::regroup_inputs(&spawnee.inputs);
let args = &args;
if spawner.is_init() {
// `init` uses a special spawn implementation; it doesn't use the `spawn_${name}`
// functions which are shared by other contexts
let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
let let_instant = if app.uses_schedule(receiver) {
let m = extra.monotonic();
Some(quote!(let instant = unsafe { <#m as rtfm::Monotonic>::zero() };))
} else {
None
};
methods.push(quote!(
#(#cfgs)*
fn #name(&self #(,#args)*) -> Result<(), #ty> {
#let_instant
#body
}
));
} else {
let spawn = util::spawn_ident(name, sender);
if !seen.contains(name) {
// generate a `spawn_${name}_S${sender}` function
seen.insert(name);
let instant = if app.uses_schedule(receiver) {
let m = extra.monotonic();
Some(quote!(, instant: <#m as rtfm::Monotonic>::Instant))
} else {
None
};
let body = spawn_body::codegen(spawner, &name, app, analysis, extra);
items.push(quote!(
#cfg_sender
#(#cfgs)*
unsafe fn #spawn(
priority: &rtfm::export::Priority
#instant
#(,#args)*
) -> Result<(), #ty> {
#body
}
));
}
let (let_instant, instant) = if app.uses_schedule(receiver) {
let m = extra.monotonic();
(
Some(if spawner.is_idle() {
quote!(let instant = <#m as rtfm::Monotonic>::now();)
} else {
quote!(let instant = self.instant();)
}),
Some(quote!(, instant)),
)
} else {
(None, None)
};
methods.push(quote!(
#(#cfgs)*
#[inline(always)]
fn #name(&self #(,#args)*) -> Result<(), #ty> {
unsafe {
#let_instant
#spawn(self.priority() #instant #(,#untupled)*)
}
}
));
}
}
let lt = if spawner.is_init() {
None
} else {
Some(quote!('a))
};
let spawner = spawner.ident(app);
debug_assert!(!methods.is_empty());
items.push(quote!(
#cfg_sender
impl<#lt> #spawner::Spawn<#lt> {
#(#methods)*
}
));
}
items
}

View file

@ -0,0 +1,81 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::{ast::App, Context};
use syn::Ident;
use crate::{analyze::Analysis, check::Extra, codegen::util};
pub fn codegen(
spawner: Context,
name: &Ident,
app: &App,
analysis: &Analysis,
extra: &Extra,
) -> TokenStream2 {
let sender = spawner.core(app);
let spawnee = &app.software_tasks[name];
let priority = spawnee.args.priority;
let receiver = spawnee.args.core;
let write_instant = if app.uses_schedule(receiver) {
let instants = util::instants_ident(name, sender);
Some(quote!(
#instants.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(instant);
))
} else {
None
};
let t = util::spawn_t_ident(receiver, priority, sender);
let fq = util::fq_ident(name, sender);
let rq = util::rq_ident(receiver, priority, sender);
let (dequeue, enqueue) = if spawner.is_init() {
(
quote!(#fq.dequeue()),
quote!(#rq.enqueue_unchecked((#t::#name, index));),
)
} else {
(
quote!((#fq { priority }.lock(|fq| fq.split().1.dequeue()))),
quote!((#rq { priority }.lock(|rq| {
rq.split().0.enqueue_unchecked((#t::#name, index))
}));),
)
};
let device = extra.device;
let interrupt = &analysis.interrupts[&receiver][&priority];
let pend = if sender != receiver {
quote!(
#device::xpend(#receiver, #device::Interrupt::#interrupt);
)
} else {
quote!(
rtfm::pend(#device::Interrupt::#interrupt);
)
};
let (_, tupled, _, _) = util::regroup_inputs(&spawnee.inputs);
let inputs = util::inputs_ident(name, sender);
quote!(
unsafe {
use rtfm::Mutex as _;
let input = #tupled;
if let Some(index) = #dequeue {
#inputs.get_unchecked_mut(usize::from(index)).as_mut_ptr().write(input);
#write_instant
#enqueue
#pend
Ok(())
} else {
Err(input)
}
}
)
}

View file

@ -0,0 +1,147 @@
use proc_macro2::TokenStream as TokenStream2;
use quote::quote;
use rtfm_syntax::ast::App;
use crate::{analyze::Analysis, check::Extra, codegen::util};
/// Generates timer queues and timer queue handlers
pub fn codegen(app: &App, analysis: &Analysis, extra: &Extra) -> Vec<TokenStream2> {
let mut items = vec![];
for (&sender, timer_queue) in &analysis.timer_queues {
let cfg_sender = util::cfg_core(sender, app.args.cores);
let t = util::schedule_t_ident(sender);
// Enumeration of `schedule`-able tasks
{
let variants = timer_queue
.tasks
.iter()
.map(|name| {
let cfgs = &app.software_tasks[name].cfgs;
quote!(
#(#cfgs)*
#name
)
})
.collect::<Vec<_>>();
let doc = format!("Tasks that can be scheduled from core #{}", sender);
items.push(quote!(
#cfg_sender
#[doc = #doc]
#[allow(non_camel_case_types)]
#[derive(Clone, Copy)]
enum #t {
#(#variants,)*
}
));
}
let tq = util::tq_ident(sender);
// Static variable and resource proxy
{
let doc = format!("Core #{} timer queue", sender);
let m = extra.monotonic();
let n = util::capacity_typenum(timer_queue.capacity, false);
let tq_ty = quote!(rtfm::export::TimerQueue<#m, #t, #n>);
items.push(quote!(
#cfg_sender
#[doc = #doc]
static mut #tq: #tq_ty = rtfm::export::TimerQueue(
rtfm::export::BinaryHeap(
rtfm::export::iBinaryHeap::new()
)
);
#cfg_sender
struct #tq<'a> {
priority: &'a rtfm::export::Priority,
}
));
items.push(util::impl_mutex(
extra,
&[],
cfg_sender.as_ref(),
false,
&tq,
tq_ty,
timer_queue.ceiling,
quote!(&mut #tq),
));
}
// Timer queue handler
{
let device = extra.device;
let arms = timer_queue
.tasks
.iter()
.map(|name| {
let task = &app.software_tasks[name];
let cfgs = &task.cfgs;
let priority = task.args.priority;
let receiver = task.args.core;
let rq = util::rq_ident(receiver, priority, sender);
let rqt = util::spawn_t_ident(receiver, priority, sender);
let interrupt = &analysis.interrupts[&receiver][&priority];
let pend = if sender != receiver {
quote!(
#device::xpend(#receiver, #device::Interrupt::#interrupt);
)
} else {
quote!(
rtfm::pend(#device::Interrupt::#interrupt);
)
};
quote!(
#(#cfgs)*
#t::#name => {
(#rq { priority: &rtfm::export::Priority::new(PRIORITY) }).lock(|rq| {
rq.split().0.enqueue_unchecked((#rqt::#name, index))
});
#pend
}
)
})
.collect::<Vec<_>>();
let priority = timer_queue.priority;
items.push(quote!(
#cfg_sender
#[no_mangle]
unsafe fn SysTick() {
use rtfm::Mutex as _;
/// The priority of this handler
const PRIORITY: u8 = #priority;
rtfm::export::run(PRIORITY, || {
while let Some((task, index)) = (#tq {
// NOTE dynamic priority is always the static priority at this point
priority: &rtfm::export::Priority::new(PRIORITY),
})
// NOTE `inline(always)` produces faster and smaller code
.lock(#[inline(always)]
|tq| tq.dequeue())
{
match task {
#(#arms)*
}
}
});
}
));
}
}
items
}

253
macros/src/codegen/util.rs Normal file
View file

@ -0,0 +1,253 @@
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use rtfm_syntax::{ast::App, Context, Core};
use syn::{ArgCaptured, Attribute, Ident, IntSuffix, LitInt};
use crate::check::Extra;
/// Turns `capacity` into an unsuffixed integer literal
pub fn capacity_literal(capacity: u8) -> LitInt {
LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
}
/// Turns `capacity` into a type-level (`typenum`) integer
pub fn capacity_typenum(capacity: u8, round_up_to_power_of_two: bool) -> TokenStream2 {
let capacity = if round_up_to_power_of_two {
capacity.checked_next_power_of_two().expect("UNREACHABLE")
} else {
capacity
};
let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
quote!(rtfm::export::consts::#ident)
}
/// Generates a `#[cfg(core = "0")]` attribute if we are in multi-core mode
pub fn cfg_core(core: Core, cores: u8) -> Option<TokenStream2> {
if cores == 1 {
None
} else {
let core = core.to_string();
Some(quote!(#[cfg(core = #core)]))
}
}
/// Identifier for the free queue
///
/// There may be more than one free queue per task because we need one for each sender core so we
/// include the sender (e.g. `S0`) in the name
pub fn fq_ident(task: &Ident, sender: Core) -> Ident {
Ident::new(
&format!("{}_S{}_FQ", task.to_string(), sender),
Span::call_site(),
)
}
/// Generates a `Mutex` implementation
pub fn impl_mutex(
extra: &Extra,
cfgs: &[Attribute],
cfg_core: Option<&TokenStream2>,
resources_prefix: bool,
name: &Ident,
ty: TokenStream2,
ceiling: u8,
ptr: TokenStream2,
) -> TokenStream2 {
let (path, priority) = if resources_prefix {
(quote!(resources::#name), quote!(self.priority()))
} else {
(quote!(#name), quote!(self.priority))
};
let device = extra.device;
quote!(
#(#cfgs)*
#cfg_core
impl<'a> rtfm::Mutex for #path<'a> {
type T = #ty;
#[inline(always)]
fn lock<R>(&mut self, f: impl FnOnce(&mut #ty) -> R) -> R {
/// Priority ceiling
const CEILING: u8 = #ceiling;
unsafe {
rtfm::export::lock(
#ptr,
#priority,
CEILING,
#device::NVIC_PRIO_BITS,
f,
)
}
}
}
)
}
/// Generates an identifier for a cross-initialization barrier
pub fn init_barrier(initializer: Core) -> Ident {
Ident::new(&format!("IB{}", initializer), Span::call_site())
}
/// Generates an identifier for the `INPUTS` buffer (`spawn` & `schedule` API)
pub fn inputs_ident(task: &Ident, sender: Core) -> Ident {
Ident::new(&format!("{}_S{}_INPUTS", task, sender), Span::call_site())
}
/// Generates an identifier for the `INSTANTS` buffer (`schedule` API)
pub fn instants_ident(task: &Ident, sender: Core) -> Ident {
Ident::new(&format!("{}_S{}_INSTANTS", task, sender), Span::call_site())
}
/// Generates a pre-reexport identifier for the "late resources" struct
pub fn late_resources_ident(init: &Ident) -> Ident {
Ident::new(
&format!("{}LateResources", init.to_string()),
Span::call_site(),
)
}
/// Generates a pre-reexport identifier for the "locals" struct
pub fn locals_ident(ctxt: Context, app: &App) -> Ident {
let mut s = match ctxt {
Context::Init(core) => app.inits[&core].name.to_string(),
Context::Idle(core) => app.idles[&core].name.to_string(),
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
};
s.push_str("Locals");
Ident::new(&s, Span::call_site())
}
/// Generates an identifier for a rendezvous barrier
pub fn rendezvous_ident(core: Core) -> Ident {
Ident::new(&format!("RV{}", core), Span::call_site())
}
// Regroups the inputs of a task
//
// `inputs` could be &[`input: Foo`] OR &[`mut x: i32`, `ref y: i64`]
pub fn regroup_inputs(
inputs: &[ArgCaptured],
) -> (
// args e.g. &[`_0`], &[`_0: i32`, `_1: i64`]
Vec<TokenStream2>,
// tupled e.g. `_0`, `(_0, _1)`
TokenStream2,
// untupled e.g. &[`_0`], &[`_0`, `_1`]
Vec<TokenStream2>,
// ty e.g. `Foo`, `(i32, i64)`
TokenStream2,
) {
if inputs.len() == 1 {
let ty = &inputs[0].ty;
(
vec![quote!(_0: #ty)],
quote!(_0),
vec![quote!(_0)],
quote!(#ty),
)
} else {
let mut args = vec![];
let mut pats = vec![];
let mut tys = vec![];
for (i, input) in inputs.iter().enumerate() {
let i = Ident::new(&format!("_{}", i), Span::call_site());
let ty = &input.ty;
args.push(quote!(#i: #ty));
pats.push(quote!(#i));
tys.push(quote!(#ty));
}
let tupled = {
let pats = pats.clone();
quote!((#(#pats,)*))
};
let ty = quote!((#(#tys,)*));
(args, tupled, pats, ty)
}
}
/// Generates a pre-reexport identifier for the "resources" struct
pub fn resources_ident(ctxt: Context, app: &App) -> Ident {
let mut s = match ctxt {
Context::Init(core) => app.inits[&core].name.to_string(),
Context::Idle(core) => app.idles[&core].name.to_string(),
Context::HardwareTask(ident) | Context::SoftwareTask(ident) => ident.to_string(),
};
s.push_str("Resources");
Ident::new(&s, Span::call_site())
}
/// Generates an identifier for a ready queue
///
/// Each core may have several task dispatchers, one for each priority level. Each task dispatcher
/// in turn may use more than one ready queue because the queues are SPSC queues so one is needed
/// per sender core.
pub fn rq_ident(receiver: Core, priority: u8, sender: Core) -> Ident {
Ident::new(
&format!("R{}_P{}_S{}_RQ", receiver, priority, sender),
Span::call_site(),
)
}
/// Generates an identifier for a "schedule" function
///
/// The methods of the `Schedule` structs invoke these functions. As one task may be `schedule`-ed
/// by different cores we need one "schedule" function per possible task-sender pair
pub fn schedule_ident(name: &Ident, sender: Core) -> Ident {
Ident::new(
&format!("schedule_{}_S{}", name.to_string(), sender),
Span::call_site(),
)
}
/// Generates an identifier for the `enum` of `schedule`-able tasks
pub fn schedule_t_ident(core: Core) -> Ident {
Ident::new(&format!("T{}", core), Span::call_site())
}
/// Generates an identifier for a cross-spawn barrier
pub fn spawn_barrier(receiver: Core) -> Ident {
Ident::new(&format!("SB{}", receiver), Span::call_site())
}
/// Generates an identifier for a "spawn" function
///
/// The methods of the `Spawn` structs invoke these functions. As one task may be `spawn`-ed by
/// different cores we need one "spawn" function per possible task-sender pair
pub fn spawn_ident(name: &Ident, sender: Core) -> Ident {
Ident::new(
&format!("spawn_{}_S{}", name.to_string(), sender),
Span::call_site(),
)
}
/// Generates an identifier for the `enum` of `spawn`-able tasks
///
/// This identifier needs the same structure as the `RQ` identifier because there's one ready queue
/// for each of these `T` enums
pub fn spawn_t_ident(receiver: Core, priority: u8, sender: Core) -> Ident {
Ident::new(
&format!("R{}_P{}_S{}_T", receiver, priority, sender),
Span::call_site(),
)
}
/// Generates an identifier for a timer queue
///
/// At most there's one timer queue per core
pub fn tq_ident(core: Core) -> Ident {
Ident::new(&format!("TQ{}", core), Span::call_site())
}

View file

@ -6,307 +6,41 @@ extern crate proc_macro;
use proc_macro::TokenStream;
use std::{fs, path::Path};
use syn::parse_macro_input;
use rtfm_syntax::Settings;
mod analyze;
mod check;
mod codegen;
mod syntax;
#[cfg(test)]
mod tests;
/// Attribute used to declare a RTFM application
///
/// This attribute must be applied to a `const` item of type `()`. The `const` item is effectively
/// used as a `mod` item: its value must be a block that contains items commonly found in modules,
/// like functions and `static` variables.
///
/// The `app` attribute has one mandatory argument:
///
/// - `device = <path>`. The path must point to a device crate generated using [`svd2rust`]
/// **v0.14.x**.
///
/// [`svd2rust`]: https://crates.io/crates/svd2rust
///
/// The items allowed in the block value of the `const` item are specified below:
///
/// # 1. `static [mut]` variables
///
/// These variables are used as *resources*. Resources can be owned by tasks or shared between them.
/// Tasks can get `&mut` (exclusives) references to `static mut` resources, but only `&` (shared)
/// references to `static` resources. Lower priority tasks will need a [`lock`] to get a `&mut`
/// reference to a `static mut` resource shared with higher priority tasks.
///
/// [`lock`]: ../rtfm/trait.Mutex.html#method.lock
///
/// `static mut` resources that are shared by tasks that run at *different* priorities need to
/// implement the [`Send`] trait. Similarly, `static` resources that are shared by tasks that run at
/// *different* priorities need to implement the [`Sync`] trait.
///
/// [`Send`]: https://doc.rust-lang.org/core/marker/trait.Send.html
/// [`Sync`]: https://doc.rust-lang.org/core/marker/trait.Sync.html
///
/// Resources can be initialized at runtime by assigning them `()` (the unit value) as their initial
/// value in their declaration. These "late" resources need to be initialized an the end of the
/// `init` function.
///
/// The `app` attribute will inject a `resources` module in the root of the crate. This module
/// contains proxy `struct`s that implement the [`Mutex`] trait. The `struct` are named after the
/// `static mut` resources. For example, `static mut FOO: u32 = 0` will map to a `resources::FOO`
/// `struct` that implements the `Mutex<Data = u32>` trait.
///
/// [`Mutex`]: ../rtfm/trait.Mutex.html
///
/// # 2. `fn`
///
/// Functions must contain *one* of the following attributes: `init`, `idle`, `interrupt`,
/// `exception` or `task`. The attribute defines the role of the function in the application.
///
/// ## a. `#[init]`
///
/// This attribute indicates that the function is to be used as the *initialization function*. There
/// must be exactly one instance of the `init` attribute inside the `app` pseudo-module. The
/// signature of the `init` function must be `[unsafe] fn ()`.
///
/// The `init` function runs after memory (RAM) is initialized and runs with interrupts disabled.
/// Interrupts are re-enabled after `init` returns.
///
/// The `init` attribute accepts the following optional arguments:
///
/// - `resources = [RESOURCE_A, RESOURCE_B, ..]`. This is the list of resources this function has
/// access to.
///
/// - `schedule = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can
/// schedule to run in the future. *IMPORTANT*: This argument is accepted only if the `timer-queue`
/// feature has been enabled.
///
/// - `spawn = [task_a, task_b, ..]`. This is the list of *software* tasks that this function can
/// immediately spawn.
///
/// The `app` attribute will injected a *context* into this function that comprises the following
/// variables:
///
/// - `core: rtfm::Peripherals`. Exclusive access to core peripherals. See [`rtfm::Peripherals`] for
/// more details.
///
/// [`rtfm::Peripherals`]: ../rtfm/struct.Peripherals.html
///
/// - `device: <device-path>::Peripherals`. Exclusive access to device-specific peripherals.
/// `<device-path>` is the path to the device crate declared in the top `app` attribute.
///
/// - `start: rtfm::Instant`. The `start` time of the system: `Instant(0 /* cycles */)`. **NOTE**:
/// only present if the `timer-queue` feature is enabled.
///
/// - `resources: _`. An opaque `struct` that contains all the resources assigned to this function.
/// The resource maybe appear by value (`impl Singleton`), by references (`&[mut]`) or by proxy
/// (`impl Mutex`).
///
/// - `schedule: init::Schedule`. A `struct` that can be used to schedule *software* tasks.
/// **NOTE**: only present if the `timer-queue` feature is enabled.
///
/// - `spawn: init::Spawn`. A `struct` that can be used to spawn *software* tasks.
///
/// Other properties / constraints:
///
/// - The `init` function can **not** be called from software.
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &'static mut u32`.
///
/// - Assignments (e.g. `FOO = 0`) at the end of this function can be used to initialize *late*
/// resources.
///
/// ## b. `#[idle]`
///
/// This attribute indicates that the function is to be used as the *idle task*. There can be at
/// most once instance of the `idle` attribute inside the `app` pseudo-module. The signature of the
/// `idle` function must be `fn() -> !`.
///
/// The `idle` task is a special task that always runs in the background. The `idle` task runs at
/// the lowest priority of `0`. If the `idle` task is not defined then the runtime sets the
/// [SLEEPONEXIT] bit after executing `init`.
///
/// [SLEEPONEXIT]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100737/0100/power-management/sleep-mode/sleep-on-exit-bit
///
/// The `idle` attribute accepts the following optional arguments:
///
/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
///
/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
///
/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
/// The `app` attribute will injected a *context* into this function that comprises the following
/// variables:
///
/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
///
/// - `schedule: idle::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
///
/// - `spawn: idle::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
///
/// Other properties / constraints:
///
/// - The `idle` function can **not** be called from software.
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&'static mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &'static mut u32`.
///
/// ## c. `#[exception]`
///
/// This attribute indicates that the function is to be used as an *exception handler*, a type of
/// hardware task. The signature of `exception` handlers must be `[unsafe] fn()`.
///
/// The name of the function must match one of the Cortex-M exceptions that has [configurable
/// priority][system-handler].
///
/// [system-handler]: ../cortex_m/peripheral/scb/enum.SystemHandler.html
///
/// The `exception` attribute accepts the following optional arguments.
///
/// - `priority = <integer>`. This is the static priority of the exception handler. The value must
/// be in the range `1..=(1 << <device-path>::NVIC_PRIO_BITS)` where `<device-path>` is the path to
/// the device crate declared in the top `app` attribute. If this argument is omitted the priority
/// is assumed to be 1.
///
/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
///
/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
///
/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
/// The `app` attribute will injected a *context* into this function that comprises the following
/// variables:
///
/// - `start: rtfm::Instant`. The time at which this handler started executing. **NOTE**: only
/// present if the `timer-queue` feature is enabled.
///
/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
///
/// - `schedule: <exception-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
///
/// - `spawn: <exception-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
///
/// Other properties / constraints:
///
/// - `exception` handlers can **not** be called from software.
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &mut u32`.
///
/// ## d. `#[interrupt]`
///
/// This attribute indicates that the function is to be used as an *interrupt handler*, a type of
/// hardware task. The signature of `interrupt` handlers must be `[unsafe] fn()`.
///
/// The name of the function must match one of the device specific interrupts. See your device crate
/// documentation (`Interrupt` enum) for more details.
///
/// The `interrupt` attribute accepts the following optional arguments.
///
/// - `priority = (..)`. Same meaning / function as [`#[exception].priority`](#b-exception).
///
/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
///
/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
///
/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
/// The `app` attribute will injected a *context* into this function that comprises the following
/// variables:
///
/// - `start: rtfm::Instant`. Same meaning / function as [`exception.start`](#b-exception).
///
/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
///
/// - `schedule: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
///
/// - `spawn: <interrupt-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
///
/// Other properties / constraints:
///
/// - `interrupt` handlers can **not** be called from software, but they can be [`pend`]-ed by the
/// software from any context.
///
/// [`pend`]: ../rtfm/fn.pend.html
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &mut u32`.
///
/// ## e. `#[task]`
///
/// This attribute indicates that the function is to be used as a *software task*. The signature of
/// software `task`s must be `[unsafe] fn(<inputs>)`.
///
/// The `task` attribute accepts the following optional arguments.
///
/// - `capacity = <integer>`. The maximum number of instances of this task that can be queued onto
/// the task scheduler for execution. The value must be in the range `1..=255`. If the `capacity`
/// argument is omitted then the capacity will be inferred.
///
/// - `priority = <integer>`. Same meaning / function as [`#[exception].priority`](#b-exception).
///
/// - `resources = (..)`. Same meaning / function as [`#[init].resources`](#a-init).
///
/// - `schedule = (..)`. Same meaning / function as [`#[init].schedule`](#a-init).
///
/// - `spawn = (..)`. Same meaning / function as [`#[init].spawn`](#a-init).
///
/// The `app` attribute will injected a *context* into this function that comprises the following
/// variables:
///
/// - `scheduled: rtfm::Instant`. The time at which this task was scheduled to run. **NOTE**: Only
/// present if `timer-queue` is enabled.
///
/// - `resources: _`. Same meaning / function as [`init.resources`](#a-init).
///
/// - `schedule: <interrupt-name>::Schedule`. Same meaning / function as [`init.schedule`](#a-init).
///
/// - `spawn: <interrupt-name>::Spawn`. Same meaning / function as [`init.spawn`](#a-init).
///
/// Other properties / constraints:
///
/// - Software `task`s can **not** be called from software, but they can be `spawn`-ed and
/// `schedule`-d by the software from any context.
///
/// - The `static mut` variables declared at the beginning of this function will be transformed into
/// `&mut` references that are safe to access. For example, `static mut FOO: u32 = 0` will
/// become `FOO: &mut u32`.
///
/// # 3. `extern` block
///
/// This `extern` block contains a list of interrupts which are *not* used by the application as
/// hardware tasks. These interrupts will be used to dispatch software tasks. Each interrupt will be
/// used to dispatch *multiple* software tasks *at the same priority level*.
///
/// This `extern` block must only contain functions with signature `fn ()`. The names of these
/// functions must match the names of the target device interrupts.
///
/// Importantly, attributes can be applied to the functions inside this block. These attributes will
/// be forwarded to the interrupt handlers generated by the `app` attribute.
#[proc_macro_attribute]
pub fn app(args: TokenStream, input: TokenStream) -> TokenStream {
// Parse
let args = parse_macro_input!(args as syntax::AppArgs);
let input = parse_macro_input!(input as syntax::Input);
let app = match syntax::App::parse(input.items, args) {
let (app, analysis) = match rtfm_syntax::parse(
args,
input,
Settings {
parse_cores: cfg!(feature = "heterogeneous"),
parse_exception: true,
parse_extern_interrupt: true,
parse_interrupt: true,
parse_schedule: true,
optimize_priorities: true,
..Settings::default()
},
) {
Err(e) => return e.to_compile_error().into(),
Ok(app) => app,
Ok(x) => x,
};
// Check the specification
if let Err(e) = check::app(&app) {
return e.to_compile_error().into();
}
let extra = match check::app(&app, &analysis) {
Err(e) => return e.to_compile_error().into(),
Ok(x) => x,
};
// Ceiling analysis
let analysis = analyze::app(&app);
let analysis = analyze::app(analysis, &app);
// Code generation
let ts = codegen::app(&input.ident, &app, &analysis);
let ts = codegen::app(&app, &analysis, &extra);
// Try to write the expanded code to disk
if Path::new("target").exists() {

File diff suppressed because it is too large Load diff

5
macros/src/tests.rs Normal file
View file

@ -0,0 +1,5 @@
// NOTE these tests are specific to the Cortex-M port; `rtfm-syntax` has a more extensive test suite
// that tests functionality common to all the RTFM ports
mod multi;
mod single;

59
macros/src/tests/multi.rs Normal file
View file

@ -0,0 +1,59 @@
use quote::quote;
use rtfm_syntax::Settings;
#[test]
fn analyze() {
let (app, analysis) = rtfm_syntax::parse2(
quote!(device = pac, cores = 2),
quote!(
const APP: () = {
#[task(core = 0, priority = 1)]
fn a(_: a::Context) {}
#[task(core = 0, priority = 2)]
fn b(_: b::Context) {}
#[task(core = 1, priority = 1)]
fn c(_: c::Context) {}
#[task(core = 1, priority = 2)]
fn d(_: d::Context) {}
// first interrupt is assigned to the highest priority dispatcher
extern "C" {
#[core = 0]
fn B();
#[core = 0]
fn A();
#[core = 1]
fn A();
#[core = 1]
fn C();
}
};
),
Settings {
parse_cores: true,
parse_extern_interrupt: true,
..Settings::default()
},
)
.unwrap();
let analysis = crate::analyze::app(analysis, &app);
// first core
let interrupts0 = &analysis.interrupts[&0];
assert_eq!(interrupts0.len(), 2);
assert_eq!(interrupts0[&2].to_string(), "B");
assert_eq!(interrupts0[&1].to_string(), "A");
// second core
let interrupts1 = &analysis.interrupts[&1];
assert_eq!(interrupts1.len(), 2);
assert_eq!(interrupts1[&2].to_string(), "A");
assert_eq!(interrupts1[&1].to_string(), "C");
}

View file

@ -0,0 +1,35 @@
use quote::quote;
use rtfm_syntax::Settings;
#[test]
fn analyze() {
let (app, analysis) = rtfm_syntax::parse2(
quote!(device = pac),
quote!(
const APP: () = {
#[task(priority = 1)]
fn a(_: a::Context) {}
#[task(priority = 2)]
fn b(_: b::Context) {}
// first interrupt is assigned to the highest priority dispatcher
extern "C" {
fn B();
fn A();
}
};
),
Settings {
parse_extern_interrupt: true,
..Settings::default()
},
)
.unwrap();
let analysis = crate::analyze::app(analysis, &app);
let interrupts = &analysis.interrupts[&0];
assert_eq!(interrupts.len(), 2);
assert_eq!(interrupts[&2].to_string(), "B");
assert_eq!(interrupts[&1].to_string(), "A");
}

18
mc/Cargo.toml Normal file
View file

@ -0,0 +1,18 @@
[package]
authors = ["Jorge Aparicio <jorge@japaric.io>"]
edition = "2018"
name = "mc"
# this crate is only used for testing
publish = false
version = "0.0.0-alpha.0"
[dependencies]
cortex-m = "0.6.0"
[dependencies.cortex-m-rtfm]
path = ".."
features = ["heterogeneous"]
[dev-dependencies]
panic-halt = "0.2.0"
microamp = "0.1.0-alpha.1"

1
mc/README.md Normal file
View file

@ -0,0 +1 @@
This directory contains multi-core compile pass tests.

7
mc/examples/smallest.rs Normal file
View file

@ -0,0 +1,7 @@
#![no_main]
#![no_std]
use panic_halt as _;
#[rtfm::app(cores = 2, device = mc)]
const APP: () = {};

39
mc/examples/x-init-2.rs Normal file
View file

@ -0,0 +1,39 @@
//! [compile-pass] Cross initialization of late resources
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
use panic_halt as _;
#[rtfm::app(cores = 2, device = mc)]
const APP: () = {
extern "C" {
// owned by core #1 but initialized by core #0
static mut X: u32;
// owned by core #0 but initialized by core #1
static mut Y: u32;
}
#[init(core = 0, late = [X])]
fn a(_: a::Context) -> a::LateResources {
a::LateResources { X: 0 }
}
#[idle(core = 0, resources = [Y])]
fn b(_: b::Context) -> ! {
loop {}
}
#[init(core = 1)]
fn c(_: c::Context) -> c::LateResources {
c::LateResources { Y: 0 }
}
#[idle(core = 1, resources = [X])]
fn d(_: d::Context) -> ! {
loop {}
}
};

26
mc/examples/x-init.rs Normal file
View file

@ -0,0 +1,26 @@
//! [compile-pass] Split initialization of late resources
#![deny(unsafe_code)]
#![deny(warnings)]
#![no_main]
#![no_std]
use panic_halt as _;
#[rtfm::app(cores = 2, device = mc)]
const APP: () = {
extern "C" {
static mut X: u32;
static mut Y: u32;
}
#[init(core = 0, late = [X])]
fn a(_: a::Context) -> a::LateResources {
a::LateResources { X: 0 }
}
#[init(core = 1)]
fn b(_: b::Context) -> b::LateResources {
b::LateResources { Y: 0 }
}
};

36
mc/examples/x-schedule.rs Normal file
View file

@ -0,0 +1,36 @@
#![no_main]
#![no_std]
use panic_halt as _;
#[rtfm::app(cores = 2, device = mc, monotonic = mc::MT)]
const APP: () = {
#[init(core = 0, spawn = [ping])]
fn init(c: init::Context) {
c.spawn.ping().ok();
}
#[task(core = 0, schedule = [ping])]
fn pong(c: pong::Context) {
c.schedule.ping(c.scheduled + 1_000_000).ok();
}
#[task(core = 1, schedule = [pong])]
fn ping(c: ping::Context) {
c.schedule.pong(c.scheduled + 1_000_000).ok();
}
extern "C" {
#[core = 0]
fn I0();
#[core = 0]
fn I1();
#[core = 1]
fn I0();
#[core = 1]
fn I1();
}
};

20
mc/examples/x-spawn.rs Normal file
View file

@ -0,0 +1,20 @@
#![no_main]
#![no_std]
use panic_halt as _;
#[rtfm::app(cores = 2, device = mc)]
const APP: () = {
#[init(core = 0, spawn = [foo])]
fn init(c: init::Context) {
c.spawn.foo().ok();
}
#[task(core = 1)]
fn foo(_: foo::Context) {}
extern "C" {
#[core = 1]
fn I0();
}
};

99
mc/src/lib.rs Normal file
View file

@ -0,0 +1,99 @@
//! Fake multi-core PAC
#![no_std]
use core::{
cmp::Ordering,
ops::{Add, Sub},
};
use cortex_m::interrupt::Nr;
use rtfm::Monotonic;
// Fake priority bits
pub const NVIC_PRIO_BITS: u8 = 3;
pub struct CrossPend;
pub fn xpend(_core: u8, _interrupt: impl Nr) {}
/// Fake monotonic timer
pub struct MT;
unsafe impl Monotonic for MT {
type Instant = Instant;
fn ratio() -> u32 {
1
}
unsafe fn reset() {
(0xE0001004 as *mut u32).write_volatile(0)
}
fn now() -> Instant {
unsafe { Instant((0xE0001004 as *const u32).read_volatile() as i32) }
}
fn zero() -> Instant {
Instant(0)
}
}
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Instant(i32);
impl Add<u32> for Instant {
type Output = Instant;
fn add(self, rhs: u32) -> Self {
Instant(self.0.wrapping_add(rhs as i32))
}
}
impl Sub for Instant {
type Output = u32;
fn sub(self, rhs: Self) -> u32 {
self.0.checked_sub(rhs.0).unwrap() as u32
}
}
impl Ord for Instant {
fn cmp(&self, rhs: &Self) -> Ordering {
self.0.wrapping_sub(rhs.0).cmp(&0)
}
}
impl PartialOrd for Instant {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
// Fake interrupts
pub enum Interrupt {
I0,
I1,
I2,
I3,
I4,
I5,
I6,
I7,
}
unsafe impl Nr for Interrupt {
fn nr(&self) -> u8 {
match self {
Interrupt::I0 => 0,
Interrupt::I1 => 1,
Interrupt::I2 => 2,
Interrupt::I3 => 3,
Interrupt::I4 => 4,
Interrupt::I5 => 5,
Interrupt::I6 => 6,
Interrupt::I7 => 7,
}
}
}

205
src/cyccnt.rs Normal file
View file

@ -0,0 +1,205 @@
//! Data Watchpoint Trace (DWT) unit's CYCle CouNTer
use core::{
cmp::Ordering,
convert::{Infallible, TryInto},
fmt,
marker::PhantomData,
ops,
};
use cortex_m::peripheral::DWT;
/// A measurement of the CYCCNT. Opaque and useful only with `Duration`
///
/// This data type is only available on ARMv7-M
#[derive(Clone, Copy, Eq, PartialEq)]
pub struct Instant {
inner: i32,
_not_send_or_sync: PhantomData<*mut ()>,
}
unsafe impl Sync for Instant {}
#[cfg(not(feature = "heterogeneous"))]
unsafe impl Send for Instant {}
impl Instant {
/// Returns an instant corresponding to "now"
pub fn now() -> Self {
Instant {
inner: DWT::get_cycle_count() as i32,
_not_send_or_sync: PhantomData,
}
}
/// Returns the amount of time elapsed since this instant was created.
pub fn elapsed(&self) -> Duration {
Instant::now() - *self
}
/// Returns the amount of time elapsed from another instant to this one.
pub fn duration_since(&self, earlier: Instant) -> Duration {
let diff = self.inner - earlier.inner;
assert!(diff >= 0, "second instant is later than self");
Duration { inner: diff as u32 }
}
}
impl fmt::Debug for Instant {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_tuple("Instant")
.field(&(self.inner as u32))
.finish()
}
}
impl ops::AddAssign<Duration> for Instant {
fn add_assign(&mut self, dur: Duration) {
debug_assert!(dur.inner < (1 << 31));
self.inner = self.inner.wrapping_add(dur.inner as i32);
}
}
impl ops::Add<Duration> for Instant {
type Output = Self;
fn add(mut self, dur: Duration) -> Self {
self += dur;
self
}
}
impl ops::SubAssign<Duration> for Instant {
fn sub_assign(&mut self, dur: Duration) {
// XXX should this be a non-debug assertion?
debug_assert!(dur.inner < (1 << 31));
self.inner = self.inner.wrapping_sub(dur.inner as i32);
}
}
impl ops::Sub<Duration> for Instant {
type Output = Self;
fn sub(mut self, dur: Duration) -> Self {
self -= dur;
self
}
}
impl ops::Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, other: Instant) -> Duration {
self.duration_since(other)
}
}
impl Ord for Instant {
fn cmp(&self, rhs: &Self) -> Ordering {
self.inner.wrapping_sub(rhs.inner).cmp(&0)
}
}
impl PartialOrd for Instant {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
/// A `Duration` type to represent a span of time.
///
/// This data type is only available on ARMv7-M
#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
pub struct Duration {
inner: u32,
}
impl Duration {
/// Returns the total number of clock cycles contained by this `Duration`
pub fn as_cycles(&self) -> u32 {
self.inner
}
}
impl TryInto<u32> for Duration {
type Error = Infallible;
fn try_into(self) -> Result<u32, Infallible> {
Ok(self.as_cycles())
}
}
impl ops::AddAssign for Duration {
fn add_assign(&mut self, dur: Duration) {
self.inner += dur.inner;
}
}
impl ops::Add<Duration> for Duration {
type Output = Self;
fn add(self, other: Self) -> Self {
Duration {
inner: self.inner + other.inner,
}
}
}
impl ops::SubAssign for Duration {
fn sub_assign(&mut self, rhs: Duration) {
self.inner -= rhs.inner;
}
}
impl ops::Sub<Duration> for Duration {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Duration {
inner: self.inner - rhs.inner,
}
}
}
/// Adds the `cycles` method to the `u32` type
///
/// This trait is only available on ARMv7-M
pub trait U32Ext {
/// Converts the `u32` value into clock cycles
fn cycles(self) -> Duration;
}
impl U32Ext for u32 {
fn cycles(self) -> Duration {
Duration { inner: self }
}
}
/// Implementation of the `Monotonic` trait based on CYCle CouNTer
#[cfg(not(feature = "heterogeneous"))]
pub struct CYCCNT;
#[cfg(not(feature = "heterogeneous"))]
unsafe impl crate::Monotonic for CYCCNT {
type Instant = Instant;
fn ratio() -> u32 {
1
}
unsafe fn reset() {
(0xE0001004 as *mut u32).write_volatile(0)
}
fn now() -> Instant {
Instant::now()
}
fn zero() -> Instant {
Instant {
inner: 0,
_not_send_or_sync: PhantomData,
}
}
}

View file

@ -1,21 +1,27 @@
//! IMPLEMENTATION DETAILS. DO NOT USE ANYTHING IN THIS MODULE
use core::{cell::Cell, u8};
#[cfg(armv7m)]
use cortex_m::register::basepri;
pub use cortex_m::{
asm::wfi, interrupt, peripheral::scb::SystemHandler, peripheral::syst::SystClkSource,
peripheral::Peripherals,
use core::{
cell::Cell,
sync::atomic::{AtomicBool, Ordering},
};
use heapless::spsc::SingleCore;
pub use heapless::{consts, i, spsc::Queue};
#[cfg(feature = "timer-queue")]
pub use crate::tq::{NotReady, TimerQueue};
#[cfg(armv7m)]
pub use cortex_m::register::basepri;
pub use cortex_m::{
asm::wfi,
interrupt,
peripheral::{scb::SystemHandler, syst::SystClkSource, DWT},
Peripherals,
};
use heapless::spsc::{MultiCore, SingleCore};
pub use heapless::{consts, i::Queue as iQueue, spsc::Queue};
pub use heapless::{i::BinaryHeap as iBinaryHeap, BinaryHeap};
#[cfg(feature = "heterogeneous")]
pub use microamp::shared;
pub type FreeQueue<N> = Queue<u8, N, u8, SingleCore>;
pub type ReadyQueue<T, N> = Queue<(T, u8), N, u8, SingleCore>;
pub type MCFQ<N> = Queue<u8, N, u8, MultiCore>;
pub type MCRQ<T, N> = Queue<(T, u8), N, u8, MultiCore>;
pub type SCFQ<N> = Queue<u8, N, u8, SingleCore>;
pub type SCRQ<T, N> = Queue<(T, u8), N, u8, SingleCore>;
#[cfg(armv7m)]
#[inline(always)]
@ -43,6 +49,26 @@ where
f();
}
pub struct Barrier {
inner: AtomicBool,
}
impl Barrier {
pub const fn new() -> Self {
Barrier {
inner: AtomicBool::new(false),
}
}
pub fn release(&self) {
self.inner.store(true, Ordering::Release)
}
pub fn wait(&self) {
while !self.inner.load(Ordering::Acquire) {}
}
}
// Newtype over `Cell` that forbids mutation through a shared reference
pub struct Priority {
inner: Cell<u8>,
@ -95,7 +121,7 @@ pub unsafe fn lock<T, R>(
if current < ceiling {
if ceiling == (1 << nvic_prio_bits) {
priority.set(u8::MAX);
priority.set(u8::max_value());
let r = interrupt::free(|_| f(&mut *ptr));
priority.set(current);
r
@ -124,7 +150,7 @@ pub unsafe fn lock<T, R>(
let current = priority.get();
if current < ceiling {
priority.set(u8::MAX);
priority.set(u8::max_value());
let r = interrupt::free(|_| f(&mut *ptr));
priority.set(current);
r

View file

@ -33,68 +33,45 @@
//!
//! # Cargo features
//!
//! - `timer-queue`. This opt-in feature enables the `schedule` API which can be used to schedule
//! tasks to run in the future. Also see [`Instant`] and [`Duration`].
//!
//! [`Instant`]: struct.Instant.html
//! [`Duration`]: struct.Duration.html
//!
//! - `nightly`. Enabling this opt-in feature makes RTFM internally use the unstable `const_fn`
//! language feature to reduce static memory usage, runtime overhead and initialization overhead.
//! This feature requires a nightly compiler and may stop working at any time!
//! - `heterogeneous`. This opt-in feature enables the *experimental* heterogeneous multi-core support.
#![deny(missing_docs)]
#![deny(rust_2018_compatibility)]
#![deny(rust_2018_idioms)]
#![deny(warnings)]
#![no_std]
#[cfg(feature = "timer-queue")]
use core::cmp::Ordering;
use core::{fmt, ops};
use core::ops::Sub;
#[cfg(not(feature = "timer-queue"))]
use cortex_m::peripheral::SYST;
use cortex_m::{
interrupt::Nr,
peripheral::{CBP, CPUID, DCB, DWT, FPB, FPU, ITM, MPU, NVIC, SCB, TPIU},
};
#[cfg(not(feature = "heterogeneous"))]
use cortex_m_rt as _; // vector table
pub use cortex_m_rtfm_macros::app;
pub use rtfm_core::{Exclusive, Mutex};
#[cfg(armv7m)]
pub mod cyccnt;
#[doc(hidden)]
pub mod export;
#[doc(hidden)]
#[cfg(feature = "timer-queue")]
mod tq;
#[cfg(all(feature = "timer-queue", armv6m))]
compile_error!(
"The `timer-queue` feature is currently not supported on ARMv6-M (`thumbv6m-none-eabi`)"
);
/// Core peripherals
///
/// This is `cortex_m::Peripherals` minus the peripherals that the RTFM runtime uses
///
/// - The `NVIC` field is never present.
/// - When the `timer-queue` feature is enabled the following fields are *not* present: `DWT` and
/// `SYST`.
/// `cortex_m::Peripherals` minus `SYST`
#[allow(non_snake_case)]
pub struct Peripherals<'a> {
pub struct Peripherals {
/// Cache and branch predictor maintenance operations (not present on Cortex-M0 variants)
pub CBP: CBP,
/// CPUID
pub CPUID: CPUID,
/// Debug Control Block (by value if the `timer-queue` feature is disabled)
#[cfg(feature = "timer-queue")]
pub DCB: &'a mut DCB,
/// Debug Control Block (borrowed if the `timer-queue` feature is enabled)
#[cfg(not(feature = "timer-queue"))]
/// Debug Control Block
pub DCB: DCB,
/// Data Watchpoint and Trace unit (not present if the `timer-queue` feature is enabled)
#[cfg(not(feature = "timer-queue"))]
/// Data Watchpoint and Trace unit
pub DWT: DWT,
/// Flash Patch and Breakpoint unit (not present on Cortex-M0 variants)
@ -109,245 +86,52 @@ pub struct Peripherals<'a> {
/// Memory Protection Unit
pub MPU: MPU,
// Nested Vector Interrupt Controller
// pub NVIC: NVIC,
/// Nested Vector Interrupt Controller
pub NVIC: NVIC,
/// System Control Block
pub SCB: &'a mut SCB,
/// SysTick: System Timer (not present if the `timer-queue` is enabled)
#[cfg(not(feature = "timer-queue"))]
pub SYST: SYST,
pub SCB: SCB,
// SysTick: System Timer
// pub SYST: SYST,
/// Trace Port Interface Unit (not present on Cortex-M0 variants)
pub TPIU: TPIU,
}
/// A measurement of a monotonically nondecreasing clock. Opaque and useful only with `Duration`
///
/// This data type is only available when the `timer-queue` feature is enabled
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[cfg(feature = "timer-queue")]
pub struct Instant(i32);
#[cfg(feature = "timer-queue")]
impl Instant {
/// IMPLEMENTATION DETAIL. DO NOT USE
#[doc(hidden)]
pub unsafe fn artificial(timestamp: i32) -> Self {
Instant(timestamp)
}
/// Returns an instant corresponding to "now"
pub fn now() -> Self {
Instant(DWT::get_cycle_count() as i32)
}
/// Returns the amount of time elapsed since this instant was created.
pub fn elapsed(&self) -> Duration {
Instant::now() - *self
}
/// Returns the amount of time elapsed from another instant to this one.
pub fn duration_since(&self, earlier: Instant) -> Duration {
let diff = self.0 - earlier.0;
assert!(diff >= 0, "second instant is later than self");
Duration(diff as u32)
impl From<cortex_m::Peripherals> for Peripherals {
fn from(p: cortex_m::Peripherals) -> Self {
Self {
CBP: p.CBP,
CPUID: p.CPUID,
DCB: p.DCB,
DWT: p.DWT,
FPB: p.FPB,
FPU: p.FPU,
ITM: p.ITM,
MPU: p.MPU,
NVIC: p.NVIC,
SCB: p.SCB,
TPIU: p.TPIU,
}
}
}
#[cfg(feature = "timer-queue")]
impl ops::AddAssign<Duration> for Instant {
fn add_assign(&mut self, dur: Duration) {
debug_assert!(dur.0 < (1 << 31));
self.0 = self.0.wrapping_add(dur.0 as i32);
}
}
/// A monotonic clock / counter
pub unsafe trait Monotonic {
/// A measurement of this clock
type Instant: Copy + Ord + Sub;
#[cfg(feature = "timer-queue")]
impl ops::Add<Duration> for Instant {
type Output = Self;
/// The ratio between the SysTick (system timer) frequency and this clock frequency
fn ratio() -> u32;
fn add(mut self, dur: Duration) -> Self {
self += dur;
self
}
}
/// Returns the current time
fn now() -> Self::Instant;
#[cfg(feature = "timer-queue")]
impl ops::SubAssign<Duration> for Instant {
fn sub_assign(&mut self, dur: Duration) {
// XXX should this be a non-debug assertion?
debug_assert!(dur.0 < (1 << 31));
self.0 = self.0.wrapping_sub(dur.0 as i32);
}
}
/// Resets the counter to *zero*
unsafe fn reset();
#[cfg(feature = "timer-queue")]
impl ops::Sub<Duration> for Instant {
type Output = Self;
fn sub(mut self, dur: Duration) -> Self {
self -= dur;
self
}
}
#[cfg(feature = "timer-queue")]
impl ops::Sub<Instant> for Instant {
type Output = Duration;
fn sub(self, other: Instant) -> Duration {
self.duration_since(other)
}
}
#[cfg(feature = "timer-queue")]
impl Ord for Instant {
fn cmp(&self, rhs: &Self) -> Ordering {
self.0.wrapping_sub(rhs.0).cmp(&0)
}
}
#[cfg(feature = "timer-queue")]
impl PartialOrd for Instant {
fn partial_cmp(&self, rhs: &Self) -> Option<Ordering> {
Some(self.cmp(rhs))
}
}
/// A `Duration` type to represent a span of time.
///
/// This data type is only available when the `timer-queue` feature is enabled
#[derive(Clone, Copy, Default, Eq, Ord, PartialEq, PartialOrd)]
#[cfg(feature = "timer-queue")]
pub struct Duration(u32);
#[cfg(feature = "timer-queue")]
impl Duration {
/// Returns the total number of clock cycles contained by this `Duration`
pub fn as_cycles(&self) -> u32 {
self.0
}
}
#[cfg(feature = "timer-queue")]
impl ops::AddAssign for Duration {
fn add_assign(&mut self, dur: Duration) {
self.0 += dur.0;
}
}
#[cfg(feature = "timer-queue")]
impl ops::Add<Duration> for Duration {
type Output = Self;
fn add(self, other: Self) -> Self {
Duration(self.0 + other.0)
}
}
#[cfg(feature = "timer-queue")]
impl ops::SubAssign for Duration {
fn sub_assign(&mut self, rhs: Duration) {
self.0 -= rhs.0;
}
}
#[cfg(feature = "timer-queue")]
impl ops::Sub<Duration> for Duration {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Duration(self.0 - rhs.0)
}
}
/// Adds the `cycles` method to the `u32` type
///
/// This trait is only available when the `timer-queue` feature is enabled
#[cfg(feature = "timer-queue")]
pub trait U32Ext {
/// Converts the `u32` value into clock cycles
fn cycles(self) -> Duration;
}
#[cfg(feature = "timer-queue")]
impl U32Ext for u32 {
fn cycles(self) -> Duration {
Duration(self)
}
}
/// Memory safe access to shared resources
///
/// In RTFM, locks are implemented as critical sections that prevent other tasks from *starting*.
/// These critical sections are implemented by temporarily increasing the dynamic priority (see
/// [BASEPRI]) of the current context. Entering and leaving these critical sections is always done
/// in constant time (a few instructions).
///
/// [BASEPRI]: https://developer.arm.com/products/architecture/cpu-architecture/m-profile/docs/100701/latest/special-purpose-mask-registers
pub trait Mutex {
/// Data protected by the mutex
type T;
/// Creates a critical section and grants temporary access to the protected data
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::T) -> R) -> R;
}
impl<'a, M> Mutex for &'a mut M
where
M: Mutex,
{
type T = M::T;
fn lock<R>(&mut self, f: impl FnOnce(&mut M::T) -> R) -> R {
(**self).lock(f)
}
}
/// Newtype over `&'a mut T` that implements the `Mutex` trait
///
/// The `Mutex` implementation for this type is a no-op, no critical section is created
pub struct Exclusive<'a, T>(pub &'a mut T);
impl<'a, T> Mutex for Exclusive<'a, T> {
type T = T;
fn lock<R>(&mut self, f: impl FnOnce(&mut T) -> R) -> R {
f(self.0)
}
}
impl<'a, T> fmt::Debug for Exclusive<'a, T>
where
T: fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<'a, T> fmt::Display for Exclusive<'a, T>
where
T: fmt::Display,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<'a, T> ops::Deref for Exclusive<'a, T> {
type Target = T;
fn deref(&self) -> &T {
self.0
}
}
impl<'a, T> ops::DerefMut for Exclusive<'a, T> {
fn deref_mut(&mut self) -> &mut T {
self.0
}
/// A `Self::Instant` that represents a count of *zero*
fn zero() -> Self::Instant;
}
/// Sets the given `interrupt` as pending

119
src/tq.rs
View file

@ -1,36 +1,34 @@
use core::cmp::{self, Ordering};
use core::{
cmp::{self, Ordering},
convert::TryInto,
mem,
ops::Sub,
};
use cortex_m::peripheral::{SCB, SYST};
use heapless::{binary_heap::Min, ArrayLength, BinaryHeap};
use crate::Instant;
use crate::Monotonic;
pub struct TimerQueue<T, N>
pub struct TimerQueue<M, T, N>(pub BinaryHeap<NotReady<M, T>, N, Min>)
where
N: ArrayLength<NotReady<T>>,
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
N: ArrayLength<NotReady<M, T>>,
T: Copy;
impl<M, T, N> TimerQueue<M, T, N>
where
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
N: ArrayLength<NotReady<M, T>>,
T: Copy,
{
pub syst: SYST,
pub queue: BinaryHeap<NotReady<T>, N, Min>,
}
impl<T, N> TimerQueue<T, N>
where
N: ArrayLength<NotReady<T>>,
T: Copy,
{
pub fn new(syst: SYST) -> Self {
TimerQueue {
syst,
queue: BinaryHeap::new(),
}
}
#[inline]
pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<T>) {
pub unsafe fn enqueue_unchecked(&mut self, nr: NotReady<M, T>) {
let mut is_empty = true;
if self
.queue
.0
.peek()
.map(|head| {
is_empty = false;
@ -39,77 +37,102 @@ where
.unwrap_or(true)
{
if is_empty {
self.syst.enable_interrupt();
mem::transmute::<_, SYST>(()).enable_interrupt();
}
// set SysTick pending
SCB::set_pendst();
}
self.queue.push_unchecked(nr);
self.0.push_unchecked(nr);
}
#[inline]
pub fn dequeue(&mut self) -> Option<(T, u8)> {
if let Some(instant) = self.queue.peek().map(|p| p.instant) {
let diff = instant.0.wrapping_sub(Instant::now().0);
unsafe {
if let Some(instant) = self.0.peek().map(|p| p.instant) {
let now = M::now();
if diff < 0 {
// task became ready
let nr = unsafe { self.queue.pop_unchecked() };
if instant < now {
// task became ready
let nr = self.0.pop_unchecked();
Some((nr.task, nr.index))
Some((nr.task, nr.index))
} else {
// set a new timeout
const MAX: u32 = 0x00ffffff;
let dur = match (instant - now)
.try_into()
.ok()
.and_then(|x| x.checked_mul(M::ratio()))
{
None => MAX,
Some(x) => cmp::min(MAX, x),
};
mem::transmute::<_, SYST>(()).set_reload(dur);
// start counting down from the new reload
mem::transmute::<_, SYST>(()).clear_current();
None
}
} else {
// set a new timeout
const MAX: u32 = 0x00ffffff;
self.syst.set_reload(cmp::min(MAX, diff as u32));
// start counting down from the new reload
self.syst.clear_current();
// the queue is empty
mem::transmute::<_, SYST>(()).disable_interrupt();
None
}
} else {
// the queue is empty
self.syst.disable_interrupt();
None
}
}
}
pub struct NotReady<T>
pub struct NotReady<M, T>
where
T: Copy,
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
{
pub index: u8,
pub instant: Instant,
pub instant: M::Instant,
pub task: T,
}
impl<T> Eq for NotReady<T> where T: Copy {}
impl<T> Ord for NotReady<T>
impl<M, T> Eq for NotReady<M, T>
where
T: Copy,
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
{
}
impl<M, T> Ord for NotReady<M, T>
where
T: Copy,
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
{
fn cmp(&self, other: &Self) -> Ordering {
self.instant.cmp(&other.instant)
}
}
impl<T> PartialEq for NotReady<T>
impl<M, T> PartialEq for NotReady<M, T>
where
T: Copy,
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
{
fn eq(&self, other: &Self) -> bool {
self.instant == other.instant
}
}
impl<T> PartialOrd for NotReady<T>
impl<M, T> PartialOrd for NotReady<M, T>
where
T: Copy,
M: Monotonic,
<M::Instant as Sub>::Output: TryInto<u32>,
{
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(&other))

View file

@ -1,24 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[task(
priority = 1,
priority = 2, //~ ERROR argument appears more than once
)]
fn foo(_: foo::Context) {}
extern "C" {
fn UART0();
}
};

View file

@ -1,24 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[task(
capacity = 1,
capacity = 2, //~ ERROR argument appears more than once
)]
fn foo(_: foo::Context) {}
extern "C" {
fn UART0();
}
};

View file

@ -1,20 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[exception]
fn SVCall(_: SVCall::Context) -> ! {
//~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)`
loop {}
}
};

View file

@ -1,19 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[exception]
fn SVCall(_: SVCall::Context, undef: u32) {
//~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)`
}
};

View file

@ -1,19 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[exception]
fn NonMaskableInt(_: NonMaskableInt::Context) {
//~^ ERROR only exceptions with configurable priority can be used as hardware tasks
}
};

View file

@ -1,20 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[exception]
fn SVCall(_: SVCall::Context) -> u32 {
//~^ ERROR this `exception` handler must have type signature `fn(SVCall::Context)`
0
}
};

View file

@ -1,19 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[exception]
fn SysTick(_: SysTick::Context) {
//~^ ERROR the `SysTick` exception can't be used because it's used by the runtime
}
};

View file

@ -1,19 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[idle]
fn idle(_: idle::Context, undef: u32) {
//~^ ERROR `idle` must have type signature `fn(idle::Context) -> !`
}
};

View file

@ -1,19 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[idle]
fn idle(_: idle::Context) {
//~^ ERROR `idle` must have type signature `fn(idle::Context) -> !`
}
};

View file

@ -1,17 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) -> ! {
//~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]`
loop {}
}
};

View file

@ -1,15 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) -> init::LateResources {}
//~^ error: `init` signature must be `fn(init::Context)` if there are no late resources
};

View file

@ -1,16 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context, undef: u32) {
//~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]`
}
};

View file

@ -1,17 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
static mut X: i32 = ();
#[init]
fn init(_: init::Context) {}
//~^ error: late resources have been specified so `init` must return `init::LateResources`
};

View file

@ -1,29 +0,0 @@
//! This is equivalent to the `late-not-send` cfail test
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use core::marker::PhantomData;
use rtfm::app;
pub struct NotSend {
_0: PhantomData<*const ()>,
}
#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely
const APP: () = {
static mut X: Option<NotSend> = None;
#[init(resources = [X])]
fn init(c: init::Context) {
*c.resources.X = Some(NotSend { _0: PhantomData })
}
#[interrupt(resources = [X])]
fn UART0(_: UART0::Context) {}
};

View file

@ -1,17 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) -> u32 {
//~^ ERROR `init` must have type signature `fn(init::Context) [-> init::LateResources]`
0
}
};

View file

@ -1,17 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)] //~ ERROR 1 free interrupt (`extern { .. }`) is required
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[task]
fn foo(_: foo::Context) {}
};

View file

@ -1,20 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[interrupt]
fn UART0(_: UART0::Context) -> ! {
//~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)`
loop {}
}
};

View file

@ -1,19 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[interrupt]
fn UART0(_: UART0::Context, undef: u32) {
//~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)`
}
};

View file

@ -1,20 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[interrupt]
fn UART0(_: UART0::Context) -> u32 {
//~^ ERROR this `interrupt` handler must have type signature `fn(UART0::Context)`
0
}
};

View file

@ -1,16 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
static mut X: u32 = ();
#[init(resources = [X])] //~ ERROR late resources can NOT be assigned to `init`
fn init(_: init::Context) {}
};

View file

@ -1,32 +0,0 @@
//! `init` has a static priority of `0`. Initializing resources from it is equivalent to sending a
//! message to the task that will own the resource
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use core::marker::PhantomData;
use rtfm::app;
struct NotSend {
_0: PhantomData<*const ()>,
}
#[app(device = lm3s6965)] //~ ERROR `*const ()` cannot be sent between threads safely
const APP: () = {
static mut X: NotSend = ();
#[init]
fn init(_: init::Context) -> init::LateResources {
init::LateResources {
X: NotSend { _0: PhantomData },
}
}
#[interrupt(resources = [X])]
fn UART0(_: UART0::Context) {}
};

View file

@ -1,29 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use core::marker::PhantomData;
use rtfm::app;
pub struct NotSend {
_0: PhantomData<*const ()>,
}
unsafe impl Sync for NotSend {}
#[app(device = lm3s6965)] //~ ERROR cannot be sent between threads safely
const APP: () = {
#[init(spawn = [foo])]
fn init(_: init::Context) {}
#[task]
fn foo(_: foo::Context, _x: NotSend) {}
extern "C" {
fn UART0();
}
};

View file

@ -1,35 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use core::marker::PhantomData;
use rtfm::app;
pub struct NotSync {
_0: PhantomData<*const ()>,
}
unsafe impl Send for NotSync {}
#[app(device = lm3s6965)] //~ ERROR cannot be shared between threads safely
const APP: () = {
static X: NotSync = NotSync { _0: PhantomData };
#[init(spawn = [foo])]
fn init(_: init::Context) {}
#[task(priority = 1, resources = [X])]
fn foo(_: foo::Context) {}
#[task(priority = 2, resources = [X])]
fn bar(_: bar::Context) {}
extern "C" {
fn UART0();
fn UART1();
}
};

View file

@ -1,22 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)] //~ error evaluation of constant value failed
const APP: () = {
#[init]
fn init(_: init::Context) {}
// OK, this is the maximum priority supported by the device
#[interrupt(priority = 8)]
fn UART0(_: UART0::Context) {}
// this value is too high!
#[interrupt(priority = 9)]
fn UART1(_: UART1::Context) {}
};

View file

@ -1,22 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
// OK, this is the minimum priority that tasks can have
#[interrupt(priority = 1)]
fn UART0(_: UART0::Context) {}
// this value is too low!
#[interrupt(priority = 0)] //~ error this literal must be in the range 1...255
fn UART1(_: UART1::Context) {}
};

View file

@ -1,14 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init(resources = [X])] //~ ERROR this resource has NOT been declared
fn init(_: init::Context) {}
};

View file

@ -1,17 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
pub static mut X: u32 = 0;
//~^ ERROR resources must have inherited / private visibility
#[init]
fn init(_: init::Context) {}
};

View file

@ -1,22 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
#[rtfm::app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[task]
fn foo(_: foo::Context) -> ! {
//~^ ERROR this `task` handler must have type signature `fn(foo::Context, ..)`
loop {}
}
extern "C" {
fn UART0();
}
};

View file

@ -1,23 +0,0 @@
#![no_main]
#![no_std]
extern crate lm3s6965;
extern crate panic_halt;
extern crate rtfm;
use rtfm::app;
#[app(device = lm3s6965)]
const APP: () = {
#[init]
fn init(_: init::Context) {}
#[task]
fn idle(_: idle::Context) {
//~^ ERROR `task` handlers can NOT be named `idle`, `init` or `resources`
}
extern "C" {
fn UART0();
}
};

Some files were not shown because too many files have changed in this diff Show more