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":"82a016b154dafe52b5e996f47f5208f7f53402cdcc6c8851cde9f3e218453846","CHANGELOG.md":"3be7189a41d49649a8c47d048b3a73a5f608d1646cbb9be23b47be8a29de7099","Cargo.toml":"f8d8aaf38593cd34423c0cad480b13c058aa82561c49645c4b7c121d3ba0cf69","Cargo.toml.orig":"28509b8bb95851f45675bdd29ccfda1995359ff2de9f2a0b98c2e4ce24664683","README.md":"e3c9e9ac84f212fba481f1c686c665fafd7e03fe6d9b5802ef9d5aec3fc94a5f","build.rs":"f156050287562da339b74992f9a9bb3bf150706d5e24a467993941aa396dd96c","config/xtensa_esp32.toml":"fe8c8c030ba7d060911826938e8b24cb673b97d447a72339633a1441aa6b099e","config/xtensa_esp32s2.toml":"ebf65bd41d1e1302a5207eaca8e1aa648098774f77971bcd7b1266232dfe7fed","config/xtensa_esp32s3.toml":"920358c8a6c6c61a6d6bce9ddb141f171f2a9566a91bdffd3d2109526e23ef15","exception-esp32.x.jinja":"d9f5a00b0b15885cba1155bafba8c88e55751455f42fa654b8994254291b1ad0","interrupt_level_masks.rs.jinja":"1e88776c1ca74f3ecd3c425c772efda0d5c7e5c8b7c8f14556e49082ee097406","src/exception.rs":"c9356bc3baccfa750fa793b63baa28fbaa585b6986ed40bddb0488c66b98a77e","src/exception/asm.rs":"e2e0558f94c2769c5b29bd1ccc33ab71a863c69c828ceac401b261c6f1d75dea","src/exception/context.rs":"471b06e6b30adc68db16dd10571e0d030b28912deab3a29bd7472a4a188a1422","src/interrupt.rs":"10fc81bd0fc29339902f0bbd71882015c9132049c0129f655f36c402da93b7a0","src/lib.rs":"d9e8d889f98b460f3585d8e0d7ddfc25b07edba8c320c660cf7f11c155683672","xtensa.in.x":"5c90e18211b9bd67e4c99f9719cb9c9b6eefd177130ff401207b43d0088a6200"},"package":"5c0307d03dadbf95633942e13901984f2059df4c963367348168cbd21c962669"}
+6
View File
@@ -0,0 +1,6 @@
{
"git": {
"sha1": "bdfec3781fd59c4d51173706e3cbee3760bbf8b6"
},
"path_in_vcs": "xtensa-lx-rt"
}
+36
View File
@@ -0,0 +1,36 @@
# Changelog
All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
### Changed
### Fixed
### Removed
## 0.17.2 - 2024-11-20
### Fixed
- Fixed saving the state of the FPU co-processor. (#2311)
### Removed
## 0.17.1 - 2024-09-02
### Added
- Better diagnostics when using floats inside an interrupt handler when using the default hard fault handler (#2044)
### Fixed
- Store state of FP coprocessor in stack memory (#2057)
## Initial releases
+89
View File
@@ -0,0 +1,89 @@
# 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"
rust-version = "1.65"
name = "xtensa-lx-rt"
version = "0.17.2"
build = "build.rs"
autolib = false
autobins = false
autoexamples = false
autotests = false
autobenches = false
description = "Minimal startup/runtime for Xtensa LX CPUs"
readme = "README.md"
keywords = [
"xtensa",
"lx",
"register",
"peripheral",
]
categories = [
"embedded",
"hardware-support",
"no-std",
]
license = "MIT OR Apache-2.0"
repository = "https://github.com/esp-rs/esp-hal"
[package.metadata.docs.rs]
features = ["esp32"]
[lib]
name = "xtensa_lx_rt"
path = "src/lib.rs"
[dependencies.bare-metal]
version = "1.0.0"
[dependencies.document-features]
version = "0.2.10"
[dependencies.macros]
version = "0.2.2"
package = "xtensa-lx-rt-proc-macros"
[dependencies.r0]
version = "1.0.0"
[dependencies.xtensa-lx]
version = "0.9.0"
[build-dependencies.anyhow]
version = "1.0.89"
[build-dependencies.enum-as-inner]
version = "0.6.1"
[build-dependencies.minijinja]
version = "2.3.1"
[build-dependencies.serde]
version = "1.0.210"
features = ["derive"]
[build-dependencies.strum]
version = "0.26.3"
features = ["derive"]
[build-dependencies.toml]
version = "0.8.19"
[features]
esp32 = []
esp32s2 = []
esp32s3 = []
float-save-restore = []
[lints.rust]
unexpected_cfgs = "allow"
+39
View File
@@ -0,0 +1,39 @@
# `xtensa-lx-rt`
[![Crates.io](https://img.shields.io/crates/v/xtensa-lx-rt?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/xtensa-lx-rt)
[![docs.rs](https://img.shields.io/docsrs/xtensa-lx-rt?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/xtensa-lx-rt)
![Crates.io](https://img.shields.io/crates/l/xtensa-lx-rt?labelColor=1C2C2E&style=flat-square)
[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org)
Minimal runtime/startup for Xtensa LX processors. This crate currently supports the following CPU's:
| Feature | Supported CPUs |
| --------- | ---------------- |
| `esp32` | ESP32 (_LX6_) |
| `esp32s2` | ESP32-S2 (_LX7_) |
| `esp32s3` | ESP32-S3 (_LX7_) |
## I get linker errors when I build for debug
Xtensa only provides a small code space for exceptions to fit inside, when building an unoptimized build the code size of a exception handler may exceed that size, causing a linker error. To fix this, you should always optimize this crate, even in debug builds. Adding the following to your projects `Cargo.toml` should do the trick.
```toml
[profile.dev.package.xtensa-lx-rt]
opt-level = 'z'
```
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the
work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.
+243
View File
@@ -0,0 +1,243 @@
use std::{
collections::{HashMap, HashSet},
env,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
};
use anyhow::Result;
use enum_as_inner::EnumAsInner;
use minijinja::{context, Environment};
use serde::Deserialize;
use strum::{Display, EnumIter, EnumString};
/// The chips which are present in the xtensa-overlays repository
///
/// When `.to_string()` is called on a variant, the resulting string is the path
/// to the chip's corresponding directory.
#[derive(Debug, Clone, Copy, PartialEq, Display, EnumIter, Deserialize)]
enum Chip {
#[strum(to_string = "xtensa_esp32")]
Esp32,
#[strum(to_string = "xtensa_esp32s2")]
Esp32s2,
#[strum(to_string = "xtensa_esp32s3")]
Esp32s3,
}
/// The valid interrupt types declared in the `core-isa.h` headers
#[derive(Debug, Clone, Copy, PartialEq, EnumString, Deserialize)]
enum InterruptType {
#[strum(serialize = "XTHAL_INTTYPE_EXTERN_EDGE")]
ExternEdge,
#[strum(serialize = "XTHAL_INTTYPE_EXTERN_LEVEL")]
ExternLevel,
#[strum(serialize = "XTHAL_INTTYPE_NMI")]
Nmi,
#[strum(serialize = "XTHAL_INTTYPE_PROFILING")]
Profiling,
#[strum(serialize = "XTHAL_INTTYPE_SOFTWARE")]
Software,
#[strum(serialize = "XTHAL_INTTYPE_TIMER")]
Timer,
#[strum(serialize = "XTHAL_TIMER_UNCONFIGURED")]
TimerUnconfigured,
}
/// The allowable value types for definitions
#[derive(Debug, Clone, PartialEq, EnumAsInner, Deserialize)]
enum Value {
Integer(i64),
Interrupt(InterruptType),
String(String),
}
fn main() -> Result<()> {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
// Put the linker script somewhere the linker can find it
println!("cargo:rustc-link-search={}", out.display());
File::create(out.join("link.x"))?.write_all(include_bytes!("xtensa.in.x"))?;
handle_esp32()?;
// Only re-run the build script when xtensa.in.x is changed,
// instead of when any part of the source code changes.
println!("cargo:rerun-if-changed=xtensa.in.x");
Ok(())
}
fn handle_esp32() -> Result<()> {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
let rustflags = env::var_os("CARGO_ENCODED_RUSTFLAGS")
.unwrap()
.into_string()
.unwrap();
let mut features_to_disable: HashSet<String> = HashSet::new();
// Users can pass -Ctarget-feature to the compiler multiple times, so we have to
// handle that
let target_flags = rustflags
.split(0x1f as char)
.filter(|s| s.starts_with("target-feature="))
.filter_map(|s| s.strip_prefix("target-feature="));
for tf in target_flags {
tf.split(',')
.map(|s| s.trim())
.filter(|s| s.starts_with('-'))
.filter_map(|s| s.strip_prefix('-'))
.filter_map(rustc_feature_to_xchal_have)
.for_each(|s| {
features_to_disable.insert(s.to_owned());
})
}
let chip = match (
cfg!(feature = "esp32"),
cfg!(feature = "esp32s2"),
cfg!(feature = "esp32s3"),
) {
(true, false, false) => Chip::Esp32,
(false, true, false) => Chip::Esp32s2,
(false, false, true) => Chip::Esp32s3,
_ => panic!("Either the esp32, esp32s2, esp32s3 feature must be enabled"),
};
let isa_toml = fs::read_to_string(format!("config/{chip}.toml"))?;
let isa_config: HashMap<String, Value> = toml::from_str(&isa_toml)?;
inject_cfgs(&isa_config, &features_to_disable);
inject_cpu_cfgs(&isa_config);
generate_exception_x(out, &isa_config)?;
generate_interrupt_level_masks(out, &isa_config)?;
Ok(())
}
fn generate_interrupt_level_masks(out: &Path, isa_config: &HashMap<String, Value>) -> Result<()> {
let exception_source_template = include_str!("interrupt_level_masks.rs.jinja");
let mut env = Environment::new();
env.add_template("interrupt_level_masks.rs", exception_source_template)?;
let template = env.get_template("interrupt_level_masks.rs").unwrap();
let exception_source = template.render(context! {
XCHAL_INTLEVEL1_MASK => isa_config.get("XCHAL_INTLEVEL1_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL2_MASK => isa_config.get("XCHAL_INTLEVEL2_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL3_MASK => isa_config.get("XCHAL_INTLEVEL3_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL4_MASK => isa_config.get("XCHAL_INTLEVEL4_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL5_MASK => isa_config.get("XCHAL_INTLEVEL5_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL6_MASK => isa_config.get("XCHAL_INTLEVEL6_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL7_MASK => isa_config.get("XCHAL_INTLEVEL7_MASK").unwrap().as_integer(),
})?;
File::create(out.join("interrupt_level_masks.rs"))?.write_all(exception_source.as_bytes())?;
Ok(())
}
fn generate_exception_x(out: &Path, isa_config: &HashMap<String, Value>) -> Result<()> {
let exception_source_template = include_str!("exception-esp32.x.jinja");
let mut env = Environment::new();
env.add_template("exception.x", exception_source_template)?;
let template = env.get_template("exception.x")?;
let exception_source = template.render(
context! {
XCHAL_WINDOW_OF4_VECOFS => isa_config.get("XCHAL_WINDOW_OF4_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_UF4_VECOFS => isa_config.get("XCHAL_WINDOW_UF4_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_OF8_VECOFS => isa_config.get("XCHAL_WINDOW_OF8_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_UF8_VECOFS => isa_config.get("XCHAL_WINDOW_UF8_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_OF12_VECOFS => isa_config.get("XCHAL_WINDOW_OF12_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_UF12_VECOFS => isa_config.get("XCHAL_WINDOW_UF12_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL2_VECOFS => isa_config.get("XCHAL_INTLEVEL2_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL3_VECOFS => isa_config.get("XCHAL_INTLEVEL3_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL4_VECOFS => isa_config.get("XCHAL_INTLEVEL4_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL5_VECOFS => isa_config.get("XCHAL_INTLEVEL5_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL6_VECOFS => isa_config.get("XCHAL_INTLEVEL6_VECOFS").unwrap().as_integer(),
XCHAL_NMI_VECOFS => isa_config.get("XCHAL_NMI_VECOFS").unwrap().as_integer(),
XCHAL_KERNEL_VECOFS => isa_config.get("XCHAL_KERNEL_VECOFS").unwrap().as_integer(),
XCHAL_USER_VECOFS => isa_config.get("XCHAL_USER_VECOFS").unwrap().as_integer(),
XCHAL_DOUBLEEXC_VECOFS => isa_config.get("XCHAL_DOUBLEEXC_VECOFS").unwrap().as_integer(),
}
)?;
File::create(out.join("exception.x"))?.write_all(exception_source.as_bytes())?;
Ok(())
}
fn inject_cfgs(isa_config: &HashMap<String, Value>, disabled_features: &HashSet<String>) {
for (key, value) in isa_config {
if key.starts_with("XCHAL_HAVE")
&& *value.as_integer().unwrap_or(&0) != 0
&& !disabled_features.contains(key)
{
println!("cargo:rustc-cfg={}", key);
}
}
}
fn inject_cpu_cfgs(isa_config: &HashMap<String, Value>) {
for (key, value) in isa_config {
if (key.starts_with("XCHAL_TIMER")
|| key.starts_with("XCHAL_PROFILING")
|| key.starts_with("XCHAL_NMI"))
&& value.as_integer().is_some()
{
let mut s = String::from(key.trim_end_matches("_INTERRUPT"));
let first = s.chars().position(|c| c == '_').unwrap() + 1;
s.insert_str(first, "HAVE_");
println!("cargo:rustc-cfg={}", s);
}
}
if let Some(value) = isa_config
.get("XCHAL_INTTYPE_MASK_SOFTWARE")
.and_then(|v| v.as_integer())
{
for i in 0..value.count_ones() {
println!("cargo:rustc-cfg=XCHAL_HAVE_SOFTWARE{}", i);
}
}
}
fn rustc_feature_to_xchal_have(s: &str) -> Option<&str> {
// List of rustc features taken from here:
// https://github.com/esp-rs/rust/blob/84ecb3f010525cb1b2e7d4da306099c2eaa3e6cd/compiler/rustc_codegen_ssa/src/target_features.rs#L278
// unlikely to change
Some(match s {
"fp" => "XCHAL_HAVE_FP",
"windowed" => "XCHAL_HAVE_WINDOWED",
"bool" => "XCHAL_HAVE_BOOLEANS",
"loop" => "XCHAL_HAVE_LOOPS",
"sext" => "XCHAL_HAVE_SEXT",
"nsa" => "XCHAL_HAVE_NSA",
"mul32" => "XCHAL_HAVE_MUL32",
"mul32high" => "XCHAL_HAVE_MUL32_HIGH",
"div32" => "XCHAL_HAVE_DIV32",
"mac16" => "XCHAL_HAVE_MAC16",
"dfpaccel" => "XCHAL_HAVE_DFP",
"s32c1i" => "XCHAL_HAVE_S32C1I",
"threadptr" => "XCHAL_HAVE_THREADPTR",
"extendedl32r" => "XCHAL_HAVE_ABSOLUTE_LITERALS",
"debug" => "XCHAL_HAVE_DEBUG",
"exception" => "XCHAL_HAVE_EXCEPTIONS",
"highpriinterrupts" => "XCHAL_HAVE_HIGHPRI_INTERRUPTS",
"coprocessor" => "XCHAL_HAVE_CP",
"interrupt" => "XCHAL_HAVE_INTERRUPTS",
"rvector" => "XCHAL_HAVE_VECTOR_SELECT",
"prid" => "XCHAL_HAVE_PRID",
"regprotect" => "XCHAL_HAVE_MIMIC_CACHEATTR",
"miscsr" => return None, // XCHAL_NUM_MISC_REGS
"timerint" => return None, // XCHAL_NUM_TIMERS
"atomctl" => return None,
"memctl" => return None,
_ => return None,
})
}
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
File diff suppressed because it is too large. Load diff
+96
View File
@@ -0,0 +1,96 @@
/* exception vector for the ESP32, requiring high priority interrupts and register window support */
/* high level exception/interrupt routines, which can be override with Rust functions */
PROVIDE(__exception = __default_exception);
PROVIDE(__user_exception = __default_user_exception);
PROVIDE(__double_exception = __default_double_exception);
PROVIDE(__level_1_interrupt = __default_interrupt);
PROVIDE(__level_2_interrupt = __default_interrupt);
PROVIDE(__level_3_interrupt = __default_interrupt);
PROVIDE(__level_4_interrupt = __default_interrupt);
PROVIDE(__level_5_interrupt = __default_interrupt);
PROVIDE(__level_6_interrupt = __default_interrupt);
PROVIDE(__level_7_interrupt = __default_interrupt);
/* high level CPU interrupts */
PROVIDE(Timer0 = __default_user_exception);
PROVIDE(Timer1 = __default_user_exception);
PROVIDE(Timer2 = __default_user_exception);
PROVIDE(Timer3 = __default_user_exception);
PROVIDE(Profiling = __default_user_exception);
PROVIDE(NMI = __default_user_exception);
PROVIDE(Software0 = __default_user_exception);
PROVIDE(Software1 = __default_user_exception);
/* low level exception/interrupt, which must be overridden using naked functions */
PROVIDE(__naked_user_exception = __default_naked_exception);
PROVIDE(__naked_kernel_exception = __default_naked_exception);
PROVIDE(__naked_double_exception = __default_naked_double_exception);
PROVIDE(__naked_level_2_interrupt = __default_naked_level_2_interrupt);
PROVIDE(__naked_level_3_interrupt = __default_naked_level_3_interrupt);
PROVIDE(__naked_level_4_interrupt = __default_naked_level_4_interrupt);
PROVIDE(__naked_level_5_interrupt = __default_naked_level_5_interrupt);
PROVIDE(__naked_level_6_interrupt = __default_naked_level_6_interrupt);
PROVIDE(__naked_level_7_interrupt = __default_naked_level_7_interrupt);
/* needed to force inclusion of the vectors */
EXTERN(__default_exception);
EXTERN(__default_double_exception);
EXTERN(__default_interrupt);
EXTERN(__default_naked_exception);
EXTERN(__default_naked_double_exception);
EXTERN(__default_naked_level_2_interrupt);
EXTERN(__default_naked_level_3_interrupt);
EXTERN(__default_naked_level_4_interrupt);
EXTERN(__default_naked_level_5_interrupt);
EXTERN(__default_naked_level_6_interrupt);
EXTERN(__default_naked_level_7_interrupt);
/* Define output sections */
SECTIONS {
.vectors :
{
/*
Each vector has 64 bytes that it must fit inside. For each vector we calculate the size of the previous one,
and subtract that from 64 and start the new vector there.
*/
_init_start = ABSOLUTE(.);
. = ALIGN(64);
KEEP(*(.WindowOverflow4.text));
. = ALIGN(64);
KEEP(*(.WindowUnderflow4.text));
. = ALIGN(64);
KEEP(*(.WindowOverflow8.text));
. = ALIGN(64);
KEEP(*(.WindowUnderflow8.text));
. = ALIGN(64);
KEEP(*(.WindowOverflow12.text));
. = ALIGN(64);
KEEP(*(.WindowUnderflow12.text));
. = ALIGN(64);
KEEP(*(.Level2InterruptVector.text));
. = ALIGN(64);
KEEP(*(.Level3InterruptVector.text));
. = ALIGN(64);
KEEP(*(.Level4InterruptVector.text));
. = ALIGN(64);
KEEP(*(.Level5InterruptVector.text));
. = ALIGN(64);
KEEP(*(.DebugExceptionVector.text));
. = ALIGN(64);
KEEP(*(.NMIExceptionVector.text));
. = ALIGN(64);
KEEP(*(.KernelExceptionVector.text));
. = ALIGN(64);
KEEP(*(.UserExceptionVector.text));
. = ALIGN(128);
KEEP(*(.DoubleExceptionVector.text));
. = ALIGN(64);
. = ALIGN(0x400);
_init_end = ABSOLUTE(.);
} > vectors_seg
}
+23
View File
@@ -0,0 +1,23 @@
pub enum CpuInterruptLevel {
Level1,
Level2,
Level3,
Level4,
Level5,
Level6,
Level7,
}
impl CpuInterruptLevel {
pub fn mask(&self) -> u32 {
match &self {
CpuInterruptLevel::Level1 => {{ XCHAL_INTLEVEL1_MASK }}u32,
CpuInterruptLevel::Level2 => {{ XCHAL_INTLEVEL2_MASK }}u32,
CpuInterruptLevel::Level3 => {{ XCHAL_INTLEVEL3_MASK }}u32,
CpuInterruptLevel::Level4 => {{ XCHAL_INTLEVEL4_MASK }}u32,
CpuInterruptLevel::Level5 => {{ XCHAL_INTLEVEL5_MASK }}u32,
CpuInterruptLevel::Level6 => {{ XCHAL_INTLEVEL6_MASK }}u32,
CpuInterruptLevel::Level7 => {{ XCHAL_INTLEVEL7_MASK }}u32,
}
}
}
+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)*)
};
}
+79
View File
@@ -0,0 +1,79 @@
/* before memory.x to allow override */
ENTRY(Reset)
INCLUDE memory.x
/* after memory.x to allow override */
PROVIDE(__pre_init = DefaultPreInit);
PROVIDE(__zero_bss = default_mem_hook);
PROVIDE(__init_data = default_mem_hook);
PROVIDE(__post_init = default_post_init);
INCLUDE exception.x
SECTIONS {
.text : ALIGN(4)
{
_stext = .;
. = ALIGN (4);
_text_start = ABSOLUTE(.);
. = ALIGN (4);
*(.literal .text .literal.* .text.*)
_text_end = ABSOLUTE(.);
_etext = .;
} > ROTEXT
.rodata : ALIGN(4)
{
_rodata_start = ABSOLUTE(.);
. = ALIGN (4);
*(.rodata .rodata.*)
_rodata_end = ABSOLUTE(.);
} > RODATA
.data : ALIGN(4)
{
_data_start = ABSOLUTE(.);
. = ALIGN (4);
*(.data .data.*)
_data_end = ABSOLUTE(.);
} > RWDATA AT > RODATA
/* LMA of .data */
_sidata = LOADADDR(.data);
.bss (NOLOAD) : ALIGN(4)
{
_bss_start = ABSOLUTE(.);
. = ALIGN (4);
*(.bss .bss.* COMMON)
_bss_end = ABSOLUTE(.);
} > RWDATA
.noinit (NOLOAD) : ALIGN(4)
{
. = ALIGN(4);
*(.noinit .noinit.*)
} > RWDATA
.rwtext : ALIGN(4)
{
. = ALIGN (4);
*(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*)
} > RWTEXT
/* must be last segment using RWTEXT */
.text_heap_start (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_text_heap_start = ABSOLUTE(.);
} > RWTEXT
/* must be last segment using RWDATA */
.heap_start (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_heap_start = ABSOLUTE(.);
} > RWDATA
}