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

+121
View File
@@ -0,0 +1,121 @@
//! Exception handling
//!
//! Currently specialized for ESP32 (LX6) configuration: which extra registers
//! to store, how many interrupt levels etc.
//!
//! First level interrupts and exceptions save full processor state to the user
//! stack. This includes the coprocessor registers contrary to the esp-idf where
//! these are lazily saved. (Kernel mode option is currently not used.)
//!
//! WindowUnder/Overflow and AllocA use default Xtensa implementation.
//!
//! LoadStoreError and Unaligned are not (yet) implemented: so all accesses to
//! IRAM must be word sized and aligned.
//!
//! Syscall 0 is not (yet) implemented: it doesn't seem to be used in rust.
//!
//! Double Exceptions can only occur during the early setup of the exception
//! handler. Afterwards PS.EXCM is set to 0 to be able to handle
//! WindowUnderflow/Overflow and recursive exceptions will happen instead.
//!
//! In various places call0 are used as long jump: `j.l` syntax is not supported
//! and `call0` can always be expanded to `mov a0,label; call a0`. Care must be
//! taken since A0 is overwritten.
mod asm;
mod context;
pub use context::Context;
/// EXCCAUSE register values
///
/// General Exception Causes. (Values of EXCCAUSE special register set by
/// general exceptions, which vector to the user, kernel, or double-exception
/// vectors).
#[allow(unused)]
#[derive(Debug, PartialEq)]
#[repr(C)]
pub enum ExceptionCause {
/// Illegal Instruction
Illegal = 0,
/// System Call (Syscall Instruction)
Syscall = 1,
/// Instruction Fetch Error
InstrError = 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
Privileged = 8,
/// Unaligned Load Or Store
Unaligned = 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 = 255,
}
+690
View File
@@ -0,0 +1,690 @@
use core::arch::{asm, global_asm};
use crate::cfg_asm;
// We could cfg symbols away and reduce frame size depending on features enabled
// i.e the frame size is a fixed size based on all the features right now
// we know at compile time if a target has loops for example, if it doesn't
// we could cut that memory usage.
// However in order to conveniently use `addmi` we need 256-byte alignment
// anyway so wasting a bit more stack space seems to be the better option.
//
// The only exception is XT_STK_F64R_LO_CPENABLE which combines two register
// values depending on `float-save-restore` feature. This is done so that their
// position stays the same in `Context` without creating a large hole in the
// struct.
//
// Additionally there is a chunk of memory reserved for spilled registers.
//
// With the exception of XT_STK_TMP, the fields must be aligned with the
// `Context` struct in context.rs
global_asm!(
"
.set XT_STK_PC, 0
.set XT_STK_PS, 4
.set XT_STK_A0, 8
.equ XT_STK_A1, 12
.set XT_STK_A2, 16
.set XT_STK_A3, 20
.set XT_STK_A4, 24
.set XT_STK_A5, 28
.set XT_STK_A6, 32
.set XT_STK_A7, 36
.set XT_STK_A8, 40
.set XT_STK_A9, 44
.set XT_STK_A10, 48
.set XT_STK_A11, 52
.set XT_STK_A12, 56
.set XT_STK_A13, 60
.set XT_STK_A14, 64
.set XT_STK_A15, 68
.set XT_STK_SAR, 72
.set XT_STK_EXCCAUSE, 76
.set XT_STK_EXCVADDR, 80
.set XT_STK_LBEG, 84 // Registers for Loop Option
.set XT_STK_LEND, 88
.set XT_STK_LCOUNT, 92
.set XT_STK_THREADPTR, 96 // freely usable 32-bit register intended for TLS
.set XT_STK_SCOMPARE1, 100 // Register for s32ci instruction
.set XT_STK_BR, 104 // Register for Boolean Option
.set XT_STK_ACCLO, 108 // Registers for MAC16 option
.set XT_STK_ACCHI, 112
.set XT_STK_M0, 116
.set XT_STK_M1, 120
.set XT_STK_M2, 124
.set XT_STK_M3, 128
// if `float-save-restore` is enabled: Registers for double support option.
// Otherwise, the saved value of CPENABLE
.set XT_STK_F64R_LO_CPENABLE, 132
.set XT_STK_F64R_HI, 136
.set XT_STK_F64S, 140
.set XT_STK_FCR, 144 // Registers for floating point coprocessor
.set XT_STK_FSR, 148
.set XT_STK_F0, 152
.set XT_STK_F1, 156
.set XT_STK_F2, 160
.set XT_STK_F3, 164
.set XT_STK_F4, 168
.set XT_STK_F5, 172
.set XT_STK_F6, 176
.set XT_STK_F7, 180
.set XT_STK_F8, 184
.set XT_STK_F9, 188
.set XT_STK_F10, 192
.set XT_STK_F11, 196
.set XT_STK_F12, 200
.set XT_STK_F13, 204
.set XT_STK_F14, 208
.set XT_STK_F15, 212
.set XT_STK_TMP, 216
.set XT_STK_FRMSZ, 256 // needs to be multiple of 16 and enough additional free space
// for the registers spilled to the stack (max 8 registers / 0x20 bytes)
// multiple of 256 allows use of addmi instruction
.set PS_INTLEVEL_EXCM, 3 // interrupt handlers above this level shouldn't be written in high level languages
.set PS_INTLEVEL_MASK, 0x0000000f
.set PS_EXCM, 0x00000010
.set PS_UM, 0x00000020
.set PS_WOE, 0x00040000
"
);
/// Save processor state to stack.
///
/// *Must only be called with call0.*
/// *For spill all window registers to work WOE must be enabled on entry
///
/// Saves all registers except PC, PS, A0, A1
///
/// Inputs:
/// A0 is the return address
/// A1 is the stack pointers
/// Exceptions are disabled (PS.EXCM = 1)
///
/// Output:
/// A0 is the return address
/// A1 is the stack pointer
/// A3, A9 are used as scratch registers
/// EPC1 is changed
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn save_context() {
cfg_asm!(
{
"
s32i a2, sp, +XT_STK_A2
s32i a3, sp, +XT_STK_A3
s32i a4, sp, +XT_STK_A4
s32i a5, sp, +XT_STK_A5
s32i a6, sp, +XT_STK_A6
s32i a7, sp, +XT_STK_A7
s32i a8, sp, +XT_STK_A8
s32i a9, sp, +XT_STK_A9
s32i a10, sp, +XT_STK_A10
s32i a11, sp, +XT_STK_A11
s32i a12, sp, +XT_STK_A12
s32i a13, sp, +XT_STK_A13
s32i a14, sp, +XT_STK_A14
s32i a15, sp, +XT_STK_A15
rsr a3, SAR
s32i a3, sp, +XT_STK_SAR
",
#[cfg(all(XCHAL_HAVE_CP, not(feature = "float-save-restore")))]
"
/* Disable coprocessor, any use of floats in ISRs will cause an exception unless float-save-restore feature is enabled */
rsr a3, CPENABLE
s32i a3, sp, +XT_STK_F64R_LO_CPENABLE
movi a3, 0
wsr a3, CPENABLE
rsync
",
#[cfg(XCHAL_HAVE_LOOPS)]
"
// Loop Option
rsr a3, LBEG
s32i a3, sp, +XT_STK_LBEG
rsr a3, LEND
s32i a3, sp, +XT_STK_LEND
rsr a3, LCOUNT
s32i a3, sp, +XT_STK_LCOUNT
",
#[cfg(XCHAL_HAVE_THREADPTR)]
"
// Thread Pointer Option
rur a3, threadptr
s32i a3, sp, +XT_STK_THREADPTR
",
#[cfg(XCHAL_HAVE_S32C1I)]
"
// Conditional Store Option
rsr a3, scompare1
s32i a3, sp, +XT_STK_SCOMPARE1
",
#[cfg(XCHAL_HAVE_BOOLEANS)]
"
// Boolean Option
rsr a3, br
s32i a3, sp, +XT_STK_BR
",
#[cfg(XCHAL_HAVE_MAC16)]
"
// MAC16 Option
rsr a3, acclo
s32i a3, sp, +XT_STK_ACCLO
rsr a3, acchi
s32i a3, sp, +XT_STK_ACCHI
rsr a3, m0
s32i a3, sp, +XT_STK_M0
rsr a3, m1
s32i a3, sp, +XT_STK_M1
rsr a3, m2
s32i a3, sp, +XT_STK_M2
rsr a3, m3
s32i a3, sp, +XT_STK_M3
",
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_DFP_ACCEL))]
"
// Double Precision Accelerator Option
rur a3, f64r_lo
s32i a3, sp, +XT_STK_F64R_LO_CPENABLE
rur a3, f64r_hi
s32i a3, sp, +XT_STK_F64R_HI
rur a3, f64s
s32i a3, sp, +XT_STK_F64S
",
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
"
// Coprocessor Option
rur a3, fcr
s32i a3, sp, +XT_STK_FCR
rur a3, fsr
s32i a3, sp, +XT_STK_FSR
ssi f0, sp, +XT_STK_F0
ssi f1, sp, +XT_STK_F1
ssi f2, sp, +XT_STK_F2
ssi f3, sp, +XT_STK_F3
ssi f4, sp, +XT_STK_F4
ssi f5, sp, +XT_STK_F5
ssi f6, sp, +XT_STK_F6
ssi f7, sp, +XT_STK_F7
ssi f8, sp, +XT_STK_F8
ssi f9, sp, +XT_STK_F9
ssi f10, sp, +XT_STK_F10
ssi f11, sp, +XT_STK_F11
ssi f12, sp, +XT_STK_F12
ssi f13, sp, +XT_STK_F13
ssi f14, sp, +XT_STK_F14
ssi f15, sp, +XT_STK_F15
",
#[cfg(XCHAL_HAVE_WINDOWED)]
"
s32i a0, sp, +XT_STK_TMP // keep return address on the stack
// SPILL_REGISTERS macro requires window overflow exceptions to be enabled,
// i.e. PS.EXCM cleared and PS.WOE set.
// Since we are going to clear PS.EXCM, we also need to increase INTLEVEL
// at least to XCHAL_EXCM_LEVEL. This matches that value of effective INTLEVEL
// at entry (CINTLEVEL=max(PS.INTLEVEL, XCHAL_EXCM_LEVEL) when PS.EXCM is set.
// Since WindowOverflow exceptions will trigger inside SPILL_REGISTERS,
// need to save/restore EPC1 as well.
// Note: even though a4-a15 are saved into the exception frame, we should not
// clobber them until after SPILL_REGISTERS. This is because these registers
// may contain live windows belonging to previous frames in the call stack.
// These frames will be spilled by SPILL_REGISTERS, and if the register was
// used as a temporary by this code, the temporary value would get stored
// onto the stack, instead of the real value.
//
rsr a2, PS // to be restored after SPILL_REGISTERS
movi a0, PS_INTLEVEL_MASK
and a3, a2, a0 // get the current INTLEVEL
bgeui a3, +PS_INTLEVEL_EXCM, 1f // calculate max(INTLEVEL, XCHAL_EXCM_LEVEL) - 3 = XCHAL_EXCM_LEVEL
movi a3, PS_INTLEVEL_EXCM
1:
movi a0, PS_WOE // clear EXCM, enable window overflow, set new INTLEVEL
or a3, a3, a0
wsr a3, ps
rsr a0, EPC1
addmi sp, sp, +XT_STK_FRMSZ // go back to spill register region
SPILL_REGISTERS
addmi sp, sp, -XT_STK_FRMSZ // return the current stack pointer
wsr a2, PS // restore to the value at entry
rsync
wsr a0, EPC1
l32i a0, sp, +XT_STK_TMP
",
"
ret
",
},
options(noreturn)
);
}
global_asm!(
r#"
// Spills all active windowed registers (i.e. registers not visible as
// A0-A15) to their ABI-defined spill regions on the stack.
// It will spill registers to their reserved locations in previous frames.
//
// Unlike the Xtensa HAL implementation, this code requires that the
// EXCM and WOE bit be enabled in PS, and relies on repeated hardware
// exception handling to do the register spills. The trick is to do a
// noop write to the high registers, which the hardware will trap
// (into an overflow exception) in the case where those registers are
// already used by an existing call frame. Then it rotates the window
// and repeats until all but the A0-A3 registers of the original frame
// are guaranteed to be spilled, eventually rotating back around into
// the original frame. Advantages:
//
// - Vastly smaller code size
//
// - More easily maintained if changes are needed to window over/underflow
// exception handling.
//
// - Requires no scratch registers to do its work, so can be used safely in any
// context.
//
// - If the WOE bit is not enabled (for example, in code written for
// the CALL0 ABI), this becomes a silent noop and operates compatbily.
//
// - Hilariously it's ACTUALLY FASTER than the HAL routine. And not
// just a little bit, it's MUCH faster. With a mostly full register
// file on an LX6 core (ESP-32) I'm measuring 145 cycles to spill
// registers with this vs. 279 (!) to do it with
// xthal_spill_windows().
.macro SPILL_REGISTERS
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 3
and a12, a12, a12
rotw 4
.endm
"#
);
global_asm!(
r#"
.macro SAVE_CONTEXT level:req
mov a0, a1 // save a1/sp
addmi sp, sp, -XT_STK_FRMSZ // only allow multiple of 256
s32i a0, sp, +XT_STK_A1 // save interruptee's A1/SP
s32e a0, sp, -12 // for debug backtrace
.ifc \level,1
rsr a0, PS
s32i a0, sp, +XT_STK_PS // save interruptee's PS
rsr a0, EXCCAUSE
s32i a0, sp, +XT_STK_EXCCAUSE
rsr a0, EXCVADDR
s32i a0, sp, +XT_STK_EXCVADDR
.else
rsr a0, EPS\level
s32i a0, sp, +XT_STK_PS // save interruptee's PS
.endif
rsr a0, EPC\level
s32i a0, sp, +XT_STK_PC // save interruptee's PC
s32e a0, sp, -16 // for debug backtrace
rsr a0, EXCSAVE\level
s32i a0, sp, +XT_STK_A0 // save interruptee's A0
call0 save_context
.endm
"#
);
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn restore_context() {
cfg_asm!(
{
"
l32i a3, sp, +XT_STK_SAR
wsr a3, SAR
",
#[cfg(XCHAL_HAVE_LOOPS)]
"
// Loop Option
l32i a3, sp, +XT_STK_LBEG
wsr a3, LBEG
l32i a3, sp, +XT_STK_LEND
wsr a3, LEND
l32i a3, sp, +XT_STK_LCOUNT
wsr a3, LCOUNT
",
#[cfg(XCHAL_HAVE_THREADPTR)]
"
// Thread Pointer Option
l32i a3, sp, +XT_STK_THREADPTR
wur a3, threadptr
",
#[cfg(XCHAL_HAVE_S32C1I)]
"
// Conditional Store Option
l32i a3, sp, +XT_STK_SCOMPARE1
wsr a3, scompare1
",
#[cfg(XCHAL_HAVE_BOOLEANS)]
"
// Boolean Option
l32i a3, sp, +XT_STK_BR
wsr a3, br
",
#[cfg(XCHAL_HAVE_MAC16)]
"
// MAC16 Option
l32i a3, sp, +XT_STK_ACCLO
wsr a3, acclo
l32i a3, sp, +XT_STK_ACCHI
wsr a3, acchi
l32i a3, sp, +XT_STK_M0
wsr a3, m0
l32i a3, sp, +XT_STK_M1
wsr a3, m1
l32i a3, sp, +XT_STK_M2
wsr a3, m2
l32i a3, sp, +XT_STK_M3
wsr a3, m3
",
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_DFP_ACCEL))]
"
// Double Precision Accelerator Option
l32i a3, sp, +XT_STK_F64R_LO_CPENABLE
wur a3, f64r_lo
l32i a3, sp, +XT_STK_F64R_HI
wur a3, f64r_hi
l32i a3, sp, +XT_STK_F64S
wur a3, f64s
",
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
"
// Coprocessor Option
l32i a3, sp, +XT_STK_FCR
wur a3, fcr
l32i a3, sp, +XT_STK_FSR
wur a3, fsr
lsi f0, sp, +XT_STK_F0
lsi f1, sp, +XT_STK_F1
lsi f2, sp, +XT_STK_F2
lsi f3, sp, +XT_STK_F3
lsi f4, sp, +XT_STK_F4
lsi f5, sp, +XT_STK_F5
lsi f6, sp, +XT_STK_F6
lsi f7, sp, +XT_STK_F7
lsi f8, sp, +XT_STK_F8
lsi f9, sp, +XT_STK_F9
lsi f10, sp, +XT_STK_F10
lsi f11, sp, +XT_STK_F11
lsi f12, sp, +XT_STK_F12
lsi f13, sp, +XT_STK_F13
lsi f14, sp, +XT_STK_F14
lsi f15, sp, +XT_STK_F15
",
#[cfg(all(XCHAL_HAVE_CP, not(feature = "float-save-restore")))]
"
/* Restore coprocessor state after ISR */
l32i a3, sp, +XT_STK_F64R_LO_CPENABLE
wsr a3, CPENABLE
rsync
",
"
// general registers
l32i a2, sp, +XT_STK_A2
l32i a3, sp, +XT_STK_A3
l32i a4, sp, +XT_STK_A4
l32i a5, sp, +XT_STK_A5
l32i a6, sp, +XT_STK_A6
l32i a7, sp, +XT_STK_A7
l32i a8, sp, +XT_STK_A8
l32i a9, sp, +XT_STK_A9
l32i a10, sp, +XT_STK_A10
l32i a11, sp, +XT_STK_A11
l32i a12, sp, +XT_STK_A12
l32i a13, sp, +XT_STK_A13
l32i a14, sp, +XT_STK_A14
l32i a15, sp, +XT_STK_A15
ret
",
}, options(noreturn));
}
global_asm!(
r#"
.macro RESTORE_CONTEXT level:req
// Restore context and return
call0 restore_context
.ifc \level,1
l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS
wsr a0, PS
l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC
wsr a0, EPC\level
.else
l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS
wsr a0, EPS\level
l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC
wsr a0, EPC\level
.endif
l32i a0, sp, +XT_STK_A0 // retrieve interruptee's A0
l32i sp, sp, +XT_STK_A1 // remove exception frame
rsync // ensure PS and EPC written
.endm
"#
);
/// Handle Other Exceptions or Level 1 interrupt by storing full context and
/// then calling regular function
///
/// # Input:
/// * A0 stored in EXCSAVE1
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_exception() {
asm!(
"
SAVE_CONTEXT 1
movi a0, (PS_INTLEVEL_EXCM | PS_WOE)
wsr a0, PS
rsync
l32i a6, sp, +XT_STK_EXCCAUSE // put cause in a6 = a2 in callee
beqi a6, 4, .Level1Interrupt
mov a7, sp // put address of save frame in a7=a3 in callee
call4 __exception // call handler <= actual call!
j .RestoreContext
.Level1Interrupt:
movi a0, (1 | PS_WOE) // set PS.INTLEVEL accordingly
wsr a0, PS
rsync
movi a6, 1 // put interrupt level in a6 = a2 in callee
mov a7, sp // put address of save frame in a7=a3 in callee
call4 __level_1_interrupt // call handler <= actual call!
.RestoreContext:
RESTORE_CONTEXT 1
rfe // PS.EXCM is cleared
",
options(noreturn)
)
}
/// Handle Double Exceptions by storing full context and then calling regular
/// function Double exceptions are not a normal occurrence. They indicate a bug
/// of some kind.
///
/// # Input:
/// * A0 stored in EXCSAVE1
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_double_exception() {
asm!(
"
mov a0, a1 // save a1/sp
addmi sp, sp, -XT_STK_FRMSZ // only allow multiple of 256
s32i a0, sp, +XT_STK_A1 // save interruptee's A1/SP
s32e a0, sp, -12 // for debug backtrace
rsr a0, PS
s32i a0, sp, +XT_STK_PS // save interruptee's PS
rsr a0, EXCCAUSE
s32i a0, sp, +XT_STK_EXCCAUSE
rsr a0, EXCVADDR
s32i a0, sp, +XT_STK_EXCVADDR
rsr a0, DEPC
s32i a0, sp, +XT_STK_PC // save interruptee's PC
s32e a0, sp, -16 // for debug backtrace
rsr a0, EXCSAVE7 // ok to reuse EXCSAVE7 for double exception as long as
// double exception is not in first couple of instructions
// of level 7 handler
s32i a0, sp, +XT_STK_A0 // save interruptee's A0
call0 save_context
l32i a6, sp, +XT_STK_EXCCAUSE // put cause in a6 = a2 in callee
mov a7, sp // put address of save frame in a7=a3 in callee
call4 __exception // call handler <= actual call!
// Restore context and return
call0 restore_context
l32i a0, sp, +XT_STK_PS // retrieve interruptee's PS
wsr a0, PS
l32i a0, sp, +XT_STK_PC // retrieve interruptee's PC
wsr a0, EPC1
l32i a0, sp, +XT_STK_A0 // retrieve interruptee's A0
l32i sp, sp, +XT_STK_A1 // remove exception frame
rsync // ensure PS and EPC written
rfde
",
options(noreturn)
)
}
global_asm!(
r#"
.macro HANDLE_INTERRUPT_LEVEL level
SAVE_CONTEXT \level
movi a0, (\level | PS_WOE)
wsr a0, PS
rsync
movi a6, \level // put interrupt level in a6 = a2 in callee
mov a7, sp // put address of save frame in a7=a3 in callee
call4 __level_\level\()_interrupt // call handler <= actual call!
RESTORE_CONTEXT \level
rfi \level
.endm
"#
);
/// Handle Level 2 Interrupt by storing full context and then calling regular
/// function
///
/// # Input:
/// * A0 stored in EXCSAVE2
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_level_2_interrupt() {
asm!("HANDLE_INTERRUPT_LEVEL 2", options(noreturn));
}
/// Handle Level 3 Interrupt by storing full context and then calling regular
/// function
///
/// # Input:
/// * A0 stored in EXCSAVE3
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_level_3_interrupt() {
asm!("HANDLE_INTERRUPT_LEVEL 3", options(noreturn));
}
/// Handle Level 4 Interrupt by storing full context and then calling regular
/// function
///
/// # Input:
/// * A0 stored in EXCSAVE4
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_level_4_interrupt() {
asm!("HANDLE_INTERRUPT_LEVEL 4", options(noreturn));
}
/// Handle Level 5 Interrupt by storing full context and then calling regular
/// function
///
/// # Input:
/// * A0 stored in EXCSAVE5
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_level_5_interrupt() {
asm!("HANDLE_INTERRUPT_LEVEL 5", options(noreturn));
}
/// Handle Level 6 (=Debug) Interrupt by storing full context and then calling
/// regular function
///
/// # Input:
/// * A0 stored in EXCSAVE6
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_level_6_interrupt() {
asm!("HANDLE_INTERRUPT_LEVEL 6", options(noreturn));
}
/// Handle Level 7 (=NMI) Interrupt by storing full context and then calling
/// regular function
///
/// # Input:
/// * A0 stored in EXCSAVE7
#[naked]
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_naked_level_7_interrupt() {
asm!("HANDLE_INTERRUPT_LEVEL 7", options(noreturn));
}
+453
View File
@@ -0,0 +1,453 @@
use core::arch::asm;
use super::ExceptionCause;
/// State of the CPU saved when entering exception or interrupt
///
/// Must be aligned with assembly frame format in asm.rs
#[repr(C)]
#[allow(non_snake_case)]
#[derive(Debug, Clone, Copy)]
pub struct Context {
pub PC: u32,
pub PS: u32,
pub A0: u32,
pub A1: u32,
pub A2: u32,
pub A3: u32,
pub A4: u32,
pub A5: u32,
pub A6: u32,
pub A7: u32,
pub A8: u32,
pub A9: u32,
pub A10: u32,
pub A11: u32,
pub A12: u32,
pub A13: u32,
pub A14: u32,
pub A15: u32,
pub SAR: u32,
pub EXCCAUSE: u32,
pub EXCVADDR: u32,
pub LBEG: u32,
pub LEND: u32,
pub LCOUNT: u32,
pub THREADPTR: u32,
pub SCOMPARE1: u32,
pub BR: u32,
pub ACCLO: u32,
pub ACCHI: u32,
pub M0: u32,
pub M1: u32,
pub M2: u32,
pub M3: u32,
#[cfg(XCHAL_HAVE_CP)]
// Either F64R_LO or CPENABLE depending on `float-save-restore`.
pub F64R_LO_CPENABLE: u32,
// F64R_HI is only meaningful with cfg!(XCHAL_HAVE_DFP_ACCEL) but it's present in the stack
// frame unconditionally
#[cfg(feature = "float-save-restore")]
pub F64R_HI: u32,
// F64S is only meaningful with cfg!(XCHAL_HAVE_DFP_ACCEL) but it's present in the stack frame
// unconditionally
#[cfg(feature = "float-save-restore")]
pub F64S: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub FCR: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub FSR: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F0: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F1: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F2: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F3: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F4: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F5: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F6: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F7: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F8: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F9: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F10: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F11: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F12: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F13: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F14: u32,
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_FP))]
pub F15: u32,
}
impl Default for Context {
fn default() -> Self {
Self::new()
}
}
impl Context {
/// Creates a new, zeroed out context.
pub const fn new() -> Self {
unsafe { core::mem::zeroed() }
}
}
extern "Rust" {
/// The exception assembly jumps here once registers have been spilled
fn __exception(cause: ExceptionCause, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[exception]`
fn __user_exception(cause: ExceptionCause, save_frame: &mut Context);
/// No attribute is supplied for this symbol as the double exception can
/// hardly occur
fn __double_exception(cause: ExceptionCause, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(1)]`
fn __level_1_interrupt(level: u32, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(2)]`
fn __level_2_interrupt(level: u32, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(3)]`
fn __level_3_interrupt(level: u32, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(4)]`
fn __level_4_interrupt(level: u32, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(5)]`
fn __level_5_interrupt(level: u32, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(6)]`
fn __level_6_interrupt(level: u32, save_frame: &mut Context);
/// This symbol will be provided by the user via `#[interrupt(7)]`
fn __level_7_interrupt(level: u32, save_frame: &mut Context);
}
#[no_mangle]
#[link_section = ".rwtext"]
unsafe extern "C" fn __default_exception(cause: ExceptionCause, save_frame: &mut Context) {
__user_exception(cause, save_frame)
}
#[no_mangle]
#[link_section = ".rwtext"]
extern "C" fn __default_user_exception(cause: ExceptionCause, save_frame: &Context) {
#[cfg(any(feature = "esp32", feature = "esp32s3"))]
if cause == ExceptionCause::Cp0Disabled {
panic!(
"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. {:08x?}",
save_frame
)
}
panic!("Exception: {:?}, {:08x?}", cause, save_frame)
}
#[no_mangle]
#[link_section = ".rwtext"]
extern "C" fn __default_interrupt(level: u32, save_frame: &Context) {
panic!("Interrupt: {:?}, {:08x?}", level, save_frame)
}
#[no_mangle]
#[link_section = ".rwtext"]
extern "C" fn __default_double_exception(cause: ExceptionCause, save_frame: &Context) {
panic!("Double Exception: {:?}, {:08x?}", cause, save_frame)
}
// Raw vector handlers
//
// The interrupt handlers all use special return instructions.
// rust still generates a ret.w instruction, which will never be reached.
// generation of the ret.w can be prevented by using
// core::intrinsics::unreachable, but then a break 15,1 will be generated (which
// takes 3 bytes instead of 2) or a 'loop {}', but then a jump to own address
// will be generated which is also 3 bytes. No way found yet to prevent this
// generation altogether.
#[naked]
#[no_mangle]
#[link_section = ".KernelExceptionVector.text"]
unsafe extern "C" fn _KernelExceptionVector() {
asm!(
"
wsr a0, EXCSAVE1 // preserve a0
rsr a0, EXCCAUSE // get exception cause
beqi a0, 5, .AllocAException
call0 __naked_kernel_exception
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".UserExceptionVector.text"]
unsafe extern "C" fn _UserExceptionVector() {
asm!(
"
wsr a0, EXCSAVE1 // preserve a0
rsr a0, EXCCAUSE // get exception cause
beqi a0, 5, .AllocAException
call0 __naked_user_exception
.AllocAException:
call0 _AllocAException
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".DoubleExceptionVector.text"]
unsafe extern "C" fn _DoubleExceptionVector() {
asm!(
"
wsr a0, EXCSAVE1 // preserve a0 (EXCSAVE1 can be reused as long as there
// is no double exception in the first exception until
// EXCSAVE1 is stored to the stack.)
call0 __naked_double_exception // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".Level2InterruptVector.text"]
unsafe extern "C" fn _Level2InterruptVector() {
asm!(
"
wsr a0, EXCSAVE2 // preserve a0
call0 __naked_level_2_interrupt // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".Level3InterruptVector.text"]
unsafe extern "C" fn _Level3InterruptVector() {
asm!(
"
wsr a0, EXCSAVE3 // preserve a0
call0 __naked_level_3_interrupt // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".Level4InterruptVector.text"]
unsafe extern "C" fn _Level4InterruptVector() {
asm!(
"
wsr a0, EXCSAVE4 // preserve a0
call0 __naked_level_4_interrupt // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".Level5InterruptVector.text"]
unsafe extern "C" fn _Level5InterruptVector() {
asm!(
"
wsr a0, EXCSAVE5 // preserve a0
call0 __naked_level_5_interrupt // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".DebugExceptionVector.text"]
unsafe extern "C" fn _Level6InterruptVector() {
asm!(
"
wsr a0, EXCSAVE6 // preserve a0
call0 __naked_level_6_interrupt // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".NMIExceptionVector.text"]
unsafe extern "C" fn _Level7InterruptVector() {
asm!(
"
wsr a0, EXCSAVE7 // preserve a0
call0 __naked_level_7_interrupt // used as long jump
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".WindowOverflow4.text"]
unsafe extern "C" fn _WindowOverflow4() {
asm!(
"
s32e a0, a5, -16
s32e a1, a5, -12
s32e a2, a5, -8
s32e a3, a5, -4
rfwo
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".WindowUnderflow4.text"]
unsafe extern "C" fn _WindowUnderflow4() {
asm!(
"
l32e a0, a5, -16
l32e a1, a5, -12
l32e a2, a5, -8
l32e a3, a5, -4
rfwu
// inline the _AllocAException saves on the ret.w for WindowUnderflow4
// this makes that it just fits, which is needed for the bbci instructions
.align 4
_AllocAException:
rsr a0, WINDOWBASE // grab WINDOWBASE before rotw changes it
rotw -1 // WINDOWBASE goes to a4, new a0-a3 are scratch
rsr a2, PS
extui a3, a2, 8, 4 // XCHAL_PS_OWB_SHIFT, XCHAL_PS_OWB_BITS
xor a3, a3, a4 // bits changed from old to current windowbase
rsr a4, EXCSAVE1 // restore original a0 (now in a4)
slli a3, a3, 8 // XCHAL_PS_OWB_SHIFT
xor a2, a2, a3 // flip changed bits in old window base
wsr a2, PS // update PS.OWB to new window base
rsync
bbci a4, 31, _WindowUnderflow4
rotw -1 // original a0 goes to a8
bbci a8, 30, _WindowUnderflow8
rotw -1
j _WindowUnderflow12
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".WindowOverflow8.text"]
unsafe extern "C" fn _WindowOverflow8() {
asm!(
"
s32e a0, a9, -16
l32e a0, a1, -12
s32e a1, a9, -12
s32e a2, a9, -8
s32e a3, a9, -4
s32e a4, a0, -32
s32e a5, a0, -28
s32e a6, a0, -24
s32e a7, a0, -20
rfwo
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".WindowUnderflow8.text"]
unsafe extern "C" fn _WindowUnderflow8() {
asm!(
"
l32e a0, a9, -16
l32e a1, a9, -12
l32e a2, a9, -8
l32e a7, a1, -12
l32e a3, a9, -4
l32e a4, a7, -32
l32e a5, a7, -28
l32e a6, a7, -24
l32e a7, a7, -20
rfwu
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".WindowOverflow12.text"]
unsafe extern "C" fn _WindowOverflow12() {
asm!(
"
s32e a0, a13, -16
l32e a0, a1, -12
s32e a1, a13, -12
s32e a2, a13, -8
s32e a3, a13, -4
s32e a4, a0, -48
s32e a5, a0, -44
s32e a6, a0, -40
s32e a7, a0, -36
s32e a8, a0, -32
s32e a9, a0, -28
s32e a10, a0, -24
s32e a11, a0, -20
rfwo
",
options(noreturn)
);
}
#[naked]
#[no_mangle]
#[link_section = ".WindowUnderflow12.text"]
unsafe extern "C" fn _WindowUnderflow12() {
asm!(
"
l32e a0, a13, -16
l32e a1, a13, -12
l32e a2, a13, -8
l32e a11, a1, -12
l32e a3, a13, -4
l32e a4, a11, -48
l32e a5, a11, -44
l32e a6, a11, -40
l32e a7, a11, -36
l32e a8, a11, -32
l32e a9, a11, -28
l32e a10, a11, -24
l32e a11, a11, -20
rfwu
",
options(noreturn)
);
}
+3
View File
@@ -0,0 +1,3 @@
//! Interrupts
include!(concat!(env!("OUT_DIR"), "/interrupt_level_masks.rs"));
+172
View File
@@ -0,0 +1,172 @@
//! Minimal startup/runtime for Xtensa LX CPUs.
//!
//! ## Minimum Supported Rust Version (MSRV)
//!
//! This crate is guaranteed to compile on stable Rust 1.65 and up. It might
//! compile with older versions but that may change in any new patch release.
//!
//! ## Feature Flags
#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![allow(asm_sub_register, named_asm_labels)]
#![feature(asm_experimental_arch, naked_functions)]
#![no_std]
use core::{
arch::asm,
ptr::{addr_of, addr_of_mut},
};
pub use macros::{entry, exception, interrupt, pre_init};
pub use r0::{init_data, zero_bss};
pub use xtensa_lx;
pub mod exception;
pub mod interrupt;
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn DefaultPreInit() {}
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
// These symbols come from `link.x`
extern "C" {
static mut _bss_start: u32;
static mut _bss_end: u32;
static mut _data_start: u32;
static mut _data_end: u32;
static _sidata: u32;
static mut _init_start: u32;
}
extern "Rust" {
// This symbol will be provided by the user via `#[entry]`
fn main() -> !;
// This symbol will be provided by the user via `#[pre_init]`
fn __pre_init();
fn __post_init();
fn __zero_bss() -> bool;
fn __init_data() -> bool;
}
__pre_init();
if __zero_bss() {
r0::zero_bss(addr_of_mut!(_bss_start), addr_of_mut!(_bss_end));
}
if __init_data() {
r0::init_data(addr_of_mut!(_data_start), addr_of_mut!(_data_end), &_sidata);
}
// Copy of data segment is done by bootloader
// According to 4.4.6.2 of the xtensa isa, ccount and compare are undefined on
// reset, set all values to zero to disable
reset_internal_timers();
// move vec table
set_vecbase(addr_of!(_init_start));
__post_init();
main();
}
#[doc(hidden)]
#[no_mangle]
#[rustfmt::skip]
pub unsafe extern "Rust" fn default_post_init() {}
// We redefine these functions to avoid pulling in `xtensa-lx` as a dependency:
#[doc(hidden)]
#[inline]
unsafe fn reset_internal_timers() {
#[cfg(any(
XCHAL_HAVE_TIMER0,
XCHAL_HAVE_TIMER1,
XCHAL_HAVE_TIMER2,
XCHAL_HAVE_TIMER3
))]
{
let value = 0;
cfg_asm!(
{
#[cfg(XCHAL_HAVE_TIMER0)]
"wsr.ccompare0 {0}",
#[cfg(XCHAL_HAVE_TIMER1)]
"wsr.ccompare1 {0}",
#[cfg(XCHAL_HAVE_TIMER2)]
"wsr.ccompare2 {0}",
#[cfg(XCHAL_HAVE_TIMER3)]
"wsr.ccompare3 {0}",
"isync",
}, in(reg) value, options(nostack));
}
}
// CPU Interrupts
extern "C" {
#[cfg(XCHAL_HAVE_TIMER0)]
pub fn Timer0(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER1)]
pub fn Timer1(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER2)]
pub fn Timer2(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER3)]
pub fn Timer3(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_PROFILING)]
pub fn Profiling(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_SOFTWARE0)]
pub fn Software0(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_SOFTWARE1)]
pub fn Software1(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_NMI)]
pub fn NMI(level: u32, save_frame: &mut crate::exception::Context);
}
#[doc(hidden)]
#[inline]
unsafe fn set_vecbase(base: *const u32) {
asm!("wsr.vecbase {0}", in(reg) base, options(nostack));
}
#[doc(hidden)]
#[no_mangle]
#[rustfmt::skip]
pub extern "Rust" fn default_mem_hook() -> bool {
true // default to zeroing bss & initializing data
}
#[doc(hidden)]
#[macro_export]
macro_rules! cfg_asm {
(@inner, [$($x:tt)*], [$($opts:tt)*], ) => {
asm!($($x)* $($opts)*)
};
(@inner, [$($x:tt)*], [$($opts:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => {
#[cfg($meta)]
cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*);
#[cfg(not($meta))]
cfg_asm!(@inner, [$($x)*], [$($opts)*], $($rest)*)
};
(@inner, [$($x:tt)*], [$($opts:tt)*], $asm:literal, $($rest:tt)*) => {
cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*)
};
({$($asms:tt)*}, $($opts:tt)*) => {
cfg_asm!(@inner, [], [$($opts)*], $($asms)*)
};
}