pub trait CorePassBackend {
// Required methods
fn post_init(
&self,
app_args: &AppArgs,
app_info: &SubApp,
app_analysis: &SubAnalysis,
) -> Option<TokenStream2>;
fn generate_resource_proxy_lock_impl(
&self,
app_args: &AppArgs,
app_info: &SubApp,
incomplete_lock_fn: ImplItemFn,
) -> ImplItemFn;
fn generate_global_definitions(
&self,
app_args: &AppArgs,
app_info: &SubApp,
app_analysis: &SubAnalysis,
) -> Option<TokenStream2>;
fn wrap_task_execution(
&self,
task_prio: u16,
dispatch_task_call: TokenStream2,
) -> Option<TokenStream2>;
fn entry_name(&self, core: u32) -> Ident;
fn populate_idle_loop(&self) -> Option<TokenStream2>;
fn generate_interrupt_free_fn(&self, empty_body_fn: ItemFn) -> ItemFn;
fn pre_codegen_validation(
&self,
app: &App,
analysis: &Analysis,
) -> Result<()>;
fn default_task_priority(&self) -> u16;
// Provided methods
fn entry_attrs(&self) -> Vec<Attribute> { ... }
fn task_attrs(&self) -> Vec<Attribute> { ... }
}
Expand description
Interface for providing the low-level hardware bindings specific for a target(s) (A.k.a The Backend) to be used during code generation phase of the *Core Compilation Pass.
Required Methods§
sourcefn post_init(
&self,
app_args: &AppArgs,
app_info: &SubApp,
app_analysis: &SubAnalysis,
) -> Option<TokenStream2>
fn post_init( &self, app_args: &AppArgs, app_info: &SubApp, app_analysis: &SubAnalysis, ) -> Option<TokenStream2>
§Setting up the system
Implementation must return the TokenStream to be inserted AFTER the call to Global #[init]
and tasks init() functions,
and BEFORE starting the idle task.
Note that the generated code resulting from the returned TokenStream will be wrapped in a critical section (interrupts are disabled at start and re-enabled at end)
§Use case
This trait method is meant to cover the following use cases:
- enabling interrupt lines used by the application
- setting priority of interrupts, and similar initializations depending on specific hardware details
- multicore systems where a master core needs to wake-up and initialize other cores (see rp2040 distribution as an example)
§Note
This function will be called several times in case of a multicore system, each time with different app_info
and app_analysis
.
§Arguments
app_args
: arguments provided to the #[app(…)] macro attribute, this includes paths to PACs, number of cores…app_info
: Contains the parsed user application. For single core this will be the full application. For multicore, this represents only a sub-application corresponding to a specific core.app_analysis
: Information about the analyzed application. For single core this will be the analysis of the full application. For multicore, this represents the analysis of a sub-application corresponding to a specific core.
sourcefn generate_resource_proxy_lock_impl(
&self,
app_args: &AppArgs,
app_info: &SubApp,
incomplete_lock_fn: ImplItemFn,
) -> ImplItemFn
fn generate_resource_proxy_lock_impl( &self, app_args: &AppArgs, app_info: &SubApp, incomplete_lock_fn: ImplItemFn, ) -> ImplItemFn
§SRP-based Resource locking implementation
The provided method argument incomplete_lock_fn
holds the TokenStream representation of an incomplete function named lock
responsible for locking a distinct resource in the system. The distribution must generate the missing target-specific logic for implementing the locking of that resource.
To illustrate this further with an example, let’s assume the user defined the following shared resources:
// Before code expansion
#[shared]
struct Shared {
pub resource1: R1Type
}
Every field of the shared resources struct has a corresponding autogenerated resource proxy struct that implements the RticMutex
internal trait. as follows:
struct __resource1_mutex {
#[doc(hidden)]
task_priority: u16,
}
impl RticMutex for __resource1_mutex {
type ResourceType = R1Type;
// this is what the trait method argument `incomplete_lock_fn` expands to
fn lock(&mut self, f: impl FnOnce(&mut Self::ResourceType)) {
const CEILING: u16 = 3u16; // resource ceiling
let task_priority = self.task_priority; // current task priority
let resource_ref = unsafe { &mut SHARED.assume_init_mut().resource1 };
/* TODO: THE HARDWARE-SPECIFIC CODE COMES HERE */
}
}
Each time RTIC needs to generate an implementation of the RticMutex
trait for a resource proxy, it calls the method CorePassBackend::generate_resource_proxy_lock_impl to ask the backend to populate the missing details of the [lock] function for that particular resource.
§Contract
-
The returned value representing the populated function must have the same signature as `incomplete_lock_fn’.
-
The implementation must be according to SRP rules such that:
- System interrupt priority ceiling is raised to the value of
CEILING
. - The closure
f
is called andresource_ref
is passed to it as a parameter. (to execute the resource critical section). - System interrupt priority ceiling should be restored back to
task_priority
value.
- System interrupt priority ceiling is raised to the value of
-
If global definitions need to be generated for use in the locking implementation, the trait method which will be described next should be used to cover such need.
§Note
This trait method is called for every shared resource in every sub-application.
§Debugging Tip
Use eprintln("{}", incomplete_lock_fn.to_token_stream().to_string())
to see the incomplete_lock_fn
signature and already provided logic inside it.
sourcefn generate_global_definitions(
&self,
app_args: &AppArgs,
app_info: &SubApp,
app_analysis: &SubAnalysis,
) -> Option<TokenStream2>
fn generate_global_definitions( &self, app_args: &AppArgs, app_info: &SubApp, app_analysis: &SubAnalysis, ) -> Option<TokenStream2>
§Implementation specific pre-computed values and global definitions
When the Implementation requires pre-computed constants, additional global `use’ statements or additional function definitions that must to be accessible from the global application scope, the above trait method could be implemented to return the TokenStream representing those global definitions.
§Example Use case
A practical example could be the RTIC implementation for a cortex M0/M0+ based MCU, where locking is implemented using Interrupt priority masking, where the masks are statically computed values accessible from the global application scope. The masks computation can and should use the information provided by app_args
, app_info
and app_analysis
arguments.
For a real example, see the rp2040 distribution which targets a cortex M0+ based MCU.
§Note
This function will be called several times in case of a multicore system, each time with different app_info
and app_analysis
for each core.
§Arguments
app_args
: arguments provided to the #[app(…)] macro attribute, this includes paths to PACs, number of cores…app_info
: Contains the parsed user application. For single core this will be the full application. For multicore, this represents only a sub-application corresponding to a specific core.app_analysis
: Information about the analyzed application. For single core this will be the analysis of the full application. For multicore, this represents the analysis of a sub-application corresponding to a specific core.
sourcefn wrap_task_execution(
&self,
task_prio: u16,
dispatch_task_call: TokenStream2,
) -> Option<TokenStream2>
fn wrap_task_execution( &self, task_prio: u16, dispatch_task_call: TokenStream2, ) -> Option<TokenStream2>
§Wrapping task execution
In certain cases, some code needs to be executed before and after the [exec] method of a task is called within an interrupt handler. To allow this “wrapping” of the task execution, this trait method can be implemented. The statement from calling the task’s [exec] method has been provided as input to this trait method (dispatch_task_call
), If you return a Some(tokenstream), the returned TokenStream must include/wrap the dispatch_task_call
.
§Example use case
An example could be an implementation for cortex M MCUs that support a BASEPRI register. Every time an interrupt handler id called, the current value of BASEPRI needs to be saved, then task [exec] method is called, then the saved BASEPRI value is restored.
§Arguments
dispatch_task_call
: call to the task [exec] method.
§Contract
The dispatch_task_call
token stream must be placed in between your custom logic. This tokenstream must not be mutated.
sourcefn entry_name(&self, core: u32) -> Ident
fn entry_name(&self, core: u32) -> Ident
§Entry name for a specific core
This trait method allows specifying the name of the entry function on each core.
§Use case
This trait method is especially useful for multicore applications that result in a single output binary (single-binary systems)
where there are multiple entries (one for each core) but only one entry must to be named main
while other entries are given
other unique identifiers.
§Contract
- For single-core and multi-binary multicore distributions, this trait method should always return “main” as the entry name.
- For single-binary multicore distributions, return “main” once only, then different identifiers should be used for other cores entries.
See rp2040 distribution for a real world example.
By default you should implement this function as
fn entry_name(&self, core: u32) -> Ident {
format_ident!("main")
}
sourcefn populate_idle_loop(&self) -> Option<TokenStream2>
fn populate_idle_loop(&self) -> Option<TokenStream2>
§Customizing the default behavior of idle task
When the user doesn’t define an idle task, RTIC automatically defines one with a default implementation.
The [exec] method of this default implementation contains an endless loop. This trait method allows customizing
what is executed inside that loop. For instance, the returned TokenStream can be a call to the wfi
instruction
such that if the idle task is resumed, the device immediately goes to sleep mode and waits for more interrupts to
wake it up.
§Customizing the default behavior of idle task
When the user doesn’t define an idle task, RTIC automatically defines one with a default implementation.
The [exec] method of this default implementation contains an endless loop. This trait method allows customizing
what is executed inside that loop. For instance, the returned TokenStream can be a call to the wfi
instruction
such that if the idle task is resumed, the device immediately goes to sleep mode and waits for more interrupts to
wake it up.
If None is returned, the default implementation of the [exec] method of the idle task will be an empty infinite loop that will only waste cycles !.
sourcefn generate_interrupt_free_fn(&self, empty_body_fn: ItemFn) -> ItemFn
fn generate_interrupt_free_fn(&self, empty_body_fn: ItemFn) -> ItemFn
§Non-preemptable code sections
The RTIC implementation occasionally generates code that must run in a non-preemptable fashion. Therefore, a
distribution must provide the implementation of this trait method to populate the body of empty_body_fn
with
the low-level implementation required to generate the function to be used to execute non-interruptible code
(i.e a traditional critical sections)
The empty_body_fn
argument, is a token stream for a function that expands to the following:
#[inline]
pub fn __rtic_critical_section<F, R>(f: F) -> R
where F: FnOnce() -> R,
{
/* TODO: You need to fill this part here */
}
§Contract
- You MUST not change the function signature (of
empty_body_fn
) - The generated function must re-enable interrupts at end of the critical section.
sourcefn pre_codegen_validation(&self, app: &App, analysis: &Analysis) -> Result<()>
fn pre_codegen_validation(&self, app: &App, analysis: &Analysis) -> Result<()>
§Additional user code validation
Implement this method to validate/analyze the resulting parsed and analyzed user application before the code generation phase starts.
§Use case
In certain cases, some checks/validation related to implementation/hardware specific details need to be made before allowing the user code to be expanded. An example, could be that the user has attempted to use an Exception line as for a dispatcher, but the distribution needs to forbid that. Implementing this trait method, gives the ability to enforcing such checks.
sourcefn default_task_priority(&self) -> u16
fn default_task_priority(&self) -> u16
Implementation must return the default task priority to be used in idle task and tasks when priority argument value is not provided by the user.
Provided Methods§
sourcefn entry_attrs(&self) -> Vec<Attribute>
fn entry_attrs(&self) -> Vec<Attribute>
Attribute macros to add to the entry point
Used often to annotate the runtime entry point for bare metal applications.
§Examples
entry_attrs
allows to add attribute macros to the main entry point, enabling the following
common use case:
#[riscv_rt::entry]
fn main() -> ! {
loop {}
}
sourcefn task_attrs(&self) -> Vec<Attribute>
fn task_attrs(&self) -> Vec<Attribute>
Attribute macros to add to tasks
Used by some implementations to inject custom pre- and postambles to task handlers.
§Examples
task_attrs
allows to add attribute macros to the main entry point, enabling the following
common use case:
#[riscv_rt::interrupt]
fn Uart() {}