From 11d2ca9f2bd47e8c653c5b830b137ce263b66b54 Mon Sep 17 00:00:00 2001 From: Olle Ronstad Date: Thu, 2 Oct 2025 17:02:43 +0200 Subject: [PATCH 1/7] added argument trampoline to hardware tasks to allow exception "masking" Co-authored-by: Salon --- rtic-macros/src/codegen/bindings/cortex.rs | 6 +- rtic-macros/src/codegen/hardware_tasks.rs | 78 ++++++++++++++++------ rtic-macros/src/syntax/ast.rs | 3 + rtic-macros/src/syntax/parse.rs | 22 +++++- 4 files changed, 81 insertions(+), 28 deletions(-) diff --git a/rtic-macros/src/codegen/bindings/cortex.rs b/rtic-macros/src/codegen/bindings/cortex.rs index 5c56261db5d4..87a06ac55ee9 100644 --- a/rtic-macros/src/codegen/bindings/cortex.rs +++ b/rtic-macros/src/codegen/bindings/cortex.rs @@ -319,13 +319,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})" ) }; diff --git a/rtic-macros/src/codegen/hardware_tasks.rs b/rtic-macros/src/codegen/hardware_tasks.rs index ee85f59f3351..e77c7f1e5881 100644 --- a/rtic-macros/src/codegen/hardware_tasks.rs +++ b/rtic-macros/src/codegen/hardware_tasks.rs @@ -24,26 +24,62 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { let exit_stmts = interrupt_exit(app, analysis); let config = handler_config(app, analysis, symbol.clone()); - 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)* - } - )); + if let Some(trampoline) = &task.args.trampoline { + let trampoline_symbol = trampoline.clone(); + mod_app.push(quote!( + + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + #(#config)* + unsafe fn #symbol() { + rtic::export::pend(rtic::export::Interrupt::#trampoline_symbol); + } + + #[allow(non_snake_case)] + #[no_mangle] + #(#attrs)* + #(#cfgs)* + #(#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 +87,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 +96,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 Date: Fri, 3 Oct 2025 11:36:56 +0200 Subject: [PATCH 2/7] added trampoline to hardware tasks . --- rtic-macros/src/codegen/hardware_tasks.rs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/rtic-macros/src/codegen/hardware_tasks.rs b/rtic-macros/src/codegen/hardware_tasks.rs index e77c7f1e5881..b7c54997029a 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,9 +24,12 @@ 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)] @@ -34,14 +38,17 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { #(#cfgs)* #(#config)* unsafe fn #symbol() { - rtic::export::pend(rtic::export::Interrupt::#trampoline_symbol); + info!("Pend trampoline"); + use #rt_err::Interrupt; + rtic::pend(Interrupt::#trampoline_symbol); + info!("Exit trampoline handler"); } #[allow(non_snake_case)] #[no_mangle] #(#attrs)* #(#cfgs)* - #(#config)* + #(#trampoline_config)* unsafe fn #trampoline_symbol() { #(#entry_stmts)* From 936dfc51e98bf107fdcbff21db03896283bdf958 Mon Sep 17 00:00:00 2001 From: Olle Ronstad Date: Fri, 3 Oct 2025 14:04:55 +0200 Subject: [PATCH 3/7] added trampoline init --- rtic-macros/src/codegen/bindings/cortex.rs | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/rtic-macros/src/codegen/bindings/cortex.rs b/rtic-macros/src/codegen/bindings/cortex.rs index 87a06ac55ee9..8f759c3eae89 100644 --- a/rtic-macros/src/codegen/bindings/cortex.rs +++ b/rtic-macros/src/codegen/bindings/cortex.rs @@ -241,7 +241,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 + return 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" ); From ccddee6198b0598b5651ce9351ee4206c6edab63 Mon Sep 17 00:00:00 2001 From: Olle Ronstad Date: Fri, 3 Oct 2025 17:30:45 +0200 Subject: [PATCH 4/7] continued work on rtic trampoline Co-authored-by: Salon --- ci/expected/lm3s6965/trampoline.run | 6 ++ examples/lm3s6965/examples/trampoline.rs | 65 ++++++++++++++++++++++ rtic-macros/src/codegen/bindings/cortex.rs | 38 ++++++++++++- rtic-macros/src/codegen/hardware_tasks.rs | 2 - 4 files changed, 107 insertions(+), 4 deletions(-) create mode 100644 ci/expected/lm3s6965/trampoline.run create mode 100644 examples/lm3s6965/examples/trampoline.rs 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/src/codegen/bindings/cortex.rs b/rtic-macros/src/codegen/bindings/cortex.rs index 8f759c3eae89..a73f92271df5 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)); @@ -245,7 +258,7 @@ pub fn pre_init_enable_interrupts(app: &App, analysis: &CodegenAnalysis) -> Vec< if let Some(trampoline) = &task.args.trampoline { if is_exception(trampoline) { // We do exceptions in another pass - return None; + None } else { // If there's a trampoline, we need to unmask and set priority for it too Some((&task.args.priority, trampoline)) @@ -362,6 +375,27 @@ pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::R } } + // Check that a exception is not used with shared resources + // TODO provide warning when a exception is used with source masking since it then ignores locks in priority ceilings + #[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 b7c54997029a..9b238295398e 100644 --- a/rtic-macros/src/codegen/hardware_tasks.rs +++ b/rtic-macros/src/codegen/hardware_tasks.rs @@ -38,10 +38,8 @@ pub fn codegen(app: &App, analysis: &Analysis) -> TokenStream2 { #(#cfgs)* #(#config)* unsafe fn #symbol() { - info!("Pend trampoline"); use #rt_err::Interrupt; rtic::pend(Interrupt::#trampoline_symbol); - info!("Exit trampoline handler"); } #[allow(non_snake_case)] From 4c855d6c839918f2761968aa678e8db83b35083f Mon Sep 17 00:00:00 2001 From: Olle Ronstad Date: Fri, 3 Oct 2025 17:35:16 +0200 Subject: [PATCH 5/7] Fixed misc errors in ci --- rtic-monotonics/src/systick.rs | 2 +- rtic/ui/task-reference-in-spawn.stderr | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/rtic-monotonics/src/systick.rs b/rtic-monotonics/src/systick.rs index 1f705db4dbfd..5ef2f32195a9 100644 --- a/rtic-monotonics/src/systick.rs +++ b/rtic-monotonics/src/systick.rs @@ -74,7 +74,7 @@ impl SystickBackend { /// Use the prelude macros instead. pub fn _start(mut systick: SYST, sysclk: u32, timer_hz: u32) { assert!( - (sysclk % timer_hz) == 0, + sysclk.is_multiple_of(timer_hz), "timer_hz cannot evenly divide sysclk! Please adjust the timer or sysclk frequency." ); let reload = sysclk / timer_hz - 1; diff --git a/rtic/ui/task-reference-in-spawn.stderr b/rtic/ui/task-reference-in-spawn.stderr index 38de78c6a2dd..e741a61c4d08 100644 --- a/rtic/ui/task-reference-in-spawn.stderr +++ b/rtic/ui/task-reference-in-spawn.stderr @@ -1,7 +1,7 @@ error[E0521]: borrowed data escapes outside of function --> 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 From dbafce153e0ad857d990bdd2b66a3f7a48e7781b Mon Sep 17 00:00:00 2001 From: Olle Ronstad Date: Mon, 6 Oct 2025 16:18:15 +0200 Subject: [PATCH 6/7] documented changes --- rtic-macros/CHANGELOG.md | 3 +++ rtic-macros/src/codegen/bindings/cortex.rs | 8 ++++++-- rtic-monotonics/CHANGELOG.md | 9 ++++++++- 3 files changed, 17 insertions(+), 3 deletions(-) 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 a73f92271df5..3610506f598f 100644 --- a/rtic-macros/src/codegen/bindings/cortex.rs +++ b/rtic-macros/src/codegen/bindings/cortex.rs @@ -376,7 +376,10 @@ pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::R } // Check that a exception is not used with shared resources - // TODO provide warning when a exception is used with source masking since it then ignores locks in priority ceilings + // 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(){ @@ -390,7 +393,8 @@ pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::R } 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", + "cannot use exceptions with shared resources as hardware tasks when using \ + source masking, consider adding the trampoline attribute", )); } } diff --git a/rtic-monotonics/CHANGELOG.md b/rtic-monotonics/CHANGELOG.md index d8c1c1c04e1f..771853f1d227 100644 --- a/rtic-monotonics/CHANGELOG.md +++ b/rtic-monotonics/CHANGELOG.md @@ -5,10 +5,17 @@ This project adheres to [Semantic Versioning](http://semver.org/). For each category, *Added*, *Changed*, *Fixed* add new entries at the top! -## Unreleased +## [Unreleased] ### Changed + +- changed (sysclk % timer_hz) == 0 to sysclk.is_multiple_of(timer_hz) to make clippy happy as required to CI. + It is unclear why this was not already implemented since the current version does not pass CI. + +### Changed + - Panic if STM32 prescaler value would overflow +- Changed modulo to ``is_multiple_of()``, to make clippy happy ## v2.1.0 - 2025-06-22 From d422879f5d9eb78e8875f21854ebe31f0d2c7d62 Mon Sep 17 00:00:00 2001 From: Olle Ronstad Date: Mon, 6 Oct 2025 16:26:02 +0200 Subject: [PATCH 7/7] more docs --- rtic/CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/rtic/CHANGELOG.md b/rtic/CHANGELOG.md index 03d0a2c8ba18..9d405246d8b2 100644 --- a/rtic/CHANGELOG.md +++ b/rtic/CHANGELOG.md @@ -23,6 +23,7 @@ Example: ### Added - Outer attributes applied to RTIC app module are now forwarded to the generated code. +- Added trampoline functionality ### Changed