feat: add display UI with splash screen, dashboard, and RSSI sparkline

Implement full SSD1306 OLED display support:
- Splash screen on boot (1.5s) with project name, version, board
- Dashboard showing radio state, config, counters, RSSI/SNR
- RSSI history sparkline (64-sample ring buffer, 2px-wide bars)
- DisplayOn/DisplayOff host commands routed via dedicated channel
- Pure render functions against generic DrawTarget (no hardware coupling)
- Display on/off control bypasses radio task entirely

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
swaitsandClaude Opus 4.6 committed 2026-03-29 20:42:54 -06:00
1 parent 7064110293
commit 3102fc747a
17624 files changed
+4564928 -38

No files matched your search

+1
View File
@@ -0,0 +1 @@
{"files":{".cargo_vcs_info.json":"f39078dcfb72ad5b76e0230d330b19e4aa52cdbb933daa5dc3ca725b8fed04e7","Cargo.toml":"df7dd42851fe5a0bbfe2b42b9f49e7654c423893e62e7d3d205918ddf44af9cc","Cargo.toml.orig":"3b9c68fdacab869570950f76b545cb13373cf062f05f82bb310ba6cea95b5879","src/lib.rs":"4a2740c7d7e1dc08009889d26863d7003a9a69d559801d6ea3976bff8690ac5a"},"package":"30f19a85fe107b65031e0ba8ec60c34c2494069fe910d6c297f5e7cb5a6f76d0"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "f73533a7d5663fc7ea85d715d828ca9983683761"
},
"path_in_vcs": "riscv-rt/macros"
}
+61
View File
@@ -0,0 +1,61 @@
# THIS FILE IS AUTOMATICALLY GENERATED BY CARGO
#
# When uploading crates to the registry Cargo will automatically
# "normalize" Cargo.toml files for maximal compatibility
# with all versions of Cargo and also rewrite `path` dependencies
# to registry (e.g., crates.io) dependencies.
#
# If you are reading this file be aware that the original Cargo.toml
# will likely look very different (and much more reasonable).
# See Cargo.toml.orig for the original contents.
[package]
edition = "2021"
name = "riscv-rt-macros"
version = "0.2.2"
authors = [
"The RISC-V Team <risc-v@teams.rust-embedded.org>",
"Jorge Aparicio <jorge@japaric.io>",
]
build = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Attributes re-exported in `riscv-rt`"
documentation = "https://docs.rs/riscv-rt"
readme = false
keywords = [
"riscv",
"runtime",
"startup",
]
categories = [
"embedded",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/rust-embedded/riscv"
[lib]
name = "riscv_rt_macros"
path = "src/lib.rs"
proc-macro = true
[dependencies.proc-macro2]
version = "1.0"
[dependencies.quote]
version = "1.0"
[dependencies.syn]
version = "2.0"
features = [
"extra-traits",
"full",
]
[features]
s-mode = []
u-boot = []
v-trap = []
+804
View File
@@ -0,0 +1,804 @@
#![deny(warnings)]
extern crate core;
extern crate proc_macro;
extern crate proc_macro2;
extern crate quote;
extern crate syn;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::quote;
use syn::{
parse::{self, Parse},
parse_macro_input, parse_quote,
punctuated::Punctuated,
spanned::Spanned,
FnArg, ItemFn, LitInt, LitStr, PatType, Path, ReturnType, Token, Type, Visibility,
};
use proc_macro::TokenStream;
/// Attribute to declare the entry point of the program
///
/// **IMPORTANT**: This attribute must appear exactly *once* in the dependency graph. Also, if you
/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
/// private modules between the item and the root of the crate); if the item is in the root of the
/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer releases.
///
/// The specified function will be called by the reset handler *after* RAM has been initialized.
/// If present, the FPU will also be enabled before the function is called.
///
/// The type of the specified function must be `[unsafe] fn() -> !` (never ending function)
///
/// # Properties
///
/// The entry point will be called by the reset handler. The program can't reference to the entry
/// point, much less invoke it.
///
/// # Examples
///
/// - Simple entry point
///
/// ``` no_run
/// # #![no_main]
/// # use riscv_rt_macros::entry;
/// #[entry]
/// fn main() -> ! {
/// loop {
/// /* .. */
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
#[cfg(not(feature = "u-boot"))]
let arguments_limit = 3;
#[cfg(feature = "u-boot")]
let arguments_limit = 2;
// check the function arguments
if f.sig.inputs.len() > arguments_limit {
return parse::Error::new(
f.sig.inputs.last().unwrap().span(),
"`#[entry]` function has too many arguments",
)
.to_compile_error()
.into();
}
fn check_correct_type(argument: &PatType, ty: &str) -> Option<TokenStream> {
let inv_type_message = format!("argument type must be {}", ty);
if !is_correct_type(&argument.ty, ty) {
let error = parse::Error::new(argument.ty.span(), inv_type_message);
Some(error.to_compile_error().into())
} else {
None
}
}
fn check_argument_type(argument: &FnArg, ty: &str) -> Option<TokenStream> {
let argument_error = parse::Error::new(argument.span(), "invalid argument");
let argument_error = argument_error.to_compile_error().into();
match argument {
FnArg::Typed(argument) => check_correct_type(argument, ty),
FnArg::Receiver(_) => Some(argument_error),
}
}
#[cfg(not(feature = "u-boot"))]
for argument in f.sig.inputs.iter() {
if let Some(message) = check_argument_type(argument, "usize") {
return message;
};
}
#[cfg(feature = "u-boot")]
if let Some(argument) = f.sig.inputs.get(0) {
if let Some(message) = check_argument_type(argument, "c_int") {
return message;
}
}
#[cfg(feature = "u-boot")]
if let Some(argument) = f.sig.inputs.get(1) {
if let Some(message) = check_argument_type(argument, "*const *const c_char") {
return message;
}
}
// check the function signature
let valid_signature = f.sig.constness.is_none()
&& f.sig.asyncness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.abi.is_none()
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
&& f.sig.variadic.is_none()
&& match f.sig.output {
ReturnType::Default => false,
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
};
if !valid_signature {
return parse::Error::new(
f.span(),
"`#[entry]` function must have signature `[unsafe] fn([arg0: usize, ...]) -> !`",
)
.to_compile_error()
.into();
}
if !args.is_empty() {
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
.to_compile_error()
.into();
}
// XXX should we blacklist other attributes?
let attrs = f.attrs;
let unsafety = f.sig.unsafety;
let args = f.sig.inputs;
let stmts = f.block.stmts;
quote!(
#[allow(non_snake_case)]
#[export_name = "main"]
#(#attrs)*
pub #unsafety fn __risc_v_rt__main(#args) -> ! {
#(#stmts)*
}
)
.into()
}
fn strip_type_path(ty: &Type) -> Option<Type> {
match ty {
Type::Ptr(ty) => {
let mut ty = ty.clone();
ty.elem = Box::new(strip_type_path(&ty.elem)?);
Some(Type::Ptr(ty))
}
Type::Path(ty) => {
let mut ty = ty.clone();
let last_segment = ty.path.segments.last().unwrap().clone();
ty.path.segments = Punctuated::new();
ty.path.segments.push_value(last_segment);
Some(Type::Path(ty))
}
_ => None,
}
}
#[allow(unused)]
fn is_correct_type(ty: &Type, name: &str) -> bool {
let correct: Type = syn::parse_str(name).unwrap();
if let Some(ty) = strip_type_path(ty) {
ty == correct
} else {
false
}
}
/// Attribute to mark which function will be called at the beginning of the reset handler.
///
/// **IMPORTANT**: This attribute can appear at most *once* in the dependency graph. Also, if you
/// are using Rust 1.30 the attribute must be used on a reachable item (i.e. there must be no
/// private modules between the item and the root of the crate); if the item is in the root of the
/// crate you'll be fine. This reachability restriction doesn't apply to Rust 1.31 and newer
/// releases.
///
/// The function must have the signature of `unsafe fn()`.
///
/// The function passed will be called before static variables are initialized. Any access of static
/// variables will result in undefined behavior.
///
/// # Examples
///
/// ```
/// # use riscv_rt_macros::pre_init;
/// #[pre_init]
/// unsafe fn before_main() {
/// // do something here
/// }
///
/// # fn main() {}
/// ```
#[proc_macro_attribute]
pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
// check the function signature
let valid_signature = f.sig.constness.is_none()
&& f.sig.asyncness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.unsafety.is_some()
&& f.sig.abi.is_none()
&& f.sig.inputs.is_empty()
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
&& f.sig.variadic.is_none()
&& match f.sig.output {
ReturnType::Default => true,
ReturnType::Type(_, ref ty) => match **ty {
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
_ => false,
},
};
if !valid_signature {
return parse::Error::new(
f.span(),
"`#[pre_init]` function must have signature `unsafe fn()`",
)
.to_compile_error()
.into();
}
if !args.is_empty() {
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
.to_compile_error()
.into();
}
// XXX should we blacklist other attributes?
let attrs = f.attrs;
let ident = f.sig.ident;
let block = f.block;
quote!(
#[export_name = "__pre_init"]
#(#attrs)*
pub unsafe fn #ident() #block
)
.into()
}
struct AsmLoopArgs {
asm_template: String,
count_from: usize,
count_to: usize,
}
impl Parse for AsmLoopArgs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let template: LitStr = input.parse().unwrap();
_ = input.parse::<Token![,]>().unwrap();
let count: LitInt = input.parse().unwrap();
if input.parse::<Token![,]>().is_ok() {
let count_to: LitInt = input.parse().unwrap();
Ok(Self {
asm_template: template.value(),
count_from: count.base10_parse().unwrap(),
count_to: count_to.base10_parse().unwrap(),
})
} else {
Ok(Self {
asm_template: template.value(),
count_from: 0,
count_to: count.base10_parse().unwrap(),
})
}
}
}
/// Loops an asm expression n times.
///
/// `loop_asm!` takes 2 or 3 arguments, the first is a string literal and the rest are a number literal
/// See [the formatting syntax documentation in `std::fmt`](../std/fmt/index.html) for details.
///
/// Argument 1 is an assembly expression, all "{}" in this assembly expression will be replaced with the
/// current loop index.
///
/// If 2 arguments are provided, the loop will start at 0 and end at the number provided in argument 2.
///
/// If 3 arguments are provided, the loop will start at the number provided in argument 2 and end at
/// the number provided in argument 3.
///
/// # Examples
///
/// ```
/// # use riscv_rt_macros::loop_asm;
/// unsafe {
/// loop_asm!("fmv.w.x f{}, x0", 32); // => core::arch::asm!("fmv.w.x f0, x0") ... core::arch::asm!("fmv.w.x f31, x0")
/// loop_asm!("fmv.w.x f{}, x0", 1, 32); // => core::arch::asm!("fmv.w.x f1, x0") ... core::arch::asm!("fmv.w.x f31, x0")
/// }
/// ```
#[proc_macro]
pub fn loop_asm(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as AsmLoopArgs);
let tokens = (args.count_from..args.count_to)
.map(|i| {
let i = i.to_string();
let asm = args.asm_template.replace("{}", &i);
format!("core::arch::asm!(\"{}\");", asm)
})
.collect::<Vec<String>>()
.join("\n");
tokens.parse().unwrap()
}
/// Loops a global_asm expression n times.
///
/// `loop_global_asm!` takes 2 or 3 arguments, the first is a string literal and the rest are a number literal
/// See [the formatting syntax documentation in `std::fmt`](../std/fmt/index.html) for details.
///
/// Argument 1 is an assembly expression, all "{}" in this assembly expression will be replaced with the
/// current loop index.
///
/// If 2 arguments are provided, the loop will start at 0 and end at the number provided in argument 2.
///
/// If 3 arguments are provided, the loop will start at the number provided in argument 2 and end at
/// the number provided in argument 3.
///
/// # Examples
///
/// ```
/// # use riscv_rt_macros::loop_global_asm;
/// unsafe {
/// loop_global_asm!("fmv.w.x f{}, x0", 32); // => core::arch::global_asm!("fmv.w.x f0, x0") ... core::arch::global_asm!("fmv.w.x f31, x0")
/// loop_global_asm!("fmv.w.x f{}, x0", 1, 32); // => core::arch::global_asm!("fmv.w.x f1, x0") ... core::arch::global_asm!("fmv.w.x f31, x0")
/// }
/// ```
#[proc_macro]
pub fn loop_global_asm(input: TokenStream) -> TokenStream {
let args = parse_macro_input!(input as AsmLoopArgs);
let instructions = (args.count_from..args.count_to)
.map(|i| {
let i = i.to_string();
args.asm_template.replace("{}", &i)
})
.collect::<Vec<String>>()
.join("\n");
let res = format!("core::arch::global_asm!(\n\"{}\"\n);", instructions);
res.parse().unwrap()
}
#[derive(Clone, Copy, Debug)]
enum RiscvArch {
Rv32,
Rv64,
}
impl RiscvArch {
fn width(&self) -> usize {
match self {
Self::Rv32 => 4,
Self::Rv64 => 8,
}
}
fn store(&self) -> &str {
match self {
Self::Rv32 => "sw",
Self::Rv64 => "sd",
}
}
fn load(&self) -> &str {
match self {
Self::Rv32 => "lw",
Self::Rv64 => "ld",
}
}
}
/// Size of the trap frame (in number of registers)
const TRAP_SIZE: usize = 16;
#[rustfmt::skip]
/// List of the register names to be stored in the trap frame
const TRAP_FRAME: [&str; TRAP_SIZE] = [
"ra",
"t0",
"t1",
"t2",
"t3",
"t4",
"t5",
"t6",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
];
/// Generate the assembly instructions to store the trap frame.
///
/// The `arch` parameter is used to determine the width of the registers.
///
/// The `filter` function is used to filter which registers to store.
/// This is useful to optimize the binary size in vectored interrupt mode, which divides the trap
/// frame storage in two parts: the first part saves space in the stack and stores only the `a0` register,
/// while the second part stores the remaining registers.
fn store_trap<T: FnMut(&str) -> bool>(arch: RiscvArch, mut filter: T) -> String {
let width = arch.width();
let store = arch.store();
TRAP_FRAME
.iter()
.enumerate()
.filter(|(_, &reg)| filter(reg))
.map(|(i, reg)| format!("{store} {reg}, {i}*{width}(sp)"))
.collect::<Vec<_>>()
.join("\n")
}
/// Generate the assembly instructions to load the trap frame.
/// The `arch` parameter is used to determine the width of the registers.
fn load_trap(arch: RiscvArch) -> String {
let width = arch.width();
let load = arch.load();
TRAP_FRAME
.iter()
.enumerate()
.map(|(i, reg)| format!("{load} {reg}, {i}*{width}(sp)"))
.collect::<Vec<_>>()
.join("\n")
}
/// Generates weak `_start_trap` function in assembly for RISCV-32 targets.
///
/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`.
/// The trap frame is allocated on the stack and deallocated after the call.
#[proc_macro]
pub fn weak_start_trap_riscv32(_input: TokenStream) -> TokenStream {
weak_start_trap(RiscvArch::Rv32)
}
/// Generates weak `_start_trap` function in assembly for RISCV-64 targets.
///
/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`.
/// The trap frame is allocated on the stack and deallocated after the call.
#[proc_macro]
pub fn weak_start_trap_riscv64(_input: TokenStream) -> TokenStream {
weak_start_trap(RiscvArch::Rv64)
}
/// Generates weak `_start_trap` function in assembly.
///
/// This implementation stores all registers in the trap frame and calls `_start_trap_rust`.
/// The trap frame is allocated on the stack and deallocated after the call.
///
/// The `arch` parameter is used to determine the width of the registers.
/// The macro also ensures that the trap frame size is 16-byte aligned.
fn weak_start_trap(arch: RiscvArch) -> TokenStream {
let width = arch.width();
// ensure we do not break that sp is 16-byte aligned
if (TRAP_SIZE * width) % 16 != 0 {
return parse::Error::new(Span::call_site(), "Trap frame size must be 16-byte aligned")
.to_compile_error()
.into();
}
let store = store_trap(arch, |_| true);
let load = load_trap(arch);
#[cfg(feature = "s-mode")]
let ret = "sret";
#[cfg(not(feature = "s-mode"))]
let ret = "mret";
format!(
r#"
core::arch::global_asm!(
".section .trap, \"ax\"
.align {width}
.weak _start_trap
_start_trap:
addi sp, sp, - {TRAP_SIZE} * {width}
{store}
add a0, sp, zero
jal ra, _start_trap_rust
{load}
addi sp, sp, {TRAP_SIZE} * {width}
{ret}
");"#
)
.parse()
.unwrap()
}
/// Generates vectored interrupt trap functions in assembly for RISCV-32 targets.
#[cfg(feature = "v-trap")]
#[proc_macro]
pub fn vectored_interrupt_trap_riscv32(_input: TokenStream) -> TokenStream {
vectored_interrupt_trap(RiscvArch::Rv32)
}
/// Generates vectored interrupt trap functions in assembly for RISCV-64 targets.
#[cfg(feature = "v-trap")]
#[proc_macro]
pub fn vectored_interrupt_trap_riscv64(_input: TokenStream) -> TokenStream {
vectored_interrupt_trap(RiscvArch::Rv64)
}
#[cfg(feature = "v-trap")]
/// Generates global '_start_DefaultHandler_trap' and '_continue_interrupt_trap' functions in assembly.
/// The '_start_DefaultHandler_trap' function stores the trap frame partially (only register a0) and
/// jumps to the interrupt handler. The '_continue_interrupt_trap' function stores the trap frame
/// partially (all registers except a0), jumps to the interrupt handler, and restores the trap frame.
fn vectored_interrupt_trap(arch: RiscvArch) -> TokenStream {
let width = arch.width();
let store_start = store_trap(arch, |reg| reg == "a0");
let store_continue = store_trap(arch, |reg| reg != "a0");
let load = load_trap(arch);
#[cfg(feature = "s-mode")]
let ret = "sret";
#[cfg(not(feature = "s-mode"))]
let ret = "mret";
let instructions = format!(
r#"
core::arch::global_asm!(
".section .trap, \"ax\"
.align 4
.global _start_DefaultHandler_trap
_start_DefaultHandler_trap:
addi sp, sp, -{TRAP_SIZE} * {width} // allocate space for trap frame
{store_start} // store trap partially (only register a0)
la a0, DefaultHandler // load interrupt handler address into a0
.align 4
.global _continue_interrupt_trap
_continue_interrupt_trap:
{store_continue} // store trap partially (all registers except a0)
jalr ra, a0, 0 // jump to corresponding interrupt handler (address stored in a0)
{load} // restore trap frame
addi sp, sp, {TRAP_SIZE} * {width} // deallocate space for trap frame
{ret} // return from interrupt
");"#
);
instructions.parse().unwrap()
}
#[derive(Clone, Copy, Debug)]
enum RiscvPacItem {
Exception,
ExternalInterrupt,
CoreInterrupt,
}
impl RiscvPacItem {
fn macro_id(&self) -> &str {
match self {
Self::Exception => "exception",
Self::ExternalInterrupt => "external_interrupt",
Self::CoreInterrupt => "core_interrupt",
}
}
fn valid_signature(&self) -> &str {
match self {
Self::Exception => "`[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]`",
_ => "`[unsafe] fn() [-> !]`",
}
}
fn check_signature(&self, f: &ItemFn) -> bool {
let valid_args = match self {
Self::Exception => {
if f.sig.inputs.len() > 1 {
return false;
}
match f.sig.inputs.first() {
Some(FnArg::Typed(t)) => {
let first_param_type = *t.ty.clone();
let expected_types: Vec<Type> = vec![
parse_quote!(&riscv_rt::TrapFrame),
parse_quote!(&mut riscv_rt::TrapFrame),
];
expected_types.iter().any(|t| first_param_type == *t)
}
Some(_) => false,
None => true,
}
}
_ => f.sig.inputs.is_empty(),
};
valid_args
&& f.sig.constness.is_none()
&& f.sig.asyncness.is_none()
&& f.vis == Visibility::Inherited
&& f.sig.abi.is_none()
&& f.sig.generics.params.is_empty()
&& f.sig.generics.where_clause.is_none()
&& f.sig.variadic.is_none()
&& match f.sig.output {
ReturnType::Default => true,
ReturnType::Type(_, ref ty) => matches!(**ty, Type::Never(_)),
}
}
fn impl_trait(&self) -> TokenStream2 {
match self {
Self::Exception => quote! { riscv_rt::ExceptionNumber },
Self::ExternalInterrupt => quote! { riscv_rt::ExternalInterruptNumber },
Self::CoreInterrupt => quote! { riscv_rt::CoreInterruptNumber },
}
}
}
#[proc_macro_attribute]
/// Attribute to declare an exception handler.
///
/// The function must have the signature `[unsafe] fn([&[mut] riscv_rt::TrapFrame]) [-> !]`.
///
/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::ExceptionNumber` trait.
///
/// # Example
///
/// ``` ignore,no_run
/// #[riscv_rt::exception(riscv::interrupt::Exception::LoadMisaligned)]
/// fn load_misaligned(trap_frame: &mut riscv_rt::TrapFrame) -> ! {
/// loop{};
/// }
/// ```
pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
trap(args, input, RiscvPacItem::Exception, None)
}
#[proc_macro_attribute]
/// Attribute to declare a core interrupt handler.
///
/// The function must have the signature `[unsafe] fn() [-> !]`.
///
/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait.
///
/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly.
///
/// # Example
///
/// ``` ignore,no_run
/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)]
/// fn supervisor_soft() -> ! {
/// loop{};
/// }
/// ```
pub fn core_interrupt_riscv32(args: TokenStream, input: TokenStream) -> TokenStream {
let arch = match () {
#[cfg(feature = "v-trap")]
() => Some(RiscvArch::Rv32),
#[cfg(not(feature = "v-trap"))]
() => None,
};
trap(args, input, RiscvPacItem::CoreInterrupt, arch)
}
#[proc_macro_attribute]
/// Attribute to declare a core interrupt handler.
///
/// The function must have the signature `[unsafe] fn() [-> !]`.
///
/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::CoreInterruptNumber` trait.
///
/// If the `v-trap` feature is enabled, this macro generates the corresponding interrupt trap handler in assembly.
///
/// # Example
///
/// ``` ignore,no_run
/// #[riscv_rt::core_interrupt(riscv::interrupt::Interrupt::SupervisorSoft)]
/// fn supervisor_soft() -> ! {
/// loop{};
/// }
/// ```
pub fn core_interrupt_riscv64(args: TokenStream, input: TokenStream) -> TokenStream {
let arch = match () {
#[cfg(feature = "v-trap")]
() => Some(RiscvArch::Rv64),
#[cfg(not(feature = "v-trap"))]
() => None,
};
trap(args, input, RiscvPacItem::CoreInterrupt, arch)
}
#[proc_macro_attribute]
/// Attribute to declare an external interrupt handler.
///
/// The function must have the signature `[unsafe] fn() [-> !]`.
///
/// The argument of the macro must be a path to a variant of an enum that implements the `riscv_rt::ExternalInterruptNumber` trait.
///
/// # Example
///
/// ``` ignore,no_run
/// #[riscv_rt::external_interrupt(e310x::interrupt::Interrupt::GPIO0)]
/// fn gpio0() -> ! {
/// loop{};
/// }
/// ```
pub fn external_interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
trap(args, input, RiscvPacItem::ExternalInterrupt, None)
}
fn trap(
args: TokenStream,
input: TokenStream,
pac_item: RiscvPacItem,
arch: Option<RiscvArch>,
) -> TokenStream {
let f = parse_macro_input!(input as ItemFn);
if !pac_item.check_signature(&f) {
let msg = format!(
"`#[{}]` function must have signature {}",
pac_item.macro_id(),
pac_item.valid_signature()
);
return parse::Error::new(f.sig.span(), msg)
.to_compile_error()
.into();
}
if args.is_empty() {
let msg = format!(
"`#[{}]` attribute expects a path to a variant of an enum that implements the {} trait.",
pac_item.macro_id(),
pac_item.impl_trait()
);
return parse::Error::new(Span::call_site(), msg)
.to_compile_error()
.into();
}
let int_path = parse_macro_input!(args as Path);
let int_ident = &int_path.segments.last().unwrap().ident;
let export_name = format!("{:#}", int_ident);
let start_trap = match arch {
Some(arch) => {
let trap = start_interrupt_trap(int_ident, arch);
quote! {
#[cfg(any(target_arch = "riscv32", target_arch = "riscv64"))]
#trap
}
}
None => proc_macro2::TokenStream::new(),
};
let pac_trait = pac_item.impl_trait();
quote!(
// Compile-time check to ensure the trap path implements the trap trait
const _: fn() = || {
fn assert_impl<T: #pac_trait>(_arg: T) {}
assert_impl(#int_path);
};
#start_trap
#[export_name = #export_name]
#f
)
.into()
}
fn start_interrupt_trap(ident: &syn::Ident, arch: RiscvArch) -> proc_macro2::TokenStream {
let interrupt = ident.to_string();
let width = arch.width();
let store = store_trap(arch, |r| r == "a0");
let instructions = format!(
r#"
core::arch::global_asm!(
".section .trap, \"ax\"
.align 2
.global _start_{interrupt}_trap
_start_{interrupt}_trap:
addi sp, sp, -{TRAP_SIZE} * {width} // allocate space for trap frame
{store} // store trap partially (only register a0)
la a0, {interrupt} // load interrupt handler address into a0
j _continue_interrupt_trap // jump to common part of interrupt trap
");"#
);
instructions.parse().unwrap()
}