diff --git a/ci/expected/lm3s6965/trampoline.run b/ci/expected/lm3s6965/trampoline.run new file mode 100644 index 000000000000..87dab9840870 --- /dev/null +++ b/ci/expected/lm3s6965/trampoline.run @@ -0,0 +1,6 @@ +gpioa lock +gpioa unlock +SysTick start +idle +SysTick start +idle end diff --git a/examples/lm3s6965/examples/trampoline.rs b/examples/lm3s6965/examples/trampoline.rs new file mode 100644 index 000000000000..03768a649865 --- /dev/null +++ b/examples/lm3s6965/examples/trampoline.rs @@ -0,0 +1,65 @@ +//! examples/bouncy_trampoline.rs + +#![no_std] +#![no_main] +#![deny(warnings)] +#![deny(unsafe_code)] +#![deny(missing_docs)] + +use panic_semihosting as _; + +// `examples/bouncy_trampoline.rs` testing trampoline feature +#[rtic::app(device = lm3s6965)] +mod app { + use cortex_m_semihosting::{debug, hprintln}; + use lm3s6965::Interrupt; + + #[shared] + struct Shared { + h_priority: u8, + } + + #[local] + struct Local {} + + #[init] + fn init(_: init::Context) -> (Shared, Local) { + // Trigger SW0 interrupt to test NMI preemption + rtic::pend(Interrupt::GPIOA); + (Shared { h_priority: 0 }, Local {}) + } + + #[idle] + fn idle(_: idle::Context) -> ! { + hprintln!("idle"); + + // pend another interrupt to test trampoline + cortex_m::peripheral::SCB::set_pendst(); + hprintln!("idle end"); + + debug::exit(debug::EXIT_SUCCESS); + loop { + cortex_m::asm::wfi(); + } + } + + #[task(binds = SysTick, trampoline = GPIOC, priority = 2, shared = [h_priority])] + fn sys_tick(_: sys_tick::Context) { + hprintln!("SysTick start"); + } + + #[task(binds = GPIOA, priority = 1, shared = [h_priority])] + fn gpioa(mut ctx: gpioa::Context) { + ctx.shared.h_priority.lock(|_| { + hprintln!("gpioa lock"); + cortex_m::peripheral::SCB::set_pendst(); + cortex_m::asm::delay(100_000_000); + hprintln!("gpioa unlock"); + }); + } + + #[task(binds = GPIOB, priority = 3, shared = [h_priority])] + fn high_priority_task(_: high_priority_task::Context) { + hprintln!("High priority task"); + } +} diff --git a/rtic-macros/CHANGELOG.md b/rtic-macros/CHANGELOG.md index f9a9445e5d07..8c6b61a2056c 100644 --- a/rtic-macros/CHANGELOG.md +++ b/rtic-macros/CHANGELOG.md @@ -10,6 +10,9 @@ For each category, *Added*, *Changed*, *Fixed* add new entries at the top! ### Added - Outer attributes applied to RTIC app module are now forwarded to the generated code. +- Added the trampoline argument to hardware tasks which lets the given trampoline execute the code, and the bind only pends the trampoline. +-Cortex-m source masking returns an error if a non maskable interrupt is used as a bind without a trampoline. +- removed leading space ``task-reference-in-spawner.stderr`` as to make ci pass. ## [v2.2.0] - 2025-06-22 diff --git a/rtic-macros/src/codegen/bindings/cortex.rs b/rtic-macros/src/codegen/bindings/cortex.rs index 5c56261db5d4..3610506f598f 100644 --- a/rtic-macros/src/codegen/bindings/cortex.rs +++ b/rtic-macros/src/codegen/bindings/cortex.rs @@ -102,7 +102,20 @@ mod source_masking { } else { None } - })) { + }) + // Add trampoline tasks if present and not exceptions + .chain(app.hardware_tasks.values().filter_map(|task| { + if let Some(trampoline) = &task.args.trampoline { + if !is_exception(trampoline) { + Some((&task.args.priority, trampoline)) + } else { + None + } + } else { + None + } + })) + ) { let v: &mut Vec<_> = prio_to_masks.entry(priority - 1).or_default(); v.push(quote!(#device::Interrupt::#name as u32)); mask_ids.push(quote!(#device::Interrupt::#name as u32)); @@ -241,7 +254,19 @@ pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec< } else { Some((&task.args.priority, &task.args.binds)) } - })) { + })).chain(app.hardware_tasks.values().filter_map(|task| { + if let Some(trampoline) = &task.args.trampoline { + if is_exception(trampoline) { + // We do exceptions in another pass + None + } else { + // If there's a trampoline, we need to unmask and set priority for it too + Some((&task.args.priority, trampoline)) + } + } else { + None + } + })){ let es = format!( "Maximum priority used by interrupt vector '{name}' is more than supported by hardware" ); @@ -319,13 +344,13 @@ pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::R .filter(|prio| *prio > 0) .collect::>(); - let need = priorities.len(); + let need_software = priorities.len(); let given = app.args.dispatchers.len(); - if need > given { + if need_software > given { let s = { format!( "not enough interrupts to dispatch \ - all software tasks (need: {need}; given: {given})" + all software tasks (need: {need_software}; given: {given})" ) }; @@ -350,6 +375,31 @@ pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::R } } + // Check that a exception is not used with shared resources + // TODO This does not stop priority inversion in source masking if the + // exception does not use shared resources. We do currently allow this, + // since it does not corrupt data, but it may be worth reconsidering this + // decision. + #[cfg(feature = "cortex-m-source-masking")] + for (name, task) in &app.hardware_tasks { + if is_exception(name) && !app.shared_resources.is_empty(){ + if let Some(trampoline) = task.args.trampoline.as_ref() { + if is_exception(trampoline) { + return Err(parse::Error::new( + trampoline.span(), + "cannot use exceptions as trampoline tasks when using source masking", + )); + } + } else { + return Err(parse::Error::new( + name.span(), + "cannot use exceptions with shared resources as hardware tasks when using \ + source masking, consider adding the trampoline attribute", + )); + } + } + } + Ok(()) } diff --git a/rtic-macros/src/codegen/hardware_tasks.rs b/rtic-macros/src/codegen/hardware_tasks.rs index ee85f59f3351..9b238295398e 100644 --- a/rtic-macros/src/codegen/hardware_tasks.rs +++ b/rtic-macros/src/codegen/hardware_tasks.rs @@ -1,3 +1,4 @@ +use crate::codegen::util; use crate::syntax::{ast::App, Context}; use crate::{ analyze::Analysis, @@ -23,27 +24,67 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let entry_stmts = interrupt_entry(app, analysis); let exit_stmts = interrupt_exit(app, analysis); let config = handler_config(app, analysis, symbol.clone()); + + if let Some(trampoline) = &task.args.trampoline { + let trampoline_symbol = trampoline.clone(); + let trampoline_config = handler_config(app, analysis, trampoline_symbol.clone()); + let rt_err = util::rt_err_ident(); - mod_app.push(quote!( - #[allow(non_snake_case)] - #[no_mangle] - #(#attrs)* - #(#cfgs)* - #(#config)* - unsafe fn #symbol() { - #(#entry_stmts)* + mod_app.push(quote!( - const PRIORITY: u8 = #priority; - - rtic::export::run(PRIORITY, || { - #name( - #name::Context::new() - ) - }); + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + #(#config)* + unsafe fn #symbol() { + use #rt_err::Interrupt; + rtic::pend(Interrupt::#trampoline_symbol); + } - #(#exit_stmts)* - } - )); + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + #(#trampoline_config)* + unsafe fn #trampoline_symbol() { + #(#entry_stmts)* + + const PRIORITY: u8 = #priority; + + rtic::export::run(PRIORITY, || { + #name( + #name::Context::new() + ) + }); + + + #(#exit_stmts)* + } + )); + + } else { + mod_app.push(quote!( + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + #(#config)* + unsafe fn #symbol() { + #(#entry_stmts)* + + const PRIORITY: u8 = #priority; + + rtic::export::run(PRIORITY, || { + #name( + #name::Context::new() + ) + }); + + #(#exit_stmts)* + } + )); + } // `${task}Locals` if !task.args.local_resources.is_empty() { @@ -51,7 +92,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { local_resources_struct::codegen(Context::HardwareTask(name), app); root.push(item); - mod_app.push(constructor); } @@ -61,7 +101,6 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { shared_resources_struct::codegen(Context::HardwareTask(name), app); root.push(item); - mod_app.push(constructor); } diff --git a/rtic-macros/src/syntax/ast.rs b/rtic-macros/src/syntax/ast.rs index 44c1385d24fb..0229e64ef2a9 100644 --- a/rtic-macros/src/syntax/ast.rs +++ b/rtic-macros/src/syntax/ast.rs @@ -298,6 +298,9 @@ pub struct HardwareTaskArgs { /// The interrupt or exception that this task is bound to pub binds: Ident, + /// if set the bind would trampoline with the given interrupt handler + pub trampoline: Option, + /// The priority of this task pub priority: u8, diff --git a/rtic-macros/src/syntax/parse.rs b/rtic-macros/src/syntax/parse.rs index ea7ff29409ad..3b0494a43a56 100644 --- a/rtic-macros/src/syntax/parse.rs +++ b/rtic-macros/src/syntax/parse.rs @@ -193,6 +193,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result parse::Result { + if trampoline.is_some() { + return Err(parse::Error::new( + ident.span(), + "argument appears more than once", + )); + } + + // Parse identifier name + let ident: Ident = input.parse()?; + + trampoline = Some(ident); + }, "priority" => { if priority.is_some() { @@ -277,8 +292,8 @@ fn task_args(tokens: TokenStream2) -> parse::Result { - return Err(parse::Error::new(ident.span(), "unexpected argument")); + a => { + return Err(parse::Error::new(ident.span(), format!("unexpected argument {}", a))); } } @@ -308,6 +323,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result ui/task-reference-in-spawn.rs:3:1 | -3 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, GPIOA])] + 3 | #[rtic::app(device = lm3s6965, dispatchers = [SSI0, QEI0, GPIOA])] | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ | | | `_0` is a reference that is only valid in the function body