mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-12-24 19:09:33 +01:00
rtfm-syntax refactor + heterogeneous multi-core support
This commit is contained in:
parent
fafeeb2727
commit
81275bfa4f
127 changed files with 4072 additions and 5848 deletions
58
Cargo.toml
58
Cargo.toml
|
@ -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",
|
||||
]
|
||||
|
|
|
@ -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
|
||||
|
|
93
ci/script.sh
93
ci/script.sh
|
@ -37,23 +37,30 @@ 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
|
||||
|
||||
cargo check --target $T
|
||||
if [ $TARGET != thumbv6m-none-eabi ]; then
|
||||
cargo check --features "timer-queue" --target $T
|
||||
fi
|
||||
popd
|
||||
|
||||
else
|
||||
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 )
|
||||
|
||||
|
@ -70,13 +77,18 @@ main() {
|
|||
linkchecker $td/api/rtfm/
|
||||
linkchecker $td/api/cortex_m_rtfm_macros/
|
||||
fi
|
||||
fi
|
||||
|
||||
cargo check --target $T
|
||||
( cd macros && cargo test --target $T )
|
||||
|
||||
return
|
||||
fi
|
||||
|
||||
if [ $TARGET = thumbv6m-none-eabi ]; then
|
||||
cargo check --target $T --examples
|
||||
if [ $TARGET != thumbv6m-none-eabi ]; then
|
||||
cargo check --features "timer-queue" --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
|
||||
if [ $ex = pool ]; then
|
||||
if [ $TARGET = thumbv6m-none-eabi ]; then
|
||||
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 >\
|
||||
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}_${features/,/_}_debug_1.hex
|
||||
ci/builds/${ex}___v7_debug_1.hex
|
||||
|
||||
cargo run --example $ex --target $TARGET --features $features --release >\
|
||||
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}_${features/,/_}_release_1.hex
|
||||
ci/builds/${ex}___v7_release_1.hex
|
||||
|
||||
rm -rf $td
|
||||
fi
|
||||
|
||||
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
|
||||
|
||||
if [ $TARGET != thumbv6m-none-eabi ]; then
|
||||
arm_example "run" $ex "debug" "timer-queue" "1"
|
||||
arm_example "run" $ex "release" "timer-queue" "1"
|
||||
arm_example "run" $ex "debug" "__v7" "1"
|
||||
arm_example "run" $ex "release" "__v7" "1"
|
||||
|
||||
continue
|
||||
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
|
||||
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
|
||||
fi
|
||||
|
||||
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
|
||||
done
|
||||
|
||||
( cd target/$TARGET/release/examples/ && size ${built[@]} )
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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: () = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
@ -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: () = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: () = {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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: () = {
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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 ()>,
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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: () = {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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: () = {};
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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: () = {
|
|
@ -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;
|
|
@ -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 {
|
|
@ -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;
|
||||
}
|
||||
};
|
|
@ -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) {
|
|
@ -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: () = {
|
|
@ -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: () = {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 = []
|
||||
|
|
|
@ -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
|
||||
}
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.parent
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_owned(&self) -> bool {
|
||||
match *self {
|
||||
Ownership::Owned { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
Ownership::Owned { priority: ceiling } if ceiling == priority => {
|
||||
*ownership = Ownership::CoOwned { priority };
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
// 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());
|
||||
}
|
||||
// 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 {
|
||||
// spawns from `init` are excluded from the ceiling analysis
|
||||
None
|
||||
}
|
||||
})
|
||||
.chain(analysis.timer_queues.get(&core).map(|tq| tq.priority))
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
if !priorities.is_empty() {
|
||||
interrupts.insert(
|
||||
core,
|
||||
priorities
|
||||
.iter()
|
||||
.cloned()
|
||||
.rev()
|
||||
.zip(app.extern_interrupts[&core].keys().cloned())
|
||||
.collect(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
})
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
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(
|
||||
res.span(),
|
||||
"this resource has NOT been declared",
|
||||
name.span(),
|
||||
"this exception can't be used because it's being used by the runtime",
|
||||
));
|
||||
} else {
|
||||
// OK
|
||||
}
|
||||
}
|
||||
|
||||
// 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`",
|
||||
name.span(),
|
||||
"only exceptions with configurable priority can be used as hardware tasks",
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
// 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())
|
||||
{
|
||||
let name_s = name.to_string();
|
||||
|
||||
match &*name_s {
|
||||
"NonMaskableInt" | "HardFault" | "MemoryManagement" | "BusFault" | "UsageFault"
|
||||
| "SecureFault" | "SVCall" | "DebugMonitor" | "PendSV" | "SysTick" => {
|
||||
return Err(parse::Error::new(
|
||||
app.init.span,
|
||||
"late resources have been specified so `init` must return `init::LateResources`",
|
||||
name.span(),
|
||||
"Cortex-M exceptions can't be used as `extern` interrupts",
|
||||
));
|
||||
}
|
||||
} 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))
|
||||
// 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
|
||||
}
|
||||
})
|
||||
.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(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
|
||||
)
|
||||
.chain(
|
||||
app.interrupts
|
||||
.values()
|
||||
.flat_map(|i| i.args.schedule.iter().chain(&i.args.spawn)),
|
||||
} else {
|
||||
format!(
|
||||
"not enough `extern` interrupts to dispatch \
|
||||
all software tasks on this core (need: {}; given: {})",
|
||||
need, given
|
||||
)
|
||||
.chain(
|
||||
app.tasks
|
||||
.values()
|
||||
.flat_map(|t| t.args.schedule.iter().chain(&t.args.spawn)),
|
||||
)
|
||||
{
|
||||
if !app.tasks.contains_key(task) {
|
||||
return Err(parse::Error::new(
|
||||
task.span(),
|
||||
"this task has NOT been declared",
|
||||
));
|
||||
};
|
||||
|
||||
return Err(parse::Error::new(first.unwrap().span(), &s));
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
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(
|
||||
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" },
|
||||
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
|
||||
),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// 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",
|
||||
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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
if !analysis.timer_queues.is_empty() && monotonic.is_none() {
|
||||
return Err(parse::Error::new(
|
||||
Span::call_site(),
|
||||
"a `monotonic` timer must be specified to use the `schedule` API",
|
||||
));
|
||||
}
|
||||
|
||||
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]`",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
|
File diff suppressed because it is too large
Load diff
26
macros/src/codegen/assertions.rs
Normal file
26
macros/src/codegen/assertions.rs
Normal 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
|
||||
}
|
178
macros/src/codegen/dispatchers.rs
Normal file
178
macros/src/codegen/dispatchers.rs
Normal 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
|
||||
}
|
121
macros/src/codegen/hardware_tasks.rs
Normal file
121
macros/src/codegen/hardware_tasks.rs
Normal 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)
|
||||
}
|
92
macros/src/codegen/idle.rs
Normal file
92
macros/src/codegen/idle.rs
Normal 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
112
macros/src/codegen/init.rs
Normal 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)
|
||||
}
|
||||
}
|
94
macros/src/codegen/locals.rs
Normal file
94
macros/src/codegen/locals.rs
Normal 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),
|
||||
)
|
||||
}
|
328
macros/src/codegen/module.rs
Normal file
328
macros/src/codegen/module.rs
Normal 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!()
|
||||
}
|
||||
}
|
139
macros/src/codegen/post_init.rs
Normal file
139
macros/src/codegen/post_init.rs
Normal 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)
|
||||
}
|
150
macros/src/codegen/pre_init.rs
Normal file
150
macros/src/codegen/pre_init.rs
Normal 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)
|
||||
}
|
115
macros/src/codegen/resources.rs
Normal file
115
macros/src/codegen/resources.rs
Normal 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)
|
||||
}
|
178
macros/src/codegen/resources_struct.rs
Normal file
178
macros/src/codegen/resources_struct.rs
Normal 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)
|
||||
}
|
95
macros/src/codegen/schedule.rs
Normal file
95
macros/src/codegen/schedule.rs
Normal 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
|
||||
}
|
61
macros/src/codegen/schedule_body.rs
Normal file
61
macros/src/codegen/schedule_body.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
169
macros/src/codegen/software_tasks.rs
Normal file
169
macros/src/codegen/software_tasks.rs
Normal 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
127
macros/src/codegen/spawn.rs
Normal 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
|
||||
}
|
81
macros/src/codegen/spawn_body.rs
Normal file
81
macros/src/codegen/spawn_body.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
147
macros/src/codegen/timer_queue.rs
Normal file
147
macros/src/codegen/timer_queue.rs
Normal 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
253
macros/src/codegen/util.rs
Normal 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())
|
||||
}
|
|
@ -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() {
|
||||
|
|
1382
macros/src/syntax.rs
1382
macros/src/syntax.rs
File diff suppressed because it is too large
Load diff
5
macros/src/tests.rs
Normal file
5
macros/src/tests.rs
Normal 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
59
macros/src/tests/multi.rs
Normal 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");
|
||||
}
|
35
macros/src/tests/single.rs
Normal file
35
macros/src/tests/single.rs
Normal 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
18
mc/Cargo.toml
Normal 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
1
mc/README.md
Normal file
|
@ -0,0 +1 @@
|
|||
This directory contains multi-core compile pass tests.
|
7
mc/examples/smallest.rs
Normal file
7
mc/examples/smallest.rs
Normal 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
39
mc/examples/x-init-2.rs
Normal 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
26
mc/examples/x-init.rs
Normal 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
36
mc/examples/x-schedule.rs
Normal 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
20
mc/examples/x-spawn.rs
Normal 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
99
mc/src/lib.rs
Normal 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
205
src/cyccnt.rs
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
306
src/lib.rs
306
src/lib.rs
|
@ -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)
|
||||
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,
|
||||
}
|
||||
|
||||
/// 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)
|
||||
}
|
||||
}
|
||||
|
||||
#[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
|
||||
|
|
99
src/tq.rs
99
src/tq.rs
|
@ -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 {
|
||||
if instant < now {
|
||||
// task became ready
|
||||
let nr = unsafe { self.queue.pop_unchecked() };
|
||||
let nr = self.0.pop_unchecked();
|
||||
|
||||
Some((nr.task, nr.index))
|
||||
} else {
|
||||
// set a new timeout
|
||||
const MAX: u32 = 0x00ffffff;
|
||||
|
||||
self.syst.set_reload(cmp::min(MAX, diff as u32));
|
||||
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
|
||||
self.syst.clear_current();
|
||||
mem::transmute::<_, SYST>(()).clear_current();
|
||||
|
||||
None
|
||||
}
|
||||
} else {
|
||||
// the queue is empty
|
||||
self.syst.disable_interrupt();
|
||||
mem::transmute::<_, 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))
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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 {}
|
||||
}
|
||||
};
|
|
@ -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)`
|
||||
}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
};
|
|
@ -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) -> !`
|
||||
}
|
||||
};
|
|
@ -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) -> !`
|
||||
}
|
||||
};
|
|
@ -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 {}
|
||||
}
|
||||
};
|
|
@ -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
|
||||
};
|
|
@ -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]`
|
||||
}
|
||||
};
|
|
@ -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`
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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 {}
|
||||
}
|
||||
};
|
|
@ -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)`
|
||||
}
|
||||
};
|
|
@ -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
|
||||
}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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) {}
|
||||
};
|
|
@ -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();
|
||||
}
|
||||
};
|
|
@ -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
Loading…
Reference in a new issue