Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions ci/expected/lm3s6965/trampoline.run
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
gpioa lock
gpioa unlock
SysTick start
idle
SysTick start
idle end
65 changes: 65 additions & 0 deletions examples/lm3s6965/examples/trampoline.rs
Original file line number Diff line number Diff line change
@@ -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
Comment on lines +1 to +11
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nitpick: Filename and comment are disagreeing :)

#[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");
}
}
3 changes: 3 additions & 0 deletions rtic-macros/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
60 changes: 55 additions & 5 deletions rtic-macros/src/codegen/bindings/cortex.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand Down Expand Up @@ -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"
);
Expand Down Expand Up @@ -319,13 +344,13 @@ pub fn architecture_specific_analysis(app: &App, _: &SyntaxAnalysis) -> parse::R
.filter(|prio| *prio > 0)
.collect::<HashSet<_>>();

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})"
)
};

Expand All @@ -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(())
}

Expand Down
79 changes: 59 additions & 20 deletions rtic-macros/src/codegen/hardware_tasks.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use crate::codegen::util;
use crate::syntax::{ast::App, Context};
use crate::{
analyze::Analysis,
Expand All @@ -23,35 +24,74 @@ 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() {
let (item, constructor) =
local_resources_struct::codegen(Context::HardwareTask(name), app);

root.push(item);

mod_app.push(constructor);
}

Expand All @@ -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);
}

Expand Down
3 changes: 3 additions & 0 deletions rtic-macros/src/syntax/ast.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Ident>,

/// The priority of this task
pub priority: u8,

Expand Down
22 changes: 19 additions & 3 deletions rtic-macros/src/syntax/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
}

let mut binds = None;
let mut trampoline = None;
let mut priority = None;
let mut shared_resources = None;
let mut local_resources = None;
Expand Down Expand Up @@ -223,7 +224,21 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
let ident = input.parse()?;

binds = Some(ident);
}
},

"trampoline" => {
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() {
Expand Down Expand Up @@ -277,8 +292,8 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
local_resources = Some(util::parse_local_resources(input)?);
}

_ => {
return Err(parse::Error::new(ident.span(), "unexpected argument"));
a => {
return Err(parse::Error::new(ident.span(), format!("unexpected argument {}", a)));
}
}

Expand Down Expand Up @@ -308,6 +323,7 @@ fn task_args(tokens: TokenStream2) -> parse::Result<Either<HardwareTaskArgs, Sof
priority,
shared_resources,
local_resources,
trampoline,
})
} else {
// Software tasks start at idle priority
Expand Down
9 changes: 8 additions & 1 deletion rtic-monotonics/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CI always uses latest stable Rust, so new versions bring in new lints/defaults all the time :)

Thank you for dealing with this, could you split out the 'rtic-monotonics' changes into a separate commit? If you want you could even create a separate PR for this, to help your colleague, that can be fast-tracked into master branch that way :)

Great you added to the CHANGELOG, but please add new entries above, building like a stack. Please cleanup the commentary and the double ### Changed headers while you're at it :P

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

Expand Down
2 changes: 1 addition & 1 deletion rtic-monotonics/src/systick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
1 change: 1 addition & 0 deletions rtic/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ Example:
### Added

- Outer attributes applied to RTIC app module are now forwarded to the generated code.
- Added trampoline functionality

### Changed

Expand Down
2 changes: 1 addition & 1 deletion rtic/ui/task-reference-in-spawn.stderr
Original file line number Diff line number Diff line change
@@ -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
Expand Down