rtic_core/backend.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
use super::*;
/// 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*.
pub trait CorePassBackend {
/// # 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.
fn post_init(
&self,
app_args: &AppArgs,
app_info: &SubApp,
app_analysis: &SubAnalysis,
) -> Option<TokenStream2>;
/// # 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:
///
/// ```rust
/// // 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:
///
/// ```rust
/// 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 and `resource_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.
///* 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.
fn generate_resource_proxy_lock_impl(
&self,
app_args: &AppArgs,
app_info: &SubApp,
incomplete_lock_fn: syn::ImplItemFn,
) -> syn::ImplItemFn;
/// # 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.
fn generate_global_definitions(
&self,
app_args: &AppArgs,
app_info: &SubApp,
app_analysis: &SubAnalysis,
) -> 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.
fn wrap_task_execution(
&self,
task_prio: u16, // TODO: more information needs to be provided here to cover more complex cases
dispatch_task_call: TokenStream2,
) -> Option<TokenStream2>;
/// # 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
/// ```rust
/// fn entry_name(&self, core: u32) -> Ident {
/// format_ident!("main")
/// }
/// ```
///
fn entry_name(&self, core: u32) -> Ident;
/// # 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 !.
fn populate_idle_loop(&self) -> Option<TokenStream2>;
/// # 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:
/// ```rust
/// #[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.
fn generate_interrupt_free_fn(&self, empty_body_fn: syn::ItemFn) -> syn::ItemFn;
/// # The "multibin" feature additional requirements
/// If a distribution enables `multibin` feature to allow targeting a multi-binary target, then the distibution must:
/// - re-export microamp crate
/// - implement this trait method to provide te path to the re-exported `shared` attribute macro from microamp crate.
///
/// Example implementation can be
/// ```rust
/// fn multibin_shared_macro_path() -> syn::Path {
/// syn::parse_quote! { rtic::export::microamp::shared }
/// }
///
/// This will be used by RTIC internally to generate the statement:
/// ```rust
/// use rtic::export::microamp::shared as multibin_shared;
///
/// where multibin_shared is the proc macro attribute used to indicate shared data across cores
/// ```
#[cfg(feature = "multibin")]
fn multibin_shared_macro_path(&self) -> syn::Path;
/// # 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.
fn pre_codegen_validation(&self, app: &App, analysis: &Analysis) -> syn::Result<()>;
/// 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.
fn default_task_priority(&self) -> u16;
/// 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:
///
/// ```rust
/// #[riscv_rt::entry]
/// fn main() -> ! {
/// loop {}
/// }
/// ```
fn entry_attrs(&self) -> Vec<syn::Attribute> {
// vec![syn::parse_quote!(#[some_random_attr1]) , syn::parse_quote!(#[some_random_attr2])]
vec![]
}
/// 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:
///
/// ```rust
/// #[riscv_rt::interrupt]
/// fn Uart() {}
/// ```
fn task_attrs(&self) -> Vec<syn::Attribute> {
// vec![syn::parse_quote!(#[some_random_attr1]) , syn::parse_quote!(#[some_random_attr2])]
vec![]
}
}