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

+341
View File
@@ -0,0 +1,341 @@
#![allow(rustdoc::bare_urls, unused_macros)]
#![cfg_attr(target_arch = "xtensa", feature(asm_experimental_arch))]
#![doc = include_str!("../README.md")]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![no_std]
#[cfg(feature = "defmt")]
use defmt as _;
#[cfg(feature = "println")]
use esp_println as _;
const MAX_BACKTRACE_ADDRESSES: usize = 10;
#[cfg(feature = "colors")]
const RESET: &str = "\u{001B}[0m";
#[cfg(feature = "colors")]
const RED: &str = "\u{001B}[31m";
#[cfg(feature = "defmt")]
macro_rules! println {
("") => {
// Do nothing if the string is just a space
};
($($arg:tt)*) => {
defmt::error!($($arg)*);
};
}
#[cfg(all(feature = "println", not(feature = "defmt")))]
macro_rules! println {
($($arg:tt)*) => {
esp_println::println!($($arg)*);
};
}
#[allow(unused, unused_variables)]
fn set_color_code(code: &str) {
#[cfg(feature = "println")]
{
println!("{}", code);
}
}
#[cfg_attr(target_arch = "riscv32", path = "riscv.rs")]
#[cfg_attr(target_arch = "xtensa", path = "xtensa.rs")]
pub mod arch;
#[cfg(feature = "panic-handler")]
#[panic_handler]
fn panic_handler(info: &core::panic::PanicInfo) -> ! {
pre_backtrace();
#[cfg(feature = "colors")]
set_color_code(RED);
println!("");
println!("====================== PANIC ======================");
#[cfg(not(feature = "defmt"))]
println!("{}", info);
#[cfg(feature = "defmt")]
println!("{}", defmt::Display2Format(info));
println!("");
println!("Backtrace:");
println!("");
let backtrace = crate::arch::backtrace();
#[cfg(target_arch = "riscv32")]
if backtrace.iter().filter(|e| e.is_some()).count() == 0 {
println!("No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)");
}
for addr in backtrace.into_iter().flatten() {
#[cfg(all(feature = "colors", feature = "println"))]
println!("{}0x{:x}", RED, addr - crate::arch::RA_OFFSET);
#[cfg(not(all(feature = "colors", feature = "println")))]
println!("0x{:x}", addr - crate::arch::RA_OFFSET);
}
#[cfg(feature = "colors")]
set_color_code(RESET);
#[cfg(feature = "semihosting")]
semihosting::process::abort();
#[cfg(not(feature = "semihosting"))]
halt();
}
#[cfg(all(feature = "exception-handler", target_arch = "xtensa"))]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe fn __user_exception(cause: arch::ExceptionCause, context: arch::Context) {
pre_backtrace();
#[cfg(feature = "colors")]
set_color_code(RED);
// Unfortunately, a different formatter string is used
#[cfg(not(feature = "defmt"))]
esp_println::println!("\n\nException occurred '{}'", cause);
#[cfg(feature = "defmt")]
defmt::error!("\n\nException occurred '{}'", cause);
println!("{:?}", context);
let backtrace = crate::arch::backtrace_internal(context.A1, 0);
for e in backtrace {
if let Some(addr) = e {
println!("0x{:x}", addr);
}
}
println!("");
println!("");
println!("");
#[cfg(feature = "colors")]
set_color_code(RESET);
#[cfg(feature = "semihosting")]
semihosting::process::abort();
#[cfg(not(feature = "semihosting"))]
halt();
}
#[cfg(all(feature = "exception-handler", target_arch = "riscv32"))]
#[export_name = "ExceptionHandler"]
fn exception_handler(context: &arch::TrapFrame) -> ! {
pre_backtrace();
let mepc = context.pc;
let code = context.mcause & 0xff;
let mtval = context.mtval;
#[cfg(feature = "colors")]
set_color_code(RED);
if code == 14 {
println!("");
println!(
"Stack overflow detected at 0x{:x} called by 0x{:x}",
mepc, context.ra
);
println!("");
} else {
let code = match code {
0 => "Instruction address misaligned",
1 => "Instruction access fault",
2 => "Illegal instruction",
3 => "Breakpoint",
4 => "Load address misaligned",
5 => "Load access fault",
6 => "Store/AMO address misaligned",
7 => "Store/AMO access fault",
8 => "Environment call from U-mode",
9 => "Environment call from S-mode",
10 => "Reserved",
11 => "Environment call from M-mode",
12 => "Instruction page fault",
13 => "Load page fault",
14 => "Reserved",
15 => "Store/AMO page fault",
_ => "UNKNOWN",
};
println!(
"Exception '{}' mepc=0x{:08x}, mtval=0x{:08x}",
code, mepc, mtval
);
#[cfg(not(feature = "defmt"))]
println!("{:x?}", context);
#[cfg(feature = "defmt")]
println!("{:?}", context);
let backtrace = crate::arch::backtrace_internal(context.s0 as u32, 0);
if backtrace.iter().filter(|e| e.is_some()).count() == 0 {
println!("No backtrace available - make sure to force frame-pointers. (see https://crates.io/crates/esp-backtrace)");
}
for addr in backtrace.into_iter().flatten() {
#[cfg(all(feature = "colors", feature = "println"))]
println!("{}0x{:x}", RED, addr - crate::arch::RA_OFFSET);
#[cfg(not(all(feature = "colors", feature = "println")))]
println!("0x{:x}", addr - crate::arch::RA_OFFSET);
}
}
println!("");
println!("");
println!("");
#[cfg(feature = "colors")]
set_color_code(RESET);
#[cfg(feature = "semihosting")]
semihosting::process::abort();
#[cfg(not(feature = "semihosting"))]
halt();
}
// Ensure that the address is in DRAM and that it is 16-byte aligned.
//
// Based loosely on the `esp_stack_ptr_in_dram` function from
// `components/esp_hw_support/include/esp_memory_utils.h` in ESP-IDF.
//
// Address ranges can be found in `components/soc/$CHIP/include/soc/soc.h` as
// `SOC_DRAM_LOW` and `SOC_DRAM_HIGH`.
fn is_valid_ram_address(address: u32) -> bool {
if (address & 0xF) != 0 {
return false;
}
#[cfg(feature = "esp32")]
if !(0x3FFA_E000..=0x4000_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32c2")]
if !(0x3FCA_0000..=0x3FCE_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32c3")]
if !(0x3FC8_0000..=0x3FCE_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32c6")]
if !(0x4080_0000..=0x4088_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32h2")]
if !(0x4080_0000..=0x4085_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32p4")]
if !(0x4FF0_0000..=0x4FFC_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32s2")]
if !(0x3FFB_0000..=0x4000_0000).contains(&address) {
return false;
}
#[cfg(feature = "esp32s3")]
if !(0x3FC8_8000..=0x3FD0_0000).contains(&address) {
return false;
}
true
}
#[cfg(all(
any(
not(any(feature = "esp32", feature = "esp32p4", feature = "esp32s3")),
not(feature = "halt-cores")
),
not(feature = "custom-halt")
))]
#[allow(unused)]
fn halt() -> ! {
loop {
continue;
}
}
#[cfg(feature = "custom-halt")]
fn halt() -> ! {
extern "Rust" {
fn custom_halt() -> !;
}
unsafe { custom_halt() }
}
// TODO: Enable `halt` function for `esp32p4` feature once implemented
#[cfg(all(any(feature = "esp32", feature = "esp32s3"), feature = "halt-cores"))]
#[allow(unused)]
fn halt() -> ! {
#[cfg(feature = "esp32")]
mod registers {
pub(crate) const OPTIONS0: u32 = 0x3ff48000;
pub(crate) const SW_CPU_STALL: u32 = 0x3ff480ac;
}
#[cfg(feature = "esp32p4")]
mod registers {
pub(crate) const SW_CPU_STALL: u32 = 0x50115200;
}
#[cfg(feature = "esp32s3")]
mod registers {
pub(crate) const OPTIONS0: u32 = 0x60008000;
pub(crate) const SW_CPU_STALL: u32 = 0x600080bc;
}
let sw_cpu_stall = registers::SW_CPU_STALL as *mut u32;
#[cfg(feature = "esp32p4")]
unsafe {}
#[cfg(not(feature = "esp32p4"))]
unsafe {
// We need to write the value "0x86" to stall a particular core. The write
// location is split into two separate bit fields named "c0" and "c1", and the
// two fields are located in different registers. Each core has its own pair of
// "c0" and "c1" bit fields.
let options0 = registers::OPTIONS0 as *mut u32;
options0.write_volatile(options0.read_volatile() & !(0b1111) | 0b1010);
sw_cpu_stall.write_volatile(
sw_cpu_stall.read_volatile() & !(0b111111 << 20) & !(0b111111 << 26)
| (0x21 << 20)
| (0x21 << 26),
);
}
loop {}
}
#[cfg(not(feature = "custom-pre-backtrace"))]
#[allow(unused)]
fn pre_backtrace() {}
#[cfg(feature = "custom-pre-backtrace")]
fn pre_backtrace() {
extern "Rust" {
fn custom_pre_backtrace();
}
unsafe { custom_pre_backtrace() }
}
+219
View File
@@ -0,0 +1,219 @@
use core::arch::asm;
use crate::MAX_BACKTRACE_ADDRESSES;
// subtract 4 from the return address
// the return address is the address following the JALR
// we get better results (especially if the caller was the last instruction in
// the calling function) if we report the address of the JALR itself
// even if it was a C.JALR we should get good results using RA - 4
#[allow(unused)]
pub(super) const RA_OFFSET: usize = 4;
/// Registers saved in trap handler
#[doc(hidden)]
#[derive(Default, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
#[cfg(feature = "exception-handler")]
pub(crate) struct TrapFrame {
/// Return address, stores the address to return to after a function call or
/// interrupt.
pub ra: usize,
/// Temporary register t0, used for intermediate values.
pub t0: usize,
/// Temporary register t1, used for intermediate values.
pub t1: usize,
/// Temporary register t2, used for intermediate values.
pub t2: usize,
/// Temporary register t3, used for intermediate values.
pub t3: usize,
/// Temporary register t4, used for intermediate values.
pub t4: usize,
/// Temporary register t5, used for intermediate values.
pub t5: usize,
/// Temporary register t6, used for intermediate values.
pub t6: usize,
/// Argument register a0, typically used to pass the first argument to a
/// function.
pub a0: usize,
/// Argument register a1, typically used to pass the second argument to a
/// function.
pub a1: usize,
/// Argument register a2, typically used to pass the third argument to a
/// function.
pub a2: usize,
/// Argument register a3, typically used to pass the fourth argument to a
/// function.
pub a3: usize,
/// Argument register a4, typically used to pass the fifth argument to a
/// function.
pub a4: usize,
/// Argument register a5, typically used to pass the sixth argument to a
/// function.
pub a5: usize,
/// Argument register a6, typically used to pass the seventh argument to a
/// function.
pub a6: usize,
/// Argument register a7, typically used to pass the eighth argument to a
/// function.
pub a7: usize,
/// Saved register s0, used to hold values across function calls.
pub s0: usize,
/// Saved register s1, used to hold values across function calls.
pub s1: usize,
/// Saved register s2, used to hold values across function calls.
pub s2: usize,
/// Saved register s3, used to hold values across function calls.
pub s3: usize,
/// Saved register s4, used to hold values across function calls.
pub s4: usize,
/// Saved register s5, used to hold values across function calls.
pub s5: usize,
/// Saved register s6, used to hold values across function calls.
pub s6: usize,
/// Saved register s7, used to hold values across function calls.
pub s7: usize,
/// Saved register s8, used to hold values across function calls.
pub s8: usize,
/// Saved register s9, used to hold values across function calls.
pub s9: usize,
/// Saved register s10, used to hold values across function calls.
pub s10: usize,
/// Saved register s11, used to hold values across function calls.
pub s11: usize,
/// Global pointer register, holds the address of the global data area.
pub gp: usize,
/// Thread pointer register, holds the address of the thread-local storage
/// area.
pub tp: usize,
/// Stack pointer register, holds the address of the top of the stack.
pub sp: usize,
/// Program counter, stores the address of the next instruction to be
/// executed.
pub pc: usize,
/// Machine status register, holds the current status of the processor,
/// including interrupt enable bits and privilege mode.
pub mstatus: usize,
/// Machine cause register, contains the reason for the trap (e.g.,
/// exception or interrupt number).
pub mcause: usize,
/// Machine trap value register, contains additional information about the
/// trap (e.g., faulting address).
pub mtval: usize,
}
#[cfg(feature = "exception-handler")]
impl core::fmt::Debug for TrapFrame {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(
fmt,
"TrapFrame
PC=0x{:08x} RA/x1=0x{:08x} SP/x2=0x{:08x} GP/x3=0x{:08x} TP/x4=0x{:08x}
T0/x5=0x{:08x} T1/x6=0x{:08x} T2/x7=0x{:08x} S0/FP/x8=0x{:08x} S1/x9=0x{:08x}
A0/x10=0x{:08x} A1/x11=0x{:08x} A2/x12=0x{:08x} A3/x13=0x{:08x} A4/x14=0x{:08x}
A5/x15=0x{:08x} A6/x16=0x{:08x} A7/x17=0x{:08x} S2/x18=0x{:08x} S3/x19=0x{:08x}
S4/x20=0x{:08x} S5/x21=0x{:08x} S6/x22=0x{:08x} S7/x23=0x{:08x} S8/x24=0x{:08x}
S9/x25=0x{:08x} S10/x26=0x{:08x} S11/x27=0x{:08x} T3/x28=0x{:08x} T4/x29=0x{:08x}
T5/x30=0x{:08x} T6/x31=0x{:08x}
MSTATUS=0x{:08x}
MCAUSE=0x{:08x}
MTVAL=0x{:08x}
",
self.pc,
self.ra,
self.gp,
self.sp,
self.tp,
self.t0,
self.t1,
self.t2,
self.s0,
self.s1,
self.a0,
self.a1,
self.a2,
self.a3,
self.a4,
self.a5,
self.a6,
self.a7,
self.s2,
self.s3,
self.s4,
self.s5,
self.s6,
self.s7,
self.s8,
self.s9,
self.s10,
self.s11,
self.t3,
self.t4,
self.t5,
self.t6,
self.mstatus,
self.mcause,
self.mtval,
)
}
}
/// Get an array of backtrace addresses.
///
/// This needs `force-frame-pointers` enabled.
pub fn backtrace() -> [Option<usize>; MAX_BACKTRACE_ADDRESSES] {
let fp = unsafe {
let mut _tmp: u32;
asm!("mv {0}, x8", out(reg) _tmp);
_tmp
};
backtrace_internal(fp, 2)
}
pub(crate) fn backtrace_internal(
fp: u32,
suppress: i32,
) -> [Option<usize>; MAX_BACKTRACE_ADDRESSES] {
let mut result = [None; 10];
let mut index = 0;
let mut fp = fp;
let mut suppress = suppress;
let mut old_address = 0;
loop {
unsafe {
let address = (fp as *const u32).offset(-1).read_volatile(); // RA/PC
fp = (fp as *const u32).offset(-2).read_volatile(); // next FP
if old_address == address {
break;
}
old_address = address;
if address == 0 {
break;
}
if !crate::is_valid_ram_address(fp) {
break;
}
if suppress == 0 {
result[index] = Some(address as usize);
index += 1;
if index >= MAX_BACKTRACE_ADDRESSES {
break;
}
} else {
suppress -= 1;
}
}
}
result
}
+424
View File
@@ -0,0 +1,424 @@
use core::{arch::asm, fmt::Display};
use crate::MAX_BACKTRACE_ADDRESSES;
// subtract 3 from the return address
// the return address is the address following the callxN
// we get better results (especially if the caller was the last function in the
// calling function) if we report the address of callxN itself
#[allow(unused)]
pub(super) const RA_OFFSET: usize = 3;
/// Exception Cause
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub enum ExceptionCause {
/// Illegal Instruction
IllegalInstruction = 0,
/// System Call (Syscall Instruction)
Syscall = 1,
/// Instruction Fetch Error
InstrFetchError = 2,
/// Load Store Error
LoadStoreError = 3,
/// Level 1 Interrupt
LevelOneInterrupt = 4,
/// Stack Extension Assist (movsp Instruction) For Alloca
Alloca = 5,
/// Integer Divide By Zero
DivideByZero = 6,
/// Use Of Failed Speculative Access (Not Implemented)
NextPCValueIllegal = 7,
/// Privileged Instruction
PrivilegedInstruction = 8,
/// Unaligned Load Or Store
UnalignedLoadOrStore = 9,
/// Reserved
ExternalRegisterPrivilegeError = 10,
/// Reserved
ExclusiveError = 11,
/// Pif Data Error On Instruction Fetch (Rb-200x And Later)
InstrDataError = 12,
/// Pif Data Error On Load Or Store (Rb-200x And Later)
LoadStoreDataError = 13,
/// Pif Address Error On Instruction Fetch (Rb-200x And Later)
InstrAddrError = 14,
/// Pif Address Error On Load Or Store (Rb-200x And Later)
LoadStoreAddrError = 15,
/// Itlb Miss (No Itlb Entry Matches, Hw Refill Also Missed)
ItlbMiss = 16,
/// Itlb Multihit (Multiple Itlb Entries Match)
ItlbMultiHit = 17,
/// Ring Privilege Violation On Instruction Fetch
InstrRing = 18,
/// Size Restriction On Ifetch (Not Implemented)
Reserved19 = 19,
/// Cache Attribute Does Not Allow Instruction Fetch
InstrProhibited = 20,
/// Reserved
Reserved21 = 21,
/// Reserved
Reserved22 = 22,
/// Reserved
Reserved23 = 23,
/// Dtlb Miss (No Dtlb Entry Matches, Hw Refill Also Missed)
DtlbMiss = 24,
/// Dtlb Multihit (Multiple Dtlb Entries Match)
DtlbMultiHit = 25,
/// Ring Privilege Violation On Load Or Store
LoadStoreRing = 26,
/// Size Restriction On Load/Store (Not Implemented)
Reserved27 = 27,
/// Cache Attribute Does Not Allow Load
LoadProhibited = 28,
/// Cache Attribute Does Not Allow Store
StoreProhibited = 29,
/// Reserved
Reserved30 = 30,
/// Reserved
Reserved31 = 31,
/// Access To Coprocessor 0 When Disabled
Cp0Disabled = 32,
/// Access To Coprocessor 1 When Disabled
Cp1Disabled = 33,
/// Access To Coprocessor 2 When Disabled
Cp2Disabled = 34,
/// Access To Coprocessor 3 When Disabled
Cp3Disabled = 35,
/// Access To Coprocessor 4 When Disabled
Cp4Disabled = 36,
/// Access To Coprocessor 5 When Disabled
Cp5Disabled = 37,
/// Access To Coprocessor 6 When Disabled
Cp6Disabled = 38,
/// Access To Coprocessor 7 When Disabled
Cp7Disabled = 39,
/// None
None = 255,
}
impl Display for ExceptionCause {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if *self == Self::Cp0Disabled {
write!(f, "Cp0Disabled (Access to the floating point coprocessor is not allowed. You may want to enable the `float-save-restore` feature of the `xtensa-lx-rt` crate.)")
} else {
write!(f, "{:?}", self)
}
}
}
#[doc(hidden)]
#[allow(non_snake_case)]
#[derive(Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
pub struct Context {
/// Program counter, stores the address of the next instruction to be
/// executed.
pub PC: u32,
/// Processor status, holds various status flags for the CPU.
pub PS: u32,
/// General-purpose register A0 used for data storage and manipulation.
pub A0: u32,
/// General-purpose register A1 used for data storage and manipulation.
pub A1: u32,
/// General-purpose register A2 used for data storage and manipulation.
pub A2: u32,
/// General-purpose register A3 used for data storage and manipulation.
pub A3: u32,
/// General-purpose register A4 used for data storage and manipulation.
pub A4: u32,
/// General-purpose register A5 used for data storage and manipulation.
pub A5: u32,
/// General-purpose register A6 used for data storage and manipulation.
pub A6: u32,
/// General-purpose register A7 used for data storage and manipulation.
pub A7: u32,
/// General-purpose register A8 used for data storage and manipulation.
pub A8: u32,
/// General-purpose register A9 used for data storage and manipulation.
pub A9: u32,
/// General-purpose register A10 used for data storage and manipulation.
pub A10: u32,
/// General-purpose register A11 used for data storage and manipulation.
pub A11: u32,
/// General-purpose register A12 used for data storage and manipulation.
pub A12: u32,
/// General-purpose register A13 used for data storage and manipulation.
pub A13: u32,
/// General-purpose register A14 used for data storage and manipulation.
pub A14: u32,
/// General-purpose register A15 used for data storage and manipulation.
pub A15: u32,
/// Shift amount register, used for shift and rotate instructions.
pub SAR: u32,
/// Exception cause, indicates the reason for the last exception.
pub EXCCAUSE: u32,
/// Exception address, holds the address related to the exception.
pub EXCVADDR: u32,
/// Loop start address, used in loop instructions.
pub LBEG: u32,
/// Loop end address, used in loop instructions.
pub LEND: u32,
/// Loop counter, used to count iterations in loop instructions.
pub LCOUNT: u32,
/// Thread pointer, used for thread-local storage.
pub THREADPTR: u32,
/// Compare register, used for certain compare instructions.
pub SCOMPARE1: u32,
/// Break register, used for breakpoint-related operations.
pub BR: u32,
/// Accumulator low register, used for extended arithmetic operations.
pub ACCLO: u32,
/// Accumulator high register, used for extended arithmetic operations.
pub ACCHI: u32,
/// Additional register M0 used for special operations.
pub M0: u32,
/// Additional register M1 used for special operations.
pub M1: u32,
/// Additional register M2 used for special operations.
pub M2: u32,
/// Additional register M3 used for special operations.
pub M3: u32,
/// 64-bit floating-point register (low part), available if the
/// `print-float-registers` feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F64R_LO: u32,
/// 64-bit floating-point register (high part), available if the
/// `print-float-registers` feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F64R_HI: u32,
/// Floating-point status register, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F64S: u32,
/// Floating-point control register, available if the
/// `print-float-registers` feature is enabled.
#[cfg(feature = "print-float-registers")]
pub FCR: u32,
/// Floating-point status register, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub FSR: u32,
/// Floating-point register F0, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F0: u32,
/// Floating-point register F1, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F1: u32,
/// Floating-point register F2, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F2: u32,
/// Floating-point register F3, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F3: u32,
/// Floating-point register F4, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F4: u32,
/// Floating-point register F5, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F5: u32,
/// Floating-point register F6, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F6: u32,
/// Floating-point register F7, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F7: u32,
/// Floating-point register F8, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F8: u32,
/// Floating-point register F9, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F9: u32,
/// Floating-point register F10, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F10: u32,
/// Floating-point register F11, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F11: u32,
/// Floating-point register F12, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F12: u32,
/// Floating-point register F13, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F13: u32,
/// Floating-point register F14, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F14: u32,
/// Floating-point register F15, available if the `print-float-registers`
/// feature is enabled.
#[cfg(feature = "print-float-registers")]
pub F15: u32,
}
impl core::fmt::Debug for Context {
fn fmt(&self, fmt: &mut core::fmt::Formatter<'_>) -> Result<(), core::fmt::Error> {
write!(
fmt,
"Context
PC=0x{:08x} PS=0x{:08x}
A0=0x{:08x} A1=0x{:08x} A2=0x{:08x} A3=0x{:08x} A4=0x{:08x}
A5=0x{:08x} A6=0x{:08x} A7=0x{:08x} A8=0x{:08x} A9=0x{:08x}
A10=0x{:08x} A11=0x{:08x} A12=0x{:08x} A13=0x{:08x} A14=0x{:08x}
A15=0x{:08x}
SAR={:08x}
EXCCAUSE=0x{:08x} EXCVADDR=0x{:08x}
LBEG=0x{:08x} LEND=0x{:08x} LCOUNT=0x{:08x}
THREADPTR=0x{:08x}
SCOMPARE1=0x{:08x}
BR=0x{:08x}
ACCLO=0x{:08x} ACCHI=0x{:08x}
M0=0x{:08x} M1=0x{:08x} M2=0x{:08x} M3=0x{:08x}
",
self.PC,
self.PS,
self.A0,
self.A1,
self.A2,
self.A3,
self.A4,
self.A5,
self.A6,
self.A7,
self.A8,
self.A9,
self.A10,
self.A11,
self.A12,
self.A13,
self.A14,
self.A15,
self.SAR,
self.EXCCAUSE,
self.EXCVADDR,
self.LBEG,
self.LEND,
self.LCOUNT,
self.THREADPTR,
self.SCOMPARE1,
self.BR,
self.ACCLO,
self.ACCHI,
self.M0,
self.M1,
self.M2,
self.M3,
)?;
#[cfg(feature = "print-float-registers")]
write!(
fmt,
"F64R_LO=0x{:08x} F64R_HI=0x{:08x} F64S=0x{:08x}
FCR=0x{:08x} FSR=0x{:08x}
F0=0x{:08x} F1=0x{:08x} F2=0x{:08x} F3=0x{:08x} F4=0x{:08x}
F5=0x{:08x} F6=0x{:08x} F7=0x{:08x} F8=0x{:08x} F9=0x{:08x}
F10=0x{:08x} F11=0x{:08x} F12=0x{:08x} F13=0x{:08x} F14=0x{:08x}
F15=0x{:08x}",
self.F64R_LO,
self.F64R_HI,
self.F64S,
self.FCR,
self.FSR,
self.F0,
self.F1,
self.F2,
self.F3,
self.F4,
self.F5,
self.F6,
self.F7,
self.F8,
self.F9,
self.F10,
self.F11,
self.F12,
self.F13,
self.F14,
self.F15,
)?;
Ok(())
}
}
/// Get an array of backtrace addresses.
pub fn backtrace() -> [Option<usize>; MAX_BACKTRACE_ADDRESSES] {
let sp = unsafe {
let mut _tmp: u32;
asm!("mov {0}, a1", out(reg) _tmp);
_tmp
};
backtrace_internal(sp, 1)
}
pub(crate) fn sanitize_address(address: u32) -> u32 {
(address & 0x3fff_ffff) | 0x4000_0000
}
pub(crate) fn backtrace_internal(
sp: u32,
suppress: i32,
) -> [Option<usize>; MAX_BACKTRACE_ADDRESSES] {
let mut result = [None; 10];
let mut index = 0;
let mut fp = sp;
let mut suppress = suppress;
let mut old_address = 0;
loop {
unsafe {
let address = sanitize_address((fp as *const u32).offset(-4).read_volatile()); // RA/PC
fp = (fp as *const u32).offset(-3).read_volatile(); // next FP
if old_address == address {
break;
}
old_address = address;
// the address is 0 but we sanitized the address - then 0 becomes 0x40000000
if address == 0x40000000 {
break;
}
if !crate::is_valid_ram_address(fp) {
break;
}
if fp == 0 {
break;
}
if suppress == 0 {
result[index] = Some(address as usize);
index += 1;
if index >= MAX_BACKTRACE_ADDRESSES {
break;
}
} else {
suppress -= 1;
}
}
}
result
}