mirror of
https://github.com/rtic-rs/rtic.git
synced 2024-11-26 13:39:41 +01:00
1816 lines
52 KiB
Rust
1816 lines
52 KiB
Rust
|
use proc_macro::TokenStream;
|
||
|
use std::{
|
||
|
collections::HashMap,
|
||
|
sync::atomic::{AtomicUsize, Ordering},
|
||
|
time::{SystemTime, UNIX_EPOCH},
|
||
|
};
|
||
|
|
||
|
use proc_macro2::Span;
|
||
|
use quote::quote;
|
||
|
use rand::{Rng, SeedableRng};
|
||
|
use syn::{ArgCaptured, Ident, IntSuffix, LitInt};
|
||
|
|
||
|
use analyze::{Analysis, Ownership};
|
||
|
use syntax::{App, Idents, Static};
|
||
|
|
||
|
// NOTE to avoid polluting the user namespaces we map some identifiers to pseudo-hygienic names.
|
||
|
// In some instances we also use the pseudo-hygienic names for safety, for example the user should
|
||
|
// not modify the priority field of resources.
|
||
|
type Aliases = HashMap<Ident, Ident>;
|
||
|
|
||
|
struct Context {
|
||
|
// Alias
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
baseline: Ident,
|
||
|
// Dispatcher -> Alias (`enum`)
|
||
|
enums: HashMap<u8, Ident>,
|
||
|
// Task -> Alias (`static` / resource)
|
||
|
free_queues: Aliases,
|
||
|
// Alias (`fn`)
|
||
|
idle: Ident,
|
||
|
// Alias (`fn`)
|
||
|
init: Ident,
|
||
|
// Task -> Alias (`static`)
|
||
|
inputs: Aliases,
|
||
|
// Alias
|
||
|
priority: Ident,
|
||
|
// Dispatcher -> Alias (`static` / resource)
|
||
|
ready_queues: HashMap<u8, Ident>,
|
||
|
// For non-singletons this maps the resource name to its `static mut` variable name
|
||
|
statics: Aliases,
|
||
|
/// Task -> Alias (`struct`)
|
||
|
resources: HashMap<Kind, Resources>,
|
||
|
// Task -> Alias (`static`)
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
scheduleds: Aliases,
|
||
|
// Task -> Alias (`fn`)
|
||
|
spawn_fn: Aliases,
|
||
|
// Alias (`enum`)
|
||
|
schedule_enum: Ident,
|
||
|
// Task -> Alias (`fn`)
|
||
|
schedule_fn: Aliases,
|
||
|
tasks: Aliases,
|
||
|
// Alias (`struct` / `static mut`)
|
||
|
timer_queue: Ident,
|
||
|
}
|
||
|
|
||
|
impl Default for Context {
|
||
|
fn default() -> Self {
|
||
|
Context {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
baseline: mk_ident(),
|
||
|
enums: HashMap::new(),
|
||
|
free_queues: Aliases::new(),
|
||
|
idle: mk_ident(),
|
||
|
init: mk_ident(),
|
||
|
inputs: Aliases::new(),
|
||
|
priority: mk_ident(),
|
||
|
ready_queues: HashMap::new(),
|
||
|
statics: Aliases::new(),
|
||
|
resources: HashMap::new(),
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
scheduleds: Aliases::new(),
|
||
|
spawn_fn: Aliases::new(),
|
||
|
schedule_enum: mk_ident(),
|
||
|
schedule_fn: Aliases::new(),
|
||
|
tasks: Aliases::new(),
|
||
|
timer_queue: mk_ident(),
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
struct Resources {
|
||
|
alias: Ident,
|
||
|
decl: proc_macro2::TokenStream,
|
||
|
}
|
||
|
|
||
|
pub fn app(app: &App, analysis: &Analysis) -> TokenStream {
|
||
|
let mut ctxt = Context::default();
|
||
|
|
||
|
let device = &app.args.device;
|
||
|
|
||
|
let resources = resources(&mut ctxt, &app, analysis);
|
||
|
|
||
|
let tasks = tasks(&mut ctxt, &app, analysis);
|
||
|
|
||
|
let (dispatchers_data, dispatchers) = dispatchers(&mut ctxt, &app, analysis);
|
||
|
|
||
|
let init_fn = init(&mut ctxt, &app, analysis);
|
||
|
let init_arg = if cfg!(feature = "timer-queue") {
|
||
|
quote!(rtfm::Peripherals {
|
||
|
CBP: p.CBP,
|
||
|
CPUID: p.CPUID,
|
||
|
DCB: &mut p.DCB,
|
||
|
FPB: p.FPB,
|
||
|
FPU: p.FPU,
|
||
|
ITM: p.ITM,
|
||
|
MPU: p.MPU,
|
||
|
SCB: &mut p.SCB,
|
||
|
TPIU: p.TPIU,
|
||
|
})
|
||
|
} else {
|
||
|
quote!(rtfm::Peripherals {
|
||
|
CBP: p.CBP,
|
||
|
CPUID: p.CPUID,
|
||
|
DCB: p.DCB,
|
||
|
DWT: p.DWT,
|
||
|
FPB: p.FPB,
|
||
|
FPU: p.FPU,
|
||
|
ITM: p.ITM,
|
||
|
MPU: p.MPU,
|
||
|
SCB: &mut p.SCB,
|
||
|
SYST: p.SYST,
|
||
|
TPIU: p.TPIU,
|
||
|
})
|
||
|
};
|
||
|
|
||
|
let post_init = post_init(&ctxt, &app, analysis);
|
||
|
|
||
|
let (idle_fn, idle_expr) = idle(&mut ctxt, &app, analysis);
|
||
|
|
||
|
let exceptions = exceptions(&mut ctxt, app, analysis);
|
||
|
|
||
|
let (root_interrupts, scoped_interrupts) = interrupts(&mut ctxt, app, analysis);
|
||
|
|
||
|
let spawn = spawn(&mut ctxt, app, analysis);
|
||
|
|
||
|
let schedule = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => schedule(&ctxt, app),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let timer_queue = timer_queue(&ctxt, app, analysis);
|
||
|
|
||
|
let pre_init = pre_init(&ctxt, analysis);
|
||
|
|
||
|
let assertions = assertions(app, analysis);
|
||
|
|
||
|
let init = &ctxt.init;
|
||
|
quote!(
|
||
|
#resources
|
||
|
|
||
|
#spawn
|
||
|
|
||
|
#timer_queue
|
||
|
|
||
|
#schedule
|
||
|
|
||
|
#dispatchers_data
|
||
|
|
||
|
#(#exceptions)*
|
||
|
|
||
|
#root_interrupts
|
||
|
|
||
|
// We put these items into a pseudo-module to avoid a collision between the `interrupt`
|
||
|
// import and user code
|
||
|
const APP: () = {
|
||
|
use #device::interrupt;
|
||
|
|
||
|
#scoped_interrupts
|
||
|
|
||
|
#(#dispatchers)*
|
||
|
};
|
||
|
|
||
|
#(#tasks)*
|
||
|
|
||
|
#init_fn
|
||
|
|
||
|
#idle_fn
|
||
|
|
||
|
#[allow(unsafe_code)]
|
||
|
#[rtfm::export::entry]
|
||
|
#[doc(hidden)]
|
||
|
unsafe fn main() -> ! {
|
||
|
#assertions
|
||
|
|
||
|
rtfm::export::interrupt::disable();
|
||
|
|
||
|
#pre_init
|
||
|
|
||
|
#init(#init_arg);
|
||
|
|
||
|
#post_init
|
||
|
|
||
|
rtfm::export::interrupt::enable();
|
||
|
|
||
|
#idle_expr
|
||
|
}
|
||
|
)
|
||
|
.into()
|
||
|
}
|
||
|
|
||
|
fn resources(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
let mut module = vec![];
|
||
|
for (name, res) in &app.resources {
|
||
|
let attrs = &res.attrs;
|
||
|
let mut_ = &res.mutability;
|
||
|
let ty = &res.ty;
|
||
|
let expr = &res.expr;
|
||
|
|
||
|
if res.singleton {
|
||
|
items.push(quote!(
|
||
|
#(#attrs)*
|
||
|
pub static #mut_ #name: #ty = #expr;
|
||
|
));
|
||
|
|
||
|
let alias = mk_ident();
|
||
|
if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
|
||
|
items.push(mk_resource(
|
||
|
ctxt,
|
||
|
name,
|
||
|
quote!(#name),
|
||
|
*ceiling,
|
||
|
quote!(&mut <#name as owned_singleton::Singleton>::new()),
|
||
|
app,
|
||
|
Some(&mut module),
|
||
|
))
|
||
|
}
|
||
|
|
||
|
ctxt.statics.insert(name.clone(), alias);
|
||
|
} else {
|
||
|
let alias = mk_ident();
|
||
|
let symbol = format!("{}::{}", name, alias);
|
||
|
|
||
|
items.push(
|
||
|
expr.as_ref()
|
||
|
.map(|expr| {
|
||
|
quote!(
|
||
|
#(#attrs)*
|
||
|
#[export_name = #symbol]
|
||
|
static mut #alias: #ty = #expr;
|
||
|
)
|
||
|
})
|
||
|
.unwrap_or_else(|| {
|
||
|
quote!(
|
||
|
#(#attrs)*
|
||
|
#[export_name = #symbol]
|
||
|
static mut #alias: rtfm::export::MaybeUninit<#ty> =
|
||
|
rtfm::export::MaybeUninit::uninitialized();
|
||
|
)
|
||
|
}),
|
||
|
);
|
||
|
|
||
|
if let Some(Ownership::Shared { ceiling }) = analysis.ownerships.get(name) {
|
||
|
if res.mutability.is_some() {
|
||
|
let ptr = if res.expr.is_none() {
|
||
|
quote!(unsafe { #alias.get_mut() })
|
||
|
} else {
|
||
|
quote!(unsafe { &mut #alias })
|
||
|
};
|
||
|
|
||
|
items.push(mk_resource(
|
||
|
ctxt,
|
||
|
name,
|
||
|
quote!(#ty),
|
||
|
*ceiling,
|
||
|
ptr,
|
||
|
app,
|
||
|
Some(&mut module),
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ctxt.statics.insert(name.clone(), alias);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !module.is_empty() {
|
||
|
items.push(quote!(
|
||
|
/// Resource proxies
|
||
|
pub mod resources {
|
||
|
#(#module)*
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
quote!(#(#items)*)
|
||
|
}
|
||
|
|
||
|
fn init(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let attrs = &app.init.attrs;
|
||
|
let locals = mk_locals(&app.init.statics, true);
|
||
|
let stmts = &app.init.stmts;
|
||
|
let assigns = app
|
||
|
.init
|
||
|
.assigns
|
||
|
.iter()
|
||
|
.map(|assign| {
|
||
|
if app
|
||
|
.resources
|
||
|
.get(&assign.left)
|
||
|
.map(|r| r.expr.is_none())
|
||
|
.unwrap_or(false)
|
||
|
{
|
||
|
let alias = &ctxt.statics[&assign.left];
|
||
|
let expr = &assign.right;
|
||
|
quote!(unsafe { #alias.set(#expr); })
|
||
|
} else {
|
||
|
let left = &assign.left;
|
||
|
let right = &assign.right;
|
||
|
quote!(#left = #right;)
|
||
|
}
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
|
||
|
let prelude = prelude(
|
||
|
ctxt,
|
||
|
Kind::Init,
|
||
|
&app.init.args.resources,
|
||
|
&app.init.args.spawn,
|
||
|
&app.init.args.schedule,
|
||
|
app,
|
||
|
255,
|
||
|
analysis,
|
||
|
);
|
||
|
|
||
|
let module = module(
|
||
|
ctxt,
|
||
|
Kind::Init,
|
||
|
!app.init.args.schedule.is_empty(),
|
||
|
!app.init.args.spawn.is_empty(),
|
||
|
app,
|
||
|
);
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
let baseline = &ctxt.baseline;
|
||
|
let baseline_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(let #baseline = rtfm::Instant::artificial(0);),
|
||
|
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let start_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(
|
||
|
#[allow(unused_variables)]
|
||
|
let start = #baseline;
|
||
|
),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let unsafety = &app.init.unsafety;
|
||
|
let device = &app.args.device;
|
||
|
let init = &ctxt.init;
|
||
|
let name = format!("init::{}", init);
|
||
|
quote!(
|
||
|
#module
|
||
|
|
||
|
#(#attrs)*
|
||
|
#[export_name = #name]
|
||
|
#unsafety fn #init(mut core: rtfm::Peripherals) {
|
||
|
#(#locals)*
|
||
|
|
||
|
#baseline_let
|
||
|
|
||
|
#prelude
|
||
|
|
||
|
let mut device = unsafe { #device::Peripherals::steal() };
|
||
|
|
||
|
#start_let
|
||
|
|
||
|
#(#stmts)*
|
||
|
|
||
|
#(#assigns)*
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
fn post_init(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let mut exprs = vec![];
|
||
|
|
||
|
// TODO turn the assertions that check that the priority is not larger than what's supported by
|
||
|
// the device into compile errors
|
||
|
let device = &app.args.device;
|
||
|
let nvic_prio_bits = quote!(#device::NVIC_PRIO_BITS);
|
||
|
for (name, interrupt) in &app.interrupts {
|
||
|
let priority = interrupt.args.priority;
|
||
|
exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name)));
|
||
|
exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
|
||
|
exprs.push(quote!(p.NVIC.set_priority(
|
||
|
#device::Interrupt::#name,
|
||
|
((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
|
||
|
)));
|
||
|
}
|
||
|
|
||
|
for (name, exception) in &app.exceptions {
|
||
|
let priority = exception.args.priority;
|
||
|
exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
|
||
|
exprs.push(quote!(p.SCB.set_priority(
|
||
|
rtfm::export::SystemHandler::#name,
|
||
|
((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
|
||
|
)));
|
||
|
}
|
||
|
|
||
|
if !analysis.timer_queue.tasks.is_empty() {
|
||
|
let priority = analysis.timer_queue.priority;
|
||
|
exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
|
||
|
exprs.push(quote!(p.SCB.set_priority(
|
||
|
rtfm::export::SystemHandler::SysTick,
|
||
|
((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
|
||
|
)));
|
||
|
}
|
||
|
|
||
|
for (priority, dispatcher) in &analysis.dispatchers {
|
||
|
let name = &dispatcher.interrupt;
|
||
|
exprs.push(quote!(p.NVIC.enable(#device::Interrupt::#name)));
|
||
|
exprs.push(quote!(assert!(#priority <= (1 << #nvic_prio_bits))));
|
||
|
exprs.push(quote!(p.NVIC.set_priority(
|
||
|
#device::Interrupt::#name,
|
||
|
((1 << #nvic_prio_bits) - #priority) << (8 - #nvic_prio_bits),
|
||
|
)));
|
||
|
}
|
||
|
|
||
|
if app.idle.is_none() {
|
||
|
// Set SLEEPONEXIT bit to enter sleep mode when returning from ISR
|
||
|
exprs.push(quote!(p.SCB.scr.modify(|r| r | 1 << 1)));
|
||
|
}
|
||
|
|
||
|
// Enable and start the system timer
|
||
|
if !analysis.timer_queue.tasks.is_empty() {
|
||
|
let tq = &ctxt.timer_queue;
|
||
|
exprs.push(quote!(#tq.get_mut().syst.set_clock_source(rtfm::export::SystClkSource::Core)));
|
||
|
exprs.push(quote!(#tq.get_mut().syst.enable_counter()));
|
||
|
}
|
||
|
|
||
|
// Enable cycle counter
|
||
|
if cfg!(feature = "timer-queue") {
|
||
|
exprs.push(quote!(p.DCB.enable_trace()));
|
||
|
exprs.push(quote!(p.DWT.enable_cycle_counter()));
|
||
|
}
|
||
|
|
||
|
quote!(#(#exprs;)*)
|
||
|
}
|
||
|
|
||
|
/// This function creates creates a module for `init` / `idle` / a `task` (see `kind` argument)
|
||
|
fn module(
|
||
|
ctxt: &mut Context,
|
||
|
kind: Kind,
|
||
|
schedule: bool,
|
||
|
spawn: bool,
|
||
|
app: &App,
|
||
|
) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
let mut fields = vec![];
|
||
|
|
||
|
let name = kind.ident();
|
||
|
let priority = &ctxt.priority;
|
||
|
let device = &app.args.device;
|
||
|
|
||
|
let mut lt = None;
|
||
|
match kind {
|
||
|
Kind::Init => {
|
||
|
if cfg!(feature = "timer-queue") {
|
||
|
fields.push(quote!(
|
||
|
/// System start time = `Instant(0 /* cycles */)`
|
||
|
pub start: rtfm::Instant,
|
||
|
));
|
||
|
}
|
||
|
|
||
|
fields.push(quote!(
|
||
|
/// Core (Cortex-M) peripherals
|
||
|
pub core: rtfm::Peripherals<'a>,
|
||
|
/// Device specific peripherals
|
||
|
pub device: #device::Peripherals,
|
||
|
));
|
||
|
lt = Some(quote!('a));
|
||
|
}
|
||
|
Kind::Idle => {}
|
||
|
Kind::Exception(_) | Kind::Interrupt(_) => {
|
||
|
if cfg!(feature = "timer-queue") {
|
||
|
fields.push(quote!(
|
||
|
/// Time at which this handler started executing
|
||
|
pub start: rtfm::Instant,
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
Kind::Task(_) => {
|
||
|
if cfg!(feature = "timer-queue") {
|
||
|
fields.push(quote!(
|
||
|
/// The time at which this task was scheduled to run
|
||
|
pub scheduled: rtfm::Instant,
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if schedule {
|
||
|
lt = Some(quote!('a));
|
||
|
|
||
|
fields.push(quote!(
|
||
|
/// Tasks that can be scheduled from this context
|
||
|
pub schedule: Schedule<'a>,
|
||
|
));
|
||
|
|
||
|
items.push(quote!(
|
||
|
/// Tasks that can be scheduled from this context
|
||
|
#[derive(Clone, Copy)]
|
||
|
pub struct Schedule<'a> {
|
||
|
#[doc(hidden)]
|
||
|
pub #priority: &'a core::cell::Cell<u8>,
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
if spawn {
|
||
|
lt = Some(quote!('a));
|
||
|
|
||
|
fields.push(quote!(
|
||
|
/// Tasks that can be spawned from this context
|
||
|
pub spawn: Spawn<'a>,
|
||
|
));
|
||
|
|
||
|
if kind.is_idle() {
|
||
|
items.push(quote!(
|
||
|
/// Tasks that can be spawned from this context
|
||
|
#[derive(Clone, Copy)]
|
||
|
pub struct Spawn<'a> {
|
||
|
#[doc(hidden)]
|
||
|
pub #priority: &'a core::cell::Cell<u8>,
|
||
|
}
|
||
|
));
|
||
|
} else {
|
||
|
let baseline_field = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let baseline = &ctxt.baseline;
|
||
|
quote!(
|
||
|
#[doc(hidden)]
|
||
|
pub #baseline: rtfm::Instant,
|
||
|
)
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
items.push(quote!(
|
||
|
/// Tasks that can be spawned from this context
|
||
|
#[derive(Clone, Copy)]
|
||
|
pub struct Spawn<'a> {
|
||
|
#baseline_field
|
||
|
#[doc(hidden)]
|
||
|
pub #priority: &'a core::cell::Cell<u8>,
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let mut root = None;
|
||
|
if let Some(resources) = ctxt.resources.get(&kind) {
|
||
|
lt = Some(quote!('a));
|
||
|
|
||
|
root = Some(resources.decl.clone());
|
||
|
|
||
|
let alias = &resources.alias;
|
||
|
items.push(quote!(
|
||
|
#[doc(inline)]
|
||
|
pub use super::#alias as Resources;
|
||
|
));
|
||
|
|
||
|
fields.push(quote!(
|
||
|
/// Resources available in this context
|
||
|
pub resources: Resources<'a>,
|
||
|
));
|
||
|
};
|
||
|
|
||
|
let doc = match kind {
|
||
|
Kind::Exception(_) => "Exception handler",
|
||
|
Kind::Idle => "Idle loop",
|
||
|
Kind::Init => "Initialization function",
|
||
|
Kind::Interrupt(_) => "Interrupt handler",
|
||
|
Kind::Task(_) => "Software task",
|
||
|
};
|
||
|
|
||
|
quote!(
|
||
|
#root
|
||
|
|
||
|
#[doc = #doc]
|
||
|
pub mod #name {
|
||
|
/// Variables injected into this context by the `app` attribute
|
||
|
pub struct Context<#lt> {
|
||
|
#(#fields)*
|
||
|
}
|
||
|
|
||
|
#(#items)*
|
||
|
}
|
||
|
)
|
||
|
}
|
||
|
|
||
|
/// The prelude injects `resources`, `spawn`, `schedule` and `start` / `scheduled` (all values) into
|
||
|
/// a function scope
|
||
|
fn prelude(
|
||
|
ctxt: &mut Context,
|
||
|
kind: Kind,
|
||
|
resources: &Idents,
|
||
|
spawn: &Idents,
|
||
|
schedule: &Idents,
|
||
|
app: &App,
|
||
|
logical_prio: u8,
|
||
|
analysis: &Analysis,
|
||
|
) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
|
||
|
let lt = if kind.runs_once() {
|
||
|
quote!('static)
|
||
|
} else {
|
||
|
quote!('a)
|
||
|
};
|
||
|
|
||
|
let module = kind.ident();
|
||
|
|
||
|
let priority = &ctxt.priority;
|
||
|
if !resources.is_empty() {
|
||
|
let mut defs = vec![];
|
||
|
let mut exprs = vec![];
|
||
|
|
||
|
// NOTE This field is just to avoid unused type parameter errors around `'a`
|
||
|
defs.push(quote!(#[allow(dead_code)] #priority: &'a core::cell::Cell<u8>));
|
||
|
exprs.push(quote!(#priority));
|
||
|
|
||
|
let mut may_call_lock = false;
|
||
|
let mut needs_unsafe = false;
|
||
|
for name in resources {
|
||
|
let res = &app.resources[name];
|
||
|
let initialized = res.expr.is_some();
|
||
|
let singleton = res.singleton;
|
||
|
let mut_ = res.mutability;
|
||
|
let ty = &res.ty;
|
||
|
|
||
|
if kind.is_init() {
|
||
|
let mut force_mut = false;
|
||
|
if !analysis.ownerships.contains_key(name) {
|
||
|
// owned by Init
|
||
|
if singleton {
|
||
|
needs_unsafe = true;
|
||
|
defs.push(quote!(pub #name: #name));
|
||
|
exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new()));
|
||
|
continue;
|
||
|
} else {
|
||
|
defs.push(quote!(pub #name: &'static #mut_ #ty));
|
||
|
}
|
||
|
} else {
|
||
|
// owned by someone else
|
||
|
if singleton {
|
||
|
needs_unsafe = true;
|
||
|
defs.push(quote!(pub #name: &'a mut #name));
|
||
|
exprs
|
||
|
.push(quote!(#name: &mut <#name as owned_singleton::Singleton>::new()));
|
||
|
continue;
|
||
|
} else {
|
||
|
force_mut = true;
|
||
|
defs.push(quote!(pub #name: &'a mut #ty));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let alias = &ctxt.statics[name];
|
||
|
// Resources assigned to init are always const initialized
|
||
|
needs_unsafe = true;
|
||
|
if force_mut {
|
||
|
exprs.push(quote!(#name: &mut #alias));
|
||
|
} else {
|
||
|
exprs.push(quote!(#name: &#mut_ #alias));
|
||
|
}
|
||
|
} else {
|
||
|
let ownership = &analysis.ownerships[name];
|
||
|
|
||
|
if ownership.needs_lock(logical_prio) {
|
||
|
may_call_lock = true;
|
||
|
if singleton {
|
||
|
if mut_.is_none() {
|
||
|
needs_unsafe = true;
|
||
|
defs.push(quote!(pub #name: &'a #name));
|
||
|
exprs
|
||
|
.push(quote!(#name: &<#name as owned_singleton::Singleton>::new()));
|
||
|
continue;
|
||
|
} else {
|
||
|
// Generate a resource proxy
|
||
|
defs.push(quote!(pub #name: resources::#name<'a>));
|
||
|
exprs.push(quote!(#name: resources::#name { #priority }));
|
||
|
continue;
|
||
|
}
|
||
|
} else {
|
||
|
if mut_.is_none() {
|
||
|
defs.push(quote!(pub #name: &'a #ty));
|
||
|
} else {
|
||
|
// Generate a resource proxy
|
||
|
defs.push(quote!(pub #name: resources::#name<'a>));
|
||
|
exprs.push(quote!(#name: resources::#name { #priority }));
|
||
|
continue;
|
||
|
}
|
||
|
}
|
||
|
} else {
|
||
|
if singleton {
|
||
|
if kind.runs_once() {
|
||
|
needs_unsafe = true;
|
||
|
defs.push(quote!(pub #name: #name));
|
||
|
exprs.push(quote!(#name: <#name as owned_singleton::Singleton>::new()));
|
||
|
} else {
|
||
|
needs_unsafe = true;
|
||
|
defs.push(quote!(pub #name: &'a mut #name));
|
||
|
exprs.push(
|
||
|
quote!(#name: &mut <#name as owned_singleton::Singleton>::new()),
|
||
|
);
|
||
|
}
|
||
|
continue;
|
||
|
} else {
|
||
|
defs.push(quote!(pub #name: &#lt #mut_ #ty));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let alias = &ctxt.statics[name];
|
||
|
needs_unsafe = true;
|
||
|
if initialized {
|
||
|
exprs.push(quote!(#name: &#mut_ #alias));
|
||
|
} else {
|
||
|
let method = if mut_.is_some() {
|
||
|
quote!(get_mut)
|
||
|
} else {
|
||
|
quote!(get_ref)
|
||
|
};
|
||
|
exprs.push(quote!(#name: #alias.#method() ));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
let alias = mk_ident();
|
||
|
let unsafety = if needs_unsafe {
|
||
|
Some(quote!(unsafe))
|
||
|
} else {
|
||
|
None
|
||
|
};
|
||
|
|
||
|
let doc = format!("`{}::Resources`", kind.ident().to_string());
|
||
|
let decl = quote!(
|
||
|
#[doc = #doc]
|
||
|
#[allow(non_snake_case)]
|
||
|
pub struct #alias<'a> { #(#defs,)* }
|
||
|
);
|
||
|
items.push(quote!(
|
||
|
#[allow(unused_variables)]
|
||
|
#[allow(unsafe_code)]
|
||
|
#[allow(unused_mut)]
|
||
|
let mut resources = #unsafety { #alias { #(#exprs,)* } };
|
||
|
));
|
||
|
|
||
|
ctxt.resources
|
||
|
.insert(kind.clone(), Resources { alias, decl });
|
||
|
|
||
|
if may_call_lock {
|
||
|
items.push(quote!(
|
||
|
use rtfm::Mutex;
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !spawn.is_empty() {
|
||
|
// Populate `spawn_fn`
|
||
|
for task in spawn {
|
||
|
if ctxt.spawn_fn.contains_key(task) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ctxt.spawn_fn.insert(task.clone(), mk_ident());
|
||
|
}
|
||
|
|
||
|
if kind.is_idle() {
|
||
|
items.push(quote!(
|
||
|
#[allow(unused_variables)]
|
||
|
let spawn = #module::Spawn { #priority };
|
||
|
));
|
||
|
} else {
|
||
|
let baseline_expr = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let baseline = &ctxt.baseline;
|
||
|
quote!(#baseline)
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
items.push(quote!(
|
||
|
#[allow(unused_variables)]
|
||
|
let spawn = #module::Spawn { #priority, #baseline_expr };
|
||
|
));
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if !schedule.is_empty() {
|
||
|
// Populate `schedule_fn`
|
||
|
for task in schedule {
|
||
|
if ctxt.schedule_fn.contains_key(task) {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
ctxt.schedule_fn.insert(task.clone(), mk_ident());
|
||
|
}
|
||
|
|
||
|
items.push(quote!(
|
||
|
#[allow(unused_imports)]
|
||
|
use rtfm::U32Ext;
|
||
|
|
||
|
#[allow(unused_variables)]
|
||
|
let schedule = #module::Schedule { #priority };
|
||
|
));
|
||
|
}
|
||
|
|
||
|
if items.is_empty() {
|
||
|
quote!()
|
||
|
} else {
|
||
|
quote!(
|
||
|
let ref #priority = core::cell::Cell::new(#logical_prio);
|
||
|
|
||
|
#(#items)*
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn idle(
|
||
|
ctxt: &mut Context,
|
||
|
app: &App,
|
||
|
analysis: &Analysis,
|
||
|
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
|
||
|
if let Some(idle) = app.idle.as_ref() {
|
||
|
let attrs = &idle.attrs;
|
||
|
let locals = mk_locals(&idle.statics, true);
|
||
|
let stmts = &idle.stmts;
|
||
|
|
||
|
let prelude = prelude(
|
||
|
ctxt,
|
||
|
Kind::Idle,
|
||
|
&idle.args.resources,
|
||
|
&idle.args.spawn,
|
||
|
&idle.args.schedule,
|
||
|
app,
|
||
|
0,
|
||
|
analysis,
|
||
|
);
|
||
|
|
||
|
let module = module(
|
||
|
ctxt,
|
||
|
Kind::Idle,
|
||
|
!idle.args.schedule.is_empty(),
|
||
|
!idle.args.spawn.is_empty(),
|
||
|
app,
|
||
|
);
|
||
|
|
||
|
let unsafety = &idle.unsafety;
|
||
|
let idle = &ctxt.idle;
|
||
|
|
||
|
let name = format!("idle::{}", idle);
|
||
|
(
|
||
|
quote!(
|
||
|
#module
|
||
|
|
||
|
#(#attrs)*
|
||
|
#[export_name = #name]
|
||
|
#unsafety fn #idle() -> ! {
|
||
|
#(#locals)*
|
||
|
|
||
|
#prelude
|
||
|
|
||
|
#(#stmts)*
|
||
|
}),
|
||
|
quote!(#idle()),
|
||
|
)
|
||
|
} else {
|
||
|
(
|
||
|
quote!(),
|
||
|
quote!(loop {
|
||
|
rtfm::export::wfi();
|
||
|
}),
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn exceptions(ctxt: &mut Context, app: &App, analysis: &Analysis) -> Vec<proc_macro2::TokenStream> {
|
||
|
app.exceptions
|
||
|
.iter()
|
||
|
.map(|(ident, exception)| {
|
||
|
let attrs = &exception.attrs;
|
||
|
let statics = &exception.statics;
|
||
|
let stmts = &exception.stmts;
|
||
|
|
||
|
let prelude = prelude(
|
||
|
ctxt,
|
||
|
Kind::Exception(ident.clone()),
|
||
|
&exception.args.resources,
|
||
|
&exception.args.spawn,
|
||
|
&exception.args.schedule,
|
||
|
app,
|
||
|
exception.args.priority,
|
||
|
analysis,
|
||
|
);
|
||
|
|
||
|
let module = module(
|
||
|
ctxt,
|
||
|
Kind::Exception(ident.clone()),
|
||
|
!exception.args.schedule.is_empty(),
|
||
|
!exception.args.spawn.is_empty(),
|
||
|
app,
|
||
|
);
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
let baseline = &ctxt.baseline;
|
||
|
let baseline_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(let #baseline = rtfm::Instant::now();),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let start_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(
|
||
|
#[allow(unused_variables)]
|
||
|
let start = #baseline;
|
||
|
),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let unsafety = &exception.unsafety;
|
||
|
quote!(
|
||
|
#module
|
||
|
|
||
|
#[rtfm::export::exception]
|
||
|
#[doc(hidden)]
|
||
|
#(#attrs)*
|
||
|
#unsafety fn #ident() {
|
||
|
#(#statics)*
|
||
|
|
||
|
#baseline_let
|
||
|
|
||
|
#prelude
|
||
|
|
||
|
#start_let
|
||
|
|
||
|
rtfm::export::run(move || {
|
||
|
#(#stmts)*
|
||
|
})
|
||
|
})
|
||
|
})
|
||
|
.collect()
|
||
|
}
|
||
|
|
||
|
fn interrupts(
|
||
|
ctxt: &mut Context,
|
||
|
app: &App,
|
||
|
analysis: &Analysis,
|
||
|
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
|
||
|
let mut root = vec![];
|
||
|
let mut scoped = vec![];
|
||
|
|
||
|
for (ident, interrupt) in &app.interrupts {
|
||
|
let attrs = &interrupt.attrs;
|
||
|
let statics = &interrupt.statics;
|
||
|
let stmts = &interrupt.stmts;
|
||
|
|
||
|
let prelude = prelude(
|
||
|
ctxt,
|
||
|
Kind::Interrupt(ident.clone()),
|
||
|
&interrupt.args.resources,
|
||
|
&interrupt.args.spawn,
|
||
|
&interrupt.args.schedule,
|
||
|
app,
|
||
|
interrupt.args.priority,
|
||
|
analysis,
|
||
|
);
|
||
|
|
||
|
root.push(module(
|
||
|
ctxt,
|
||
|
Kind::Interrupt(ident.clone()),
|
||
|
!interrupt.args.schedule.is_empty(),
|
||
|
!interrupt.args.spawn.is_empty(),
|
||
|
app,
|
||
|
));
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
let baseline = &ctxt.baseline;
|
||
|
let baseline_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(let #baseline = rtfm::Instant::now();),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let start_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(
|
||
|
#[allow(unused_variables)]
|
||
|
let start = #baseline;
|
||
|
),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let unsafety = &interrupt.unsafety;
|
||
|
scoped.push(quote!(
|
||
|
#[interrupt]
|
||
|
#(#attrs)*
|
||
|
#unsafety fn #ident() {
|
||
|
#(#statics)*
|
||
|
|
||
|
#baseline_let
|
||
|
|
||
|
#prelude
|
||
|
|
||
|
#start_let
|
||
|
|
||
|
rtfm::export::run(move || {
|
||
|
#(#stmts)*
|
||
|
})
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
(quote!(#(#root)*), quote!(#(#scoped)*))
|
||
|
}
|
||
|
|
||
|
fn tasks(ctxt: &mut Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
for (name, task) in &app.tasks {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
let scheduleds_alias = mk_ident();
|
||
|
let free_alias = mk_ident();
|
||
|
let inputs_alias = mk_ident();
|
||
|
let task_alias = mk_ident();
|
||
|
|
||
|
let attrs = &task.attrs;
|
||
|
let inputs = &task.inputs;
|
||
|
let locals = mk_locals(&task.statics, false);
|
||
|
let stmts = &task.stmts;
|
||
|
|
||
|
let prelude = prelude(
|
||
|
ctxt,
|
||
|
Kind::Task(name.clone()),
|
||
|
&task.args.resources,
|
||
|
&task.args.spawn,
|
||
|
&task.args.schedule,
|
||
|
app,
|
||
|
task.args.priority,
|
||
|
analysis,
|
||
|
);
|
||
|
|
||
|
let ty = tuple_ty(inputs);
|
||
|
|
||
|
let capacity_lit = mk_capacity_literal(analysis.capacities[name]);
|
||
|
let capacity_ty = mk_typenum_capacity(analysis.capacities[name], true);
|
||
|
|
||
|
let resource = mk_resource(
|
||
|
ctxt,
|
||
|
&free_alias,
|
||
|
quote!(rtfm::export::FreeQueue<#capacity_ty>),
|
||
|
*analysis.free_queues.get(name).unwrap_or(&0),
|
||
|
quote!(#free_alias.get_mut()),
|
||
|
app,
|
||
|
None,
|
||
|
);
|
||
|
|
||
|
let scheduleds_static = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let scheduleds_symbol = format!("{}::SCHEDULED_TIMES::{}", name, scheduleds_alias);
|
||
|
|
||
|
quote!(
|
||
|
#[export_name = #scheduleds_symbol]
|
||
|
static mut #scheduleds_alias:
|
||
|
rtfm::export::MaybeUninit<[rtfm::Instant; #capacity_lit]> =
|
||
|
rtfm::export::MaybeUninit::uninitialized();
|
||
|
)
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let scheduled_let = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let baseline = &ctxt.baseline;
|
||
|
quote!(let scheduled = #baseline;)
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let baseline_arg = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let baseline = &ctxt.baseline;
|
||
|
quote!(#baseline: rtfm::Instant,)
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
let task_symbol = format!("{}::{}", name, task_alias);
|
||
|
let inputs_symbol = format!("{}::INPUTS::{}", name, inputs_alias);
|
||
|
let free_symbol = format!("{}::FREE_QUEUE::{}", name, free_alias);
|
||
|
let unsafety = &task.unsafety;
|
||
|
items.push(quote!(
|
||
|
// FIXME(MaybeUninit) MaybeUninit won't be necessary when core::mem::MaybeUninit
|
||
|
// stabilizes because heapless constructors will work in const context
|
||
|
#[export_name = #free_symbol]
|
||
|
static mut #free_alias: rtfm::export::MaybeUninit<
|
||
|
rtfm::export::FreeQueue<#capacity_ty>
|
||
|
> = rtfm::export::MaybeUninit::uninitialized();
|
||
|
|
||
|
#resource
|
||
|
|
||
|
#[export_name = #inputs_symbol]
|
||
|
static mut #inputs_alias: rtfm::export::MaybeUninit<[#ty; #capacity_lit]> =
|
||
|
rtfm::export::MaybeUninit::uninitialized();
|
||
|
|
||
|
#scheduleds_static
|
||
|
|
||
|
#(#attrs)*
|
||
|
#[export_name = #task_symbol]
|
||
|
#unsafety fn #task_alias(#baseline_arg #(#inputs,)*) {
|
||
|
#(#locals)*
|
||
|
|
||
|
#prelude
|
||
|
|
||
|
#scheduled_let
|
||
|
|
||
|
#(#stmts)*
|
||
|
}
|
||
|
));
|
||
|
|
||
|
items.push(module(
|
||
|
ctxt,
|
||
|
Kind::Task(name.clone()),
|
||
|
!task.args.schedule.is_empty(),
|
||
|
!task.args.spawn.is_empty(),
|
||
|
app,
|
||
|
));
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
ctxt.scheduleds.insert(name.clone(), scheduleds_alias);
|
||
|
ctxt.free_queues.insert(name.clone(), free_alias);
|
||
|
ctxt.inputs.insert(name.clone(), inputs_alias);
|
||
|
ctxt.tasks.insert(name.clone(), task_alias);
|
||
|
}
|
||
|
|
||
|
quote!(#(#items)*)
|
||
|
}
|
||
|
|
||
|
fn dispatchers(
|
||
|
ctxt: &mut Context,
|
||
|
app: &App,
|
||
|
analysis: &Analysis,
|
||
|
) -> (proc_macro2::TokenStream, proc_macro2::TokenStream) {
|
||
|
let mut data = vec![];
|
||
|
let mut dispatchers = vec![];
|
||
|
|
||
|
for (level, dispatcher) in &analysis.dispatchers {
|
||
|
let ready_alias = mk_ident();
|
||
|
let enum_alias = mk_ident();
|
||
|
let tasks = &dispatcher.tasks;
|
||
|
let capacity = mk_typenum_capacity(dispatcher.capacity, true);
|
||
|
|
||
|
let symbol = format!("P{}::READY_QUEUE::{}", level, ready_alias);
|
||
|
let e = quote!(rtfm::export);
|
||
|
let ty = quote!(#e::ReadyQueue<#enum_alias, #capacity>);
|
||
|
let ceiling = *analysis.ready_queues.get(&level).unwrap_or(&0);
|
||
|
let resource = mk_resource(
|
||
|
ctxt,
|
||
|
&ready_alias,
|
||
|
ty.clone(),
|
||
|
ceiling,
|
||
|
quote!(#ready_alias.get_mut()),
|
||
|
app,
|
||
|
None,
|
||
|
);
|
||
|
data.push(quote!(
|
||
|
#[allow(dead_code)]
|
||
|
#[allow(non_camel_case_types)]
|
||
|
enum #enum_alias { #(#tasks,)* }
|
||
|
|
||
|
#[export_name = #symbol]
|
||
|
static mut #ready_alias: #e::MaybeUninit<#ty> = #e::MaybeUninit::uninitialized();
|
||
|
|
||
|
#resource
|
||
|
));
|
||
|
|
||
|
let interrupt = &dispatcher.interrupt;
|
||
|
|
||
|
let arms = dispatcher
|
||
|
.tasks
|
||
|
.iter()
|
||
|
.map(|task| {
|
||
|
let inputs = &ctxt.inputs[task];
|
||
|
let free = &ctxt.free_queues[task];
|
||
|
let pats = tuple_pat(&app.tasks[task].inputs);
|
||
|
let alias = &ctxt.tasks[task];
|
||
|
|
||
|
let baseline_let;
|
||
|
let call;
|
||
|
match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let scheduleds = &ctxt.scheduleds[task];
|
||
|
baseline_let = quote!(
|
||
|
let baseline =
|
||
|
ptr::read(#scheduleds.get_ref().get_unchecked(usize::from(index)));
|
||
|
);
|
||
|
call = quote!(#alias(baseline, #pats));
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => {
|
||
|
baseline_let = quote!();
|
||
|
call = quote!(#alias(#pats));
|
||
|
}
|
||
|
};
|
||
|
|
||
|
quote!(#enum_alias::#task => {
|
||
|
#baseline_let
|
||
|
let input = ptr::read(#inputs.get_ref().get_unchecked(usize::from(index)));
|
||
|
#free.get_mut().split().0.enqueue_unchecked(index);
|
||
|
let (#pats) = input;
|
||
|
#call
|
||
|
})
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
|
||
|
let attrs = &dispatcher.attrs;
|
||
|
dispatchers.push(quote!(
|
||
|
#(#attrs)*
|
||
|
#[interrupt]
|
||
|
unsafe fn #interrupt() {
|
||
|
use core::ptr;
|
||
|
|
||
|
rtfm::export::run(|| {
|
||
|
while let Some((task, index)) = #ready_alias.get_mut().split().1.dequeue() {
|
||
|
match task {
|
||
|
#(#arms)*
|
||
|
}
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
));
|
||
|
|
||
|
ctxt.ready_queues.insert(*level, ready_alias);
|
||
|
ctxt.enums.insert(*level, enum_alias);
|
||
|
}
|
||
|
|
||
|
(quote!(#(#data)*), quote!(#(#dispatchers)*))
|
||
|
}
|
||
|
|
||
|
fn spawn(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
|
||
|
// Generate `spawn` functions
|
||
|
let device = &app.args.device;
|
||
|
let priority = &ctxt.priority;
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
let baseline = &ctxt.baseline;
|
||
|
for (task, alias) in &ctxt.spawn_fn {
|
||
|
let free = &ctxt.free_queues[task];
|
||
|
let level = app.tasks[task].args.priority;
|
||
|
let ready = &ctxt.ready_queues[&level];
|
||
|
let enum_ = &ctxt.enums[&level];
|
||
|
let dispatcher = &analysis.dispatchers[&level].interrupt;
|
||
|
let inputs = &ctxt.inputs[task];
|
||
|
let args = &app.tasks[task].inputs;
|
||
|
let ty = tuple_ty(args);
|
||
|
let pats = tuple_pat(args);
|
||
|
|
||
|
let scheduleds_write = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
let scheduleds = &ctxt.scheduleds[task];
|
||
|
quote!(
|
||
|
ptr::write(
|
||
|
#scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
|
||
|
#baseline,
|
||
|
);
|
||
|
)
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
let baseline_arg = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => quote!(#baseline: rtfm::Instant,),
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
|
||
|
items.push(quote!(
|
||
|
#[inline(always)]
|
||
|
unsafe fn #alias(
|
||
|
#baseline_arg
|
||
|
#priority: &core::cell::Cell<u8>,
|
||
|
#(#args,)*
|
||
|
) -> Result<(), #ty> {
|
||
|
use core::ptr;
|
||
|
|
||
|
use rtfm::Mutex;
|
||
|
|
||
|
if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
|
||
|
ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats));
|
||
|
#scheduleds_write
|
||
|
|
||
|
#ready { #priority }.lock(|rq| {
|
||
|
rq.split().0.enqueue_unchecked((#enum_::#task, index))
|
||
|
});
|
||
|
|
||
|
rtfm::pend(#device::Interrupt::#dispatcher);
|
||
|
|
||
|
Ok(())
|
||
|
} else {
|
||
|
Err((#pats))
|
||
|
}
|
||
|
}
|
||
|
))
|
||
|
}
|
||
|
|
||
|
// Generate `spawn` structs; these call the `spawn` functions generated above
|
||
|
for (name, spawn) in app.spawn_callers() {
|
||
|
if spawn.is_empty() {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
let is_idle = name.to_string() == "idle";
|
||
|
|
||
|
let mut methods = vec![];
|
||
|
for task in spawn {
|
||
|
let alias = &ctxt.spawn_fn[task];
|
||
|
let inputs = &app.tasks[task].inputs;
|
||
|
let ty = tuple_ty(inputs);
|
||
|
let pats = tuple_pat(inputs);
|
||
|
|
||
|
let instant = match () {
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
() => {
|
||
|
if is_idle {
|
||
|
quote!(rtfm::Instant::now(),)
|
||
|
} else {
|
||
|
quote!(self.#baseline,)
|
||
|
}
|
||
|
}
|
||
|
#[cfg(not(feature = "timer-queue"))]
|
||
|
() => quote!(),
|
||
|
};
|
||
|
methods.push(quote!(
|
||
|
#[allow(unsafe_code)]
|
||
|
#[inline]
|
||
|
pub fn #task(&self, #(#inputs,)*) -> Result<(), #ty> {
|
||
|
unsafe { #alias(#instant &self.#priority, #pats) }
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
items.push(quote!(
|
||
|
impl<'a> #name::Spawn<'a> {
|
||
|
#(#methods)*
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
quote!(#(#items)*)
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
fn schedule(ctxt: &Context, app: &App) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
|
||
|
// Generate `schedule` functions
|
||
|
let priority = &ctxt.priority;
|
||
|
let timer_queue = &ctxt.timer_queue;
|
||
|
for (task, alias) in &ctxt.schedule_fn {
|
||
|
let free = &ctxt.free_queues[task];
|
||
|
let enum_ = &ctxt.schedule_enum;
|
||
|
let inputs = &ctxt.inputs[task];
|
||
|
let scheduleds = &ctxt.scheduleds[task];
|
||
|
let args = &app.tasks[task].inputs;
|
||
|
let ty = tuple_ty(args);
|
||
|
let pats = tuple_pat(args);
|
||
|
|
||
|
items.push(quote!(
|
||
|
#[inline(always)]
|
||
|
unsafe fn #alias(
|
||
|
#priority: &core::cell::Cell<u8>,
|
||
|
instant: rtfm::Instant,
|
||
|
#(#args,)*
|
||
|
) -> Result<(), #ty> {
|
||
|
use core::ptr;
|
||
|
|
||
|
use rtfm::Mutex;
|
||
|
|
||
|
if let Some(index) = (#free { #priority }).lock(|f| f.split().1.dequeue()) {
|
||
|
ptr::write(#inputs.get_mut().get_unchecked_mut(usize::from(index)), (#pats));
|
||
|
ptr::write(
|
||
|
#scheduleds.get_mut().get_unchecked_mut(usize::from(index)),
|
||
|
instant,
|
||
|
);
|
||
|
|
||
|
let nr = rtfm::export::NotReady {
|
||
|
instant,
|
||
|
index,
|
||
|
task: #enum_::#task,
|
||
|
};
|
||
|
|
||
|
({#timer_queue { #priority }}).lock(|tq| tq.enqueue_unchecked(nr));
|
||
|
|
||
|
Ok(())
|
||
|
} else {
|
||
|
Err((#pats))
|
||
|
}
|
||
|
}
|
||
|
))
|
||
|
}
|
||
|
|
||
|
// Generate `Schedule` structs; these call the `schedule` functions generated above
|
||
|
for (name, schedule) in app.schedule_callers() {
|
||
|
if schedule.is_empty() {
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
debug_assert!(!schedule.is_empty());
|
||
|
|
||
|
let mut methods = vec![];
|
||
|
for task in schedule {
|
||
|
let alias = &ctxt.schedule_fn[task];
|
||
|
let inputs = &app.tasks[task].inputs;
|
||
|
let ty = tuple_ty(inputs);
|
||
|
let pats = tuple_pat(inputs);
|
||
|
|
||
|
methods.push(quote!(
|
||
|
#[inline]
|
||
|
pub fn #task(
|
||
|
&self,
|
||
|
instant: rtfm::Instant,
|
||
|
#(#inputs,)*
|
||
|
) -> Result<(), #ty> {
|
||
|
unsafe { #alias(&self.#priority, instant, #pats) }
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
items.push(quote!(
|
||
|
impl<'a> #name::Schedule<'a> {
|
||
|
#(#methods)*
|
||
|
}
|
||
|
));
|
||
|
}
|
||
|
|
||
|
quote!(#(#items)*)
|
||
|
}
|
||
|
|
||
|
fn timer_queue(ctxt: &Context, app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let tasks = &analysis.timer_queue.tasks;
|
||
|
|
||
|
if tasks.is_empty() {
|
||
|
return quote!();
|
||
|
}
|
||
|
|
||
|
let mut items = vec![];
|
||
|
|
||
|
let enum_ = &ctxt.schedule_enum;
|
||
|
items.push(quote!(
|
||
|
#[allow(dead_code)]
|
||
|
#[allow(non_camel_case_types)]
|
||
|
#[derive(Clone, Copy)]
|
||
|
enum #enum_ { #(#tasks,)* }
|
||
|
));
|
||
|
|
||
|
let cap = mk_typenum_capacity(analysis.timer_queue.capacity, false);
|
||
|
let tq = &ctxt.timer_queue;
|
||
|
let symbol = format!("TIMER_QUEUE::{}", tq);
|
||
|
items.push(quote!(
|
||
|
#[export_name = #symbol]
|
||
|
static mut #tq:
|
||
|
rtfm::export::MaybeUninit<rtfm::export::TimerQueue<#enum_, #cap>> =
|
||
|
rtfm::export::MaybeUninit::uninitialized();
|
||
|
));
|
||
|
|
||
|
items.push(mk_resource(
|
||
|
ctxt,
|
||
|
tq,
|
||
|
quote!(rtfm::export::TimerQueue<#enum_, #cap>),
|
||
|
analysis.timer_queue.ceiling,
|
||
|
quote!(#tq.get_mut()),
|
||
|
app,
|
||
|
None,
|
||
|
));
|
||
|
|
||
|
let priority = &ctxt.priority;
|
||
|
let device = &app.args.device;
|
||
|
let arms = tasks
|
||
|
.iter()
|
||
|
.map(|task| {
|
||
|
let level = app.tasks[task].args.priority;
|
||
|
let tenum = &ctxt.enums[&level];
|
||
|
let ready = &ctxt.ready_queues[&level];
|
||
|
let dispatcher = &analysis.dispatchers[&level].interrupt;
|
||
|
|
||
|
quote!(
|
||
|
#enum_::#task => {
|
||
|
(#ready { #priority }).lock(|rq| {
|
||
|
rq.split().0.enqueue_unchecked((#tenum::#task, index))
|
||
|
});
|
||
|
|
||
|
rtfm::pend(#device::Interrupt::#dispatcher);
|
||
|
}
|
||
|
)
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
|
||
|
let logical_prio = analysis.timer_queue.priority;
|
||
|
items.push(quote!(
|
||
|
#[rtfm::export::exception]
|
||
|
#[doc(hidden)]
|
||
|
unsafe fn SysTick() {
|
||
|
use rtfm::Mutex;
|
||
|
|
||
|
let ref #priority = core::cell::Cell::new(#logical_prio);
|
||
|
|
||
|
rtfm::export::run(|| {
|
||
|
rtfm::export::sys_tick(#tq { #priority }, |task, index| {
|
||
|
match task {
|
||
|
#(#arms)*
|
||
|
}
|
||
|
});
|
||
|
})
|
||
|
}
|
||
|
));
|
||
|
|
||
|
quote!(#(#items)*)
|
||
|
}
|
||
|
|
||
|
fn pre_init(ctxt: &Context, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let mut exprs = vec![];
|
||
|
|
||
|
// FIXME(MaybeUninit) Because we are using a fake MaybeUninit we need to set the Option tag to
|
||
|
// Some; otherwise the get_ref and get_mut could result in UB. Also heapless collections can't
|
||
|
// be constructed in const context; we have to initialize them at runtime (i.e. here).
|
||
|
|
||
|
// these are `MaybeUninit` arrays
|
||
|
for inputs in ctxt.inputs.values() {
|
||
|
exprs.push(quote!(#inputs.set(core::mem::uninitialized());))
|
||
|
}
|
||
|
|
||
|
#[cfg(feature = "timer-queue")]
|
||
|
for inputs in ctxt.scheduleds.values() {
|
||
|
exprs.push(quote!(#inputs.set(core::mem::uninitialized());))
|
||
|
}
|
||
|
|
||
|
// these are `MaybeUninit` `ReadyQueue`s
|
||
|
for queue in ctxt.ready_queues.values() {
|
||
|
exprs.push(quote!(#queue.set(rtfm::export::ReadyQueue::new());))
|
||
|
}
|
||
|
|
||
|
// these are `MaybeUninit` `FreeQueue`s
|
||
|
for free in ctxt.free_queues.values() {
|
||
|
exprs.push(quote!(#free.set(rtfm::export::FreeQueue::new());))
|
||
|
}
|
||
|
|
||
|
// end-of-FIXME
|
||
|
|
||
|
// Initialize the timer queue
|
||
|
if !analysis.timer_queue.tasks.is_empty() {
|
||
|
let tq = &ctxt.timer_queue;
|
||
|
exprs.push(quote!(#tq.set(rtfm::export::TimerQueue::new(p.SYST));));
|
||
|
}
|
||
|
|
||
|
// Populate the `FreeQueue`s
|
||
|
for (task, alias) in &ctxt.free_queues {
|
||
|
let capacity = analysis.capacities[task];
|
||
|
exprs.push(quote!(
|
||
|
for i in 0..#capacity {
|
||
|
#alias.get_mut().enqueue_unchecked(i);
|
||
|
}
|
||
|
))
|
||
|
}
|
||
|
|
||
|
// Set the cycle count to 0 and disable it while `init` executes
|
||
|
if cfg!(feature = "timer-queue") {
|
||
|
exprs.push(quote!(p.DWT.ctrl.modify(|r| r & !1);));
|
||
|
exprs.push(quote!(p.DWT.cyccnt.write(0);));
|
||
|
}
|
||
|
|
||
|
quote!(
|
||
|
let mut p = rtfm::export::Peripherals::steal();
|
||
|
#(#exprs)*
|
||
|
)
|
||
|
}
|
||
|
|
||
|
fn assertions(app: &App, analysis: &Analysis) -> proc_macro2::TokenStream {
|
||
|
let mut items = vec![];
|
||
|
|
||
|
for ty in &analysis.assert_sync {
|
||
|
items.push(quote!(rtfm::export::assert_sync::<#ty>()));
|
||
|
}
|
||
|
|
||
|
for task in &analysis.tasks_assert_send {
|
||
|
let ty = tuple_ty(&app.tasks[task].inputs);
|
||
|
items.push(quote!(rtfm::export::assert_send::<#ty>()));
|
||
|
}
|
||
|
|
||
|
// all late resources need to be `Send`
|
||
|
for ty in &analysis.resources_assert_send {
|
||
|
items.push(quote!(rtfm::export::assert_send::<#ty>()));
|
||
|
}
|
||
|
|
||
|
quote!(#(#items;)*)
|
||
|
}
|
||
|
|
||
|
fn mk_resource(
|
||
|
ctxt: &Context,
|
||
|
struct_: &Ident,
|
||
|
ty: proc_macro2::TokenStream,
|
||
|
ceiling: u8,
|
||
|
ptr: proc_macro2::TokenStream,
|
||
|
app: &App,
|
||
|
module: Option<&mut Vec<proc_macro2::TokenStream>>,
|
||
|
) -> proc_macro2::TokenStream {
|
||
|
let priority = &ctxt.priority;
|
||
|
let device = &app.args.device;
|
||
|
|
||
|
let mut items = vec![];
|
||
|
|
||
|
let path = if let Some(module) = module {
|
||
|
let doc = format!("`{}`", ty);
|
||
|
module.push(quote!(
|
||
|
#[doc = #doc]
|
||
|
pub struct #struct_<'a> {
|
||
|
#[doc(hidden)]
|
||
|
pub #priority: &'a core::cell::Cell<u8>,
|
||
|
}
|
||
|
));
|
||
|
|
||
|
quote!(resources::#struct_)
|
||
|
} else {
|
||
|
items.push(quote!(
|
||
|
struct #struct_<'a> {
|
||
|
#priority: &'a core::cell::Cell<u8>,
|
||
|
}
|
||
|
));
|
||
|
|
||
|
quote!(#struct_)
|
||
|
};
|
||
|
|
||
|
items.push(quote!(
|
||
|
unsafe impl<'a> rtfm::Mutex for #path<'a> {
|
||
|
const CEILING: u8 = #ceiling;
|
||
|
const NVIC_PRIO_BITS: u8 = #device::NVIC_PRIO_BITS;
|
||
|
type Data = #ty;
|
||
|
|
||
|
#[inline(always)]
|
||
|
unsafe fn priority(&self) -> &core::cell::Cell<u8> {
|
||
|
&self.#priority
|
||
|
}
|
||
|
|
||
|
#[inline(always)]
|
||
|
fn ptr(&self) -> *mut Self::Data {
|
||
|
unsafe { #ptr }
|
||
|
}
|
||
|
}
|
||
|
));
|
||
|
|
||
|
quote!(#(#items)*)
|
||
|
}
|
||
|
|
||
|
fn mk_capacity_literal(capacity: u8) -> LitInt {
|
||
|
LitInt::new(u64::from(capacity), IntSuffix::None, Span::call_site())
|
||
|
}
|
||
|
|
||
|
fn mk_typenum_capacity(capacity: u8, power_of_two: bool) -> proc_macro2::TokenStream {
|
||
|
let capacity = if power_of_two {
|
||
|
capacity
|
||
|
.checked_next_power_of_two()
|
||
|
.expect("capacity.next_power_of_two()")
|
||
|
} else {
|
||
|
capacity
|
||
|
};
|
||
|
|
||
|
let ident = Ident::new(&format!("U{}", capacity), Span::call_site());
|
||
|
|
||
|
quote!(rtfm::export::consts::#ident)
|
||
|
}
|
||
|
|
||
|
fn mk_ident() -> Ident {
|
||
|
static CALL_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||
|
|
||
|
let elapsed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap();
|
||
|
|
||
|
let secs = elapsed.as_secs();
|
||
|
let nanos = elapsed.subsec_nanos();
|
||
|
|
||
|
let count = CALL_COUNT.fetch_add(1, Ordering::SeqCst) as u32;
|
||
|
let mut seed: [u8; 16] = [0; 16];
|
||
|
|
||
|
for (i, v) in seed.iter_mut().take(8).enumerate() {
|
||
|
*v = ((secs >> (i * 8)) & 0xFF) as u8
|
||
|
}
|
||
|
|
||
|
for (i, v) in seed.iter_mut().skip(8).take(4).enumerate() {
|
||
|
*v = ((nanos >> (i * 8)) & 0xFF) as u8
|
||
|
}
|
||
|
|
||
|
for (i, v) in seed.iter_mut().skip(12).enumerate() {
|
||
|
*v = ((count >> (i * 8)) & 0xFF) as u8
|
||
|
}
|
||
|
|
||
|
let mut rng = rand::rngs::SmallRng::from_seed(seed);
|
||
|
Ident::new(
|
||
|
&(0..16)
|
||
|
.map(|i| {
|
||
|
if i == 0 || rng.gen() {
|
||
|
('a' as u8 + rng.gen::<u8>() % 25) as char
|
||
|
} else {
|
||
|
('0' as u8 + rng.gen::<u8>() % 10) as char
|
||
|
}
|
||
|
})
|
||
|
.collect::<String>(),
|
||
|
Span::call_site(),
|
||
|
)
|
||
|
}
|
||
|
|
||
|
// `once = true` means that these locals will be called from a function that will run *once*
|
||
|
fn mk_locals(locals: &HashMap<Ident, Static>, once: bool) -> proc_macro2::TokenStream {
|
||
|
let lt = if once { Some(quote!('static)) } else { None };
|
||
|
|
||
|
let locals = locals
|
||
|
.iter()
|
||
|
.map(|(name, static_)| {
|
||
|
let attrs = &static_.attrs;
|
||
|
let expr = &static_.expr;
|
||
|
let ident = name;
|
||
|
let ty = &static_.ty;
|
||
|
|
||
|
quote!(
|
||
|
#[allow(non_snake_case)]
|
||
|
let #ident: &#lt mut #ty = {
|
||
|
#(#attrs)*
|
||
|
static mut #ident: #ty = #expr;
|
||
|
|
||
|
unsafe { &mut #ident }
|
||
|
};
|
||
|
)
|
||
|
})
|
||
|
.collect::<Vec<_>>();
|
||
|
|
||
|
quote!(#(#locals)*)
|
||
|
}
|
||
|
|
||
|
fn tuple_pat(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
|
||
|
if inputs.len() == 1 {
|
||
|
let pat = &inputs[0].pat;
|
||
|
quote!(#pat)
|
||
|
} else {
|
||
|
let pats = inputs.iter().map(|i| &i.pat).collect::<Vec<_>>();
|
||
|
|
||
|
quote!(#(#pats,)*)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn tuple_ty(inputs: &[ArgCaptured]) -> proc_macro2::TokenStream {
|
||
|
if inputs.len() == 1 {
|
||
|
let ty = &inputs[0].ty;
|
||
|
quote!(#ty)
|
||
|
} else {
|
||
|
let tys = inputs.iter().map(|i| &i.ty).collect::<Vec<_>>();
|
||
|
|
||
|
quote!((#(#tys,)*))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
#[derive(Clone, Eq, Hash, PartialEq)]
|
||
|
enum Kind {
|
||
|
Exception(Ident),
|
||
|
Idle,
|
||
|
Init,
|
||
|
Interrupt(Ident),
|
||
|
Task(Ident),
|
||
|
}
|
||
|
|
||
|
impl Kind {
|
||
|
fn ident(&self) -> Ident {
|
||
|
match self {
|
||
|
Kind::Init => Ident::new("init", Span::call_site()),
|
||
|
Kind::Idle => Ident::new("idle", Span::call_site()),
|
||
|
Kind::Task(name) | Kind::Interrupt(name) | Kind::Exception(name) => name.clone(),
|
||
|
}
|
||
|
}
|
||
|
|
||
|
fn is_idle(&self) -> bool {
|
||
|
*self == Kind::Idle
|
||
|
}
|
||
|
|
||
|
fn is_init(&self) -> bool {
|
||
|
*self == Kind::Init
|
||
|
}
|
||
|
|
||
|
fn runs_once(&self) -> bool {
|
||
|
match *self {
|
||
|
Kind::Init | Kind::Idle => true,
|
||
|
_ => false,
|
||
|
}
|
||
|
}
|
||
|
}
|