Add xtensa-lx and xtensa-lx-rt packages (#1813)
* Add the `xtensa-lx` package * Add the `xtensa-lx-rt` and `xtensa-lx-rt-proc-macros` packages * Exclude new packages from workspace, add to `xtask::Package` * rustfmt * clippy
This commit is contained in:
parent
14baad4625
commit
e33b060734
@ -21,4 +21,7 @@ exclude = [
|
||||
"extras/esp-wifishark",
|
||||
"extras/ieee802154-sniffer",
|
||||
"hil-test",
|
||||
"xtensa-lx",
|
||||
"xtensa-lx-rt",
|
||||
"xtensa-lx-rt/procmacros",
|
||||
]
|
||||
|
||||
@ -48,6 +48,8 @@ pub enum Package {
|
||||
EspWifi,
|
||||
Examples,
|
||||
HilTest,
|
||||
XtensaLx,
|
||||
XtensaLxRt,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter, ValueEnum, serde::Serialize)]
|
||||
|
||||
@ -126,7 +126,11 @@ struct GenerateEfuseFieldsArgs {
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct LintPackagesArgs {}
|
||||
struct LintPackagesArgs {
|
||||
/// Package(s) to target.
|
||||
#[arg(value_enum, default_values_t = Package::iter())]
|
||||
packages: Vec<Package>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Args)]
|
||||
struct RunElfArgs {
|
||||
@ -449,8 +453,8 @@ fn fmt_packages(workspace: &Path, args: FmtPackagesArgs) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn lint_packages(workspace: &Path, _args: LintPackagesArgs) -> Result<()> {
|
||||
let mut packages = Package::iter().collect::<Vec<_>>();
|
||||
fn lint_packages(workspace: &Path, args: LintPackagesArgs) -> Result<()> {
|
||||
let mut packages = args.packages;
|
||||
packages.sort();
|
||||
|
||||
for package in packages {
|
||||
@ -538,6 +542,19 @@ fn lint_packages(workspace: &Path, _args: LintPackagesArgs) -> Result<()> {
|
||||
],
|
||||
)?,
|
||||
|
||||
Package::XtensaLxRt => {
|
||||
for chip in [Chip::Esp32, Chip::Esp32s2, Chip::Esp32s3] {
|
||||
lint_package(
|
||||
&path,
|
||||
&[
|
||||
"-Zbuild-std=core",
|
||||
&format!("--target=xtensa-{chip}-none-elf"),
|
||||
&format!("--features={chip}"),
|
||||
],
|
||||
)?
|
||||
}
|
||||
}
|
||||
|
||||
// We will *not* check the following packages with `clippy`; this
|
||||
// may or may not change in the future:
|
||||
Package::Examples | Package::HilTest => {}
|
||||
|
||||
43
xtensa-lx-rt/Cargo.toml
Normal file
43
xtensa-lx-rt/Cargo.toml
Normal file
@ -0,0 +1,43 @@
|
||||
[package]
|
||||
name = "xtensa-lx-rt"
|
||||
version = "0.16.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
description = "Minimal startup/runtime for Xtensa LX CPUs"
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["xtensa", "lx", "register", "peripheral"]
|
||||
categories = ["embedded", "hardware-support", "no-std"]
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["esp32"]
|
||||
|
||||
[dependencies]
|
||||
bare-metal = "1.0.0"
|
||||
document-features = "0.2.8"
|
||||
macros = { version = "0.2.1", package = "xtensa-lx-rt-proc-macros", path = "./procmacros" }
|
||||
r0 = "1.0.0"
|
||||
xtensa-lx = { version = "0.9.0", path = "../xtensa-lx" }
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1.0.86"
|
||||
enum-as-inner = "0.6.0"
|
||||
minijinja = "2.0.3"
|
||||
serde = { version = "1.0.204", features = ["derive"] }
|
||||
strum = { version = "0.26.3", features = ["derive"] }
|
||||
toml = "0.8.10"
|
||||
|
||||
[features]
|
||||
## Save and restore float registers for exceptions
|
||||
float-save-restore = []
|
||||
|
||||
#! ### Chip Support Feature Flags
|
||||
## Target the ESP32
|
||||
esp32 = []
|
||||
## Target the ESP32-S2
|
||||
esp32s2 = []
|
||||
## Target the ESP32-S3
|
||||
esp32s3 = []
|
||||
|
||||
[lints.rust]
|
||||
unexpected_cfgs = "allow"
|
||||
39
xtensa-lx-rt/README.md
Normal file
39
xtensa-lx-rt/README.md
Normal file
@ -0,0 +1,39 @@
|
||||
# `xtensa-lx-rt`
|
||||
|
||||
[](https://crates.io/crates/xtensa-lx-rt)
|
||||
[](https://docs.rs/xtensa-lx-rt)
|
||||

|
||||
[](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
xtensa-lx-rt/build.rs
Normal file
243
xtensa-lx-rt/build.rs
Normal 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,
|
||||
})
|
||||
}
|
||||
1337
xtensa-lx-rt/config/xtensa_esp32.toml
Normal file
1337
xtensa-lx-rt/config/xtensa_esp32.toml
Normal file
File diff suppressed because it is too large
Load Diff
1475
xtensa-lx-rt/config/xtensa_esp32s2.toml
Normal file
1475
xtensa-lx-rt/config/xtensa_esp32s2.toml
Normal file
File diff suppressed because it is too large
Load Diff
1409
xtensa-lx-rt/config/xtensa_esp32s3.toml
Normal file
1409
xtensa-lx-rt/config/xtensa_esp32s3.toml
Normal file
File diff suppressed because it is too large
Load Diff
96
xtensa-lx-rt/exception-esp32.x.jinja
Normal file
96
xtensa-lx-rt/exception-esp32.x.jinja
Normal 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
xtensa-lx-rt/interrupt_level_masks.rs.jinja
Normal file
23
xtensa-lx-rt/interrupt_level_masks.rs.jinja
Normal 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,
|
||||
}
|
||||
}
|
||||
}
|
||||
24
xtensa-lx-rt/procmacros/Cargo.toml
Normal file
24
xtensa-lx-rt/procmacros/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "xtensa-lx-rt-proc-macros"
|
||||
authors = [
|
||||
"Jorge Aparicio <jorge@japaric.io>",
|
||||
"Arjan Mels <arjan@mels.email>",
|
||||
"Scott Mabin <scott@mabez.dev>",
|
||||
]
|
||||
version = "0.2.2"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
description = "Attributes re-exported in `xtensa-lx-rt`"
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
keywords = ["esp32", "xtensa-lx-rt", "runtime", "startup"]
|
||||
categories = ["embedded", "no-std"]
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[dependencies]
|
||||
darling = "0.20"
|
||||
proc-macro2 = "1.0"
|
||||
quote = "1.0"
|
||||
syn = { version = "2.0", features = ["extra-traits", "full"] }
|
||||
574
xtensa-lx-rt/procmacros/src/lib.rs
Normal file
574
xtensa-lx-rt/procmacros/src/lib.rs
Normal file
@ -0,0 +1,574 @@
|
||||
//! Internal implementation details of `xtensa-lx-rt`.
|
||||
//!
|
||||
//! Do not use this crate directly.
|
||||
|
||||
#![deny(warnings)]
|
||||
|
||||
extern crate proc_macro;
|
||||
|
||||
use std::collections::HashSet;
|
||||
|
||||
use darling::ast::NestedMeta;
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{
|
||||
parse,
|
||||
parse_macro_input,
|
||||
spanned::Spanned,
|
||||
AttrStyle,
|
||||
Attribute,
|
||||
FnArg,
|
||||
Ident,
|
||||
Item,
|
||||
ItemFn,
|
||||
ItemStatic,
|
||||
ReturnType,
|
||||
StaticMutability,
|
||||
Stmt,
|
||||
Type,
|
||||
Visibility,
|
||||
};
|
||||
|
||||
/// Marks a function as the main function to be called on program start
|
||||
#[proc_macro_attribute]
|
||||
pub fn entry(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut f = parse_macro_input!(input as ItemFn);
|
||||
|
||||
// check the function signature
|
||||
let valid_signature = f.sig.constness.is_none()
|
||||
&& f.vis == Visibility::Inherited
|
||||
&& f.sig.abi.is_none()
|
||||
&& f.sig.inputs.is_empty()
|
||||
&& f.sig.generics.params.is_empty()
|
||||
&& f.sig.generics.where_clause.is_none()
|
||||
&& f.sig.variadic.is_none()
|
||||
&& match f.sig.output {
|
||||
ReturnType::Default => false,
|
||||
ReturnType::Type(_, ref ty) => match **ty {
|
||||
Type::Never(_) => true,
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
if !valid_signature {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[entry]` function must have signature `[unsafe] fn() -> !`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
if !args.is_empty() {
|
||||
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let (statics, stmts) = match extract_static_muts(f.block.stmts) {
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
f.sig.ident = Ident::new(
|
||||
&format!("__xtensa_lx_rt_{}", f.sig.ident),
|
||||
Span::call_site(),
|
||||
);
|
||||
f.sig.inputs.extend(statics.iter().map(|statik| {
|
||||
let ident = &statik.ident;
|
||||
let ty = &statik.ty;
|
||||
let attrs = &statik.attrs;
|
||||
|
||||
// Note that we use an explicit `'static` lifetime for the entry point
|
||||
// arguments. This makes it more flexible, and is sound here, since the
|
||||
// entry will not be called again, ever.
|
||||
syn::parse::<FnArg>(
|
||||
quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &'static mut #ty).into(),
|
||||
)
|
||||
.unwrap()
|
||||
}));
|
||||
f.block.stmts = stmts;
|
||||
|
||||
let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
|
||||
let ident = &f.sig.ident;
|
||||
|
||||
let resource_args = statics
|
||||
.iter()
|
||||
.map(|statik| {
|
||||
let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
|
||||
let ident = &statik.ident;
|
||||
let ty = &statik.ty;
|
||||
let expr = &statik.expr;
|
||||
quote! {
|
||||
#(#cfgs)*
|
||||
{
|
||||
#(#attrs)*
|
||||
static mut #ident: #ty = #expr;
|
||||
&mut #ident
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Entry) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
|
||||
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#(#attrs)*
|
||||
#[doc(hidden)]
|
||||
#[export_name = "main"]
|
||||
pub unsafe extern "C" fn #tramp_ident() {
|
||||
#ident(
|
||||
#(#resource_args),*
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
#f
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Marks a function as the exception handler
|
||||
#[proc_macro_attribute]
|
||||
pub fn exception(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut f = parse_macro_input!(input as ItemFn);
|
||||
|
||||
if !args.is_empty() {
|
||||
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Exception) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let valid_signature = f.sig.constness.is_none()
|
||||
&& f.vis == Visibility::Inherited
|
||||
&& f.sig.abi.is_none()
|
||||
&& f.sig.inputs.len() <= 2
|
||||
&& f.sig.generics.params.is_empty()
|
||||
&& f.sig.generics.where_clause.is_none()
|
||||
&& f.sig.variadic.is_none()
|
||||
&& match f.sig.output {
|
||||
ReturnType::Default => true,
|
||||
ReturnType::Type(_, ref ty) => match **ty {
|
||||
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
|
||||
Type::Never(..) => true,
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
if !valid_signature {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[exception]` handlers must have signature `[unsafe] fn([ExceptionCause[, Context]) [-> !]`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let (statics, stmts) = match extract_static_muts(f.block.stmts) {
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site());
|
||||
f.sig.inputs.extend(statics.iter().map(|statik| {
|
||||
let ident = &statik.ident;
|
||||
let ty = &statik.ty;
|
||||
let attrs = &statik.attrs;
|
||||
syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
|
||||
.unwrap()
|
||||
}));
|
||||
f.block.stmts = stmts;
|
||||
|
||||
let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
|
||||
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#(#attrs)*
|
||||
#[doc(hidden)]
|
||||
#[export_name = "__user_exception"]
|
||||
#f
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Marks a function as the interrupt handler, with optional interrupt level
|
||||
/// indicated
|
||||
///
|
||||
/// When the function is also marked `#[naked]`, it is a low-level interrupt
|
||||
/// handler: no entry and exit code to store processor state will be generated.
|
||||
/// The user needs to ensure that all registers which are used are saved and
|
||||
/// restored and that the proper return instruction is used.
|
||||
#[proc_macro_attribute]
|
||||
pub fn interrupt(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let mut f: ItemFn = syn::parse(input).expect("`#[interrupt]` must be applied to a function");
|
||||
|
||||
let attr_args = match NestedMeta::parse_meta_list(args.into()) {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return TokenStream::from(darling::Error::from(e).write_errors());
|
||||
}
|
||||
};
|
||||
|
||||
if attr_args.len() > 1 {
|
||||
return parse::Error::new(
|
||||
Span::call_site(),
|
||||
"This attribute accepts zero or 1 arguments",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let mut level = 1;
|
||||
|
||||
if attr_args.len() == 1 {
|
||||
match &attr_args[0] {
|
||||
NestedMeta::Lit(syn::Lit::Int(lit_int)) => match lit_int.base10_parse::<u32>() {
|
||||
Ok(x) => level = x,
|
||||
Err(_) => {
|
||||
return parse::Error::new(
|
||||
Span::call_site(),
|
||||
"This attribute accepts an integer attribute",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
return parse::Error::new(
|
||||
Span::call_site(),
|
||||
"This attribute accepts an integer attribute",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::Interrupt) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let naked = f.attrs.iter().position(|x| eq(x, "naked")).is_some();
|
||||
|
||||
let ident_s = if naked {
|
||||
format!("__naked_level_{}_interrupt", level)
|
||||
} else {
|
||||
format!("__level_{}_interrupt", level)
|
||||
};
|
||||
|
||||
if naked && (level < 2 || level > 7) {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[naked]` `#[interrupt]` handlers must have interrupt level >=2 and <=7",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else if !naked && (level < 1 || level > 7) {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[interrupt]` handlers must have interrupt level >=1 and <=7",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
let valid_signature = f.sig.constness.is_none()
|
||||
&& f.vis == Visibility::Inherited
|
||||
&& f.sig.abi.is_none()
|
||||
&& ((!naked && f.sig.inputs.len() <= 2) || (naked && f.sig.inputs.len() == 0))
|
||||
&& f.sig.generics.params.is_empty()
|
||||
&& f.sig.generics.where_clause.is_none()
|
||||
&& f.sig.variadic.is_none()
|
||||
&& match f.sig.output {
|
||||
ReturnType::Default => true,
|
||||
ReturnType::Type(_, ref ty) => match **ty {
|
||||
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
|
||||
Type::Never(..) => true,
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
if !valid_signature {
|
||||
if naked {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[naked]` `#[interrupt]` handlers must have signature `[unsafe] fn() [-> !]`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
} else {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[interrupt]` handlers must have signature `[unsafe] fn([u32[, Context]]) [-> !]`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
}
|
||||
|
||||
let (statics, stmts) = match extract_static_muts(f.block.stmts.iter().cloned()) {
|
||||
Err(e) => return e.to_compile_error().into(),
|
||||
Ok(x) => x,
|
||||
};
|
||||
|
||||
let inputs = f.sig.inputs.clone();
|
||||
|
||||
let args = inputs.iter().map(|arg| match arg {
|
||||
syn::FnArg::Typed(x) => {
|
||||
let pat = &*x.pat;
|
||||
quote!(#pat)
|
||||
}
|
||||
_ => quote!(#arg),
|
||||
});
|
||||
|
||||
f.sig.ident = Ident::new(&format!("__xtensa_lx_6_{}", f.sig.ident), Span::call_site());
|
||||
f.sig.inputs.extend(statics.iter().map(|statik| {
|
||||
let ident = &statik.ident;
|
||||
let ty = &statik.ty;
|
||||
let attrs = &statik.attrs;
|
||||
syn::parse::<FnArg>(quote!(#[allow(non_snake_case)] #(#attrs)* #ident: &mut #ty).into())
|
||||
.unwrap()
|
||||
}));
|
||||
f.block.stmts = stmts;
|
||||
|
||||
let tramp_ident = Ident::new(&format!("{}_trampoline", f.sig.ident), Span::call_site());
|
||||
let ident = &f.sig.ident;
|
||||
|
||||
let resource_args = statics
|
||||
.iter()
|
||||
.map(|statik| {
|
||||
let (ref cfgs, ref attrs) = extract_cfgs(statik.attrs.clone());
|
||||
let ident = &statik.ident;
|
||||
let ty = &statik.ty;
|
||||
let expr = &statik.expr;
|
||||
quote! {
|
||||
#(#cfgs)*
|
||||
{
|
||||
#(#attrs)*
|
||||
static mut #ident: #ty = #expr;
|
||||
&mut #ident
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let (ref cfgs, ref attrs) = extract_cfgs(f.attrs.clone());
|
||||
|
||||
if naked {
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#(#attrs)*
|
||||
#[doc(hidden)]
|
||||
#[export_name = #ident_s]
|
||||
pub unsafe extern "C" fn #tramp_ident() {
|
||||
#ident(
|
||||
#(#resource_args),*
|
||||
)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[allow(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
#f
|
||||
)
|
||||
.into()
|
||||
} else {
|
||||
quote!(
|
||||
#(#cfgs)*
|
||||
#(#attrs)*
|
||||
#[doc(hidden)]
|
||||
#[export_name = #ident_s]
|
||||
pub unsafe extern "C" fn #tramp_ident(
|
||||
level: u32,
|
||||
frame: xtensa_lx_rt::exception::Context
|
||||
) {
|
||||
#ident(#(#args),*
|
||||
#(#resource_args),*
|
||||
)
|
||||
}
|
||||
|
||||
#[allow(clippy::inline_always)]
|
||||
#[inline(always)]
|
||||
#f
|
||||
)
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
/// Marks a function as the pre_init function. This function is called before
|
||||
/// main and *before the memory is initialized*.
|
||||
#[proc_macro_attribute]
|
||||
pub fn pre_init(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
let f = parse_macro_input!(input as ItemFn);
|
||||
|
||||
// check the function signature
|
||||
let valid_signature = f.sig.constness.is_none()
|
||||
&& f.vis == Visibility::Inherited
|
||||
&& f.sig.unsafety.is_some()
|
||||
&& f.sig.abi.is_none()
|
||||
&& f.sig.inputs.is_empty()
|
||||
&& f.sig.generics.params.is_empty()
|
||||
&& f.sig.generics.where_clause.is_none()
|
||||
&& f.sig.variadic.is_none()
|
||||
&& match f.sig.output {
|
||||
ReturnType::Default => true,
|
||||
ReturnType::Type(_, ref ty) => match **ty {
|
||||
Type::Tuple(ref tuple) => tuple.elems.is_empty(),
|
||||
_ => false,
|
||||
},
|
||||
};
|
||||
|
||||
if !valid_signature {
|
||||
return parse::Error::new(
|
||||
f.span(),
|
||||
"`#[pre_init]` function must have signature `unsafe fn()`",
|
||||
)
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
if !args.is_empty() {
|
||||
return parse::Error::new(Span::call_site(), "This attribute accepts no arguments")
|
||||
.to_compile_error()
|
||||
.into();
|
||||
}
|
||||
|
||||
if let Err(error) = check_attr_whitelist(&f.attrs, WhiteListCaller::PreInit) {
|
||||
return error;
|
||||
}
|
||||
|
||||
let attrs = f.attrs;
|
||||
let ident = f.sig.ident;
|
||||
let block = f.block;
|
||||
|
||||
quote!(
|
||||
#[export_name = "__pre_init"]
|
||||
#[allow(missing_docs)] // we make a private fn public, which can trigger this lint
|
||||
#(#attrs)*
|
||||
pub unsafe fn #ident() #block
|
||||
)
|
||||
.into()
|
||||
}
|
||||
|
||||
/// Extracts `static mut` vars from the beginning of the given statements
|
||||
fn extract_static_muts(
|
||||
stmts: impl IntoIterator<Item = Stmt>,
|
||||
) -> Result<(Vec<ItemStatic>, Vec<Stmt>), parse::Error> {
|
||||
let mut istmts = stmts.into_iter();
|
||||
|
||||
let mut seen = HashSet::new();
|
||||
let mut statics = vec![];
|
||||
let mut stmts = vec![];
|
||||
while let Some(stmt) = istmts.next() {
|
||||
match stmt {
|
||||
Stmt::Item(Item::Static(var)) => match var.mutability {
|
||||
StaticMutability::Mut(_) => {
|
||||
if seen.contains(&var.ident) {
|
||||
return Err(parse::Error::new(
|
||||
var.ident.span(),
|
||||
format!("the name `{}` is defined multiple times", var.ident),
|
||||
));
|
||||
}
|
||||
|
||||
seen.insert(var.ident.clone());
|
||||
statics.push(var);
|
||||
}
|
||||
StaticMutability::None => {
|
||||
stmts.push(Stmt::Item(Item::Static(var)));
|
||||
}
|
||||
_ => unimplemented!(), // `StaticMutability` is `#[non_exhaustive]`
|
||||
},
|
||||
_ => {
|
||||
stmts.push(stmt);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stmts.extend(istmts);
|
||||
|
||||
Ok((statics, stmts))
|
||||
}
|
||||
|
||||
fn extract_cfgs(attrs: Vec<Attribute>) -> (Vec<Attribute>, Vec<Attribute>) {
|
||||
let mut cfgs = vec![];
|
||||
let mut not_cfgs = vec![];
|
||||
|
||||
for attr in attrs {
|
||||
if eq(&attr, "cfg") {
|
||||
cfgs.push(attr);
|
||||
} else {
|
||||
not_cfgs.push(attr);
|
||||
}
|
||||
}
|
||||
|
||||
(cfgs, not_cfgs)
|
||||
}
|
||||
|
||||
enum WhiteListCaller {
|
||||
Entry,
|
||||
Exception,
|
||||
Interrupt,
|
||||
PreInit,
|
||||
}
|
||||
|
||||
fn check_attr_whitelist(attrs: &[Attribute], caller: WhiteListCaller) -> Result<(), TokenStream> {
|
||||
let whitelist = &[
|
||||
"doc",
|
||||
"link_section",
|
||||
"cfg",
|
||||
"allow",
|
||||
"warn",
|
||||
"deny",
|
||||
"forbid",
|
||||
"cold",
|
||||
"ram",
|
||||
];
|
||||
|
||||
'o: for attr in attrs {
|
||||
for val in whitelist {
|
||||
if eq(&attr, &val) {
|
||||
continue 'o;
|
||||
}
|
||||
}
|
||||
|
||||
let err_str = match caller {
|
||||
WhiteListCaller::Entry => "this attribute is not allowed on a xtensa-lx-rt entry point",
|
||||
WhiteListCaller::Exception => {
|
||||
"this attribute is not allowed on an exception handler controlled by xtensa-lx-rt"
|
||||
}
|
||||
WhiteListCaller::Interrupt => {
|
||||
if eq(&attr, "naked") {
|
||||
continue 'o;
|
||||
}
|
||||
|
||||
"this attribute is not allowed on an interrupt handler controlled by xtensa-lx-rt"
|
||||
}
|
||||
WhiteListCaller::PreInit => {
|
||||
"this attribute is not allowed on a pre-init controlled by xtensa-lx-rt"
|
||||
}
|
||||
};
|
||||
|
||||
return Err(parse::Error::new(attr.span(), &err_str)
|
||||
.to_compile_error()
|
||||
.into());
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Returns `true` if `attr.path` matches `name`
|
||||
fn eq(attr: &Attribute, name: &str) -> bool {
|
||||
attr.style == AttrStyle::Outer && attr.path().is_ident(name)
|
||||
}
|
||||
121
xtensa-lx-rt/src/exception.rs
Normal file
121
xtensa-lx-rt/src/exception.rs
Normal 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)]
|
||||
#[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,
|
||||
}
|
||||
677
xtensa-lx-rt/src/exception/asm.rs
Normal file
677
xtensa-lx-rt/src/exception/asm.rs
Normal file
@ -0,0 +1,677 @@
|
||||
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.
|
||||
// Additionally there is a chunk of memory reserved for spilled registers.
|
||||
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
|
||||
.set XT_STK_F64R_LO, 132 // Registers for double support option
|
||||
.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 */
|
||||
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
|
||||
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
|
||||
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")))]
|
||||
"
|
||||
/* Re-enable coprocessor(s) after ISR */
|
||||
movi a3, 8 /* XCHAL_CP_MAXCFG */
|
||||
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));
|
||||
}
|
||||
427
xtensa-lx-rt/src/exception/context.rs
Normal file
427
xtensa-lx-rt/src/exception/context.rs
Normal file
@ -0,0 +1,427 @@
|
||||
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 assembly_esp32
|
||||
#[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(all(feature = "float-save-restore", XCHAL_HAVE_DFP_ACCEL))]
|
||||
pub F64R_LO: u32,
|
||||
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_DFP_ACCEL))]
|
||||
pub F64R_HI: u32,
|
||||
#[cfg(all(feature = "float-save-restore", XCHAL_HAVE_DFP_ACCEL))]
|
||||
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,
|
||||
}
|
||||
|
||||
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) {
|
||||
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
xtensa-lx-rt/src/interrupt.rs
Normal file
3
xtensa-lx-rt/src/interrupt.rs
Normal file
@ -0,0 +1,3 @@
|
||||
//! Interrupts
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/interrupt_level_masks.rs"));
|
||||
172
xtensa-lx-rt/src/lib.rs
Normal file
172
xtensa-lx-rt/src/lib.rs
Normal 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
xtensa-lx-rt/xtensa.in.x
Normal file
79
xtensa-lx-rt/xtensa.in.x
Normal 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
|
||||
}
|
||||
24
xtensa-lx/Cargo.toml
Normal file
24
xtensa-lx/Cargo.toml
Normal file
@ -0,0 +1,24 @@
|
||||
[package]
|
||||
name = "xtensa-lx"
|
||||
version = "0.9.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.65"
|
||||
description = "Low-level access to Xtensa LX processors and peripherals"
|
||||
repository = "https://github.com/esp-rs/esp-hal"
|
||||
license = "MIT OR Apache-2.0"
|
||||
categories = ["embedded", "hardware-support", "no-std"]
|
||||
keywords = ["lx", "peripheral", "register", "xtensa"]
|
||||
links = "xtensa-lx"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
features = ["spin"]
|
||||
|
||||
[dependencies]
|
||||
bare-metal = "1.0.0"
|
||||
document-features = "0.2.8"
|
||||
mutex-trait = "0.2.0"
|
||||
spin = { version = "0.9.8", optional = true }
|
||||
|
||||
[features]
|
||||
## Use the [spin] package for synchronization
|
||||
spin = ["dep:spin"]
|
||||
32
xtensa-lx/README.md
Normal file
32
xtensa-lx/README.md
Normal file
@ -0,0 +1,32 @@
|
||||
# `xtensa-lx`
|
||||
|
||||
[](https://crates.io/crates/xtensa-lx)
|
||||
[](https://docs.rs/xtensa-lx)
|
||||

|
||||
[](https://matrix.to/#/#esp-rs:matrix.org)
|
||||
|
||||
Low level access to Xtensa LX processors. This crate currently supports the following CPUs:
|
||||
|
||||
| Feature | Supported CPUs |
|
||||
| --------- | ---------------- |
|
||||
| `esp32` | ESP32 (_LX6_) |
|
||||
| `esp32s2` | ESP32-S2 (_LX7_) |
|
||||
| `esp32s3` | ESP32-S3 (_LX7_) |
|
||||
|
||||
## [Documentation](https://docs.rs/crate/xtensa-lx)
|
||||
|
||||
## 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.
|
||||
6
xtensa-lx/build.rs
Normal file
6
xtensa-lx/build.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
fn main() {
|
||||
let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
|
||||
println!("cargo:rustc-link-search={}", out_dir.display());
|
||||
}
|
||||
174
xtensa-lx/src/interrupt.rs
Normal file
174
xtensa-lx/src/interrupt.rs
Normal file
@ -0,0 +1,174 @@
|
||||
//! Interrupts
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
pub use bare_metal::CriticalSection;
|
||||
|
||||
/// Trait for enums of external interrupt numbers.
|
||||
///
|
||||
/// This trait should be implemented by a peripheral access crate (PAC)
|
||||
/// on its enum of available external interrupts for a specific device.
|
||||
/// Each variant must convert to a u16 of its interrupt number,
|
||||
/// which is its exception number - 16.
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// This trait must only be implemented on enums of device interrupts. Each
|
||||
/// enum variant must represent a distinct value (no duplicates are permitted),
|
||||
/// and must always return the same value (do not change at runtime).
|
||||
///
|
||||
/// These requirements ensure safe nesting of critical sections.
|
||||
pub unsafe trait InterruptNumber: Copy {
|
||||
/// Return the interrupt number associated with this variant.
|
||||
///
|
||||
/// See trait documentation for safety requirements.
|
||||
fn number(self) -> u16;
|
||||
}
|
||||
|
||||
/// Disables all interrupts and return the previous settings
|
||||
#[inline]
|
||||
pub fn disable() -> u32 {
|
||||
unsafe { set_mask(0) }
|
||||
}
|
||||
|
||||
/// Enables all the interrupts
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - Do not call this function inside an `interrupt::free` critical section
|
||||
#[inline]
|
||||
pub unsafe fn enable() -> u32 {
|
||||
set_mask(!0)
|
||||
}
|
||||
|
||||
/// Enables specific interrupts and returns the previous setting
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - Do not call this function inside an `interrupt::free` critical section
|
||||
#[inline]
|
||||
pub unsafe fn set_mask(mut mask: u32) -> u32 {
|
||||
asm!("
|
||||
xsr {0}, intenable
|
||||
rsync
|
||||
",
|
||||
inout(reg) mask, options(nostack)
|
||||
);
|
||||
mask
|
||||
}
|
||||
|
||||
/// Disables specific interrupts and returns the previous settings
|
||||
#[inline]
|
||||
pub fn disable_mask(mask: u32) -> u32 {
|
||||
let mut prev: u32 = 0;
|
||||
let _dummy: u32;
|
||||
unsafe {
|
||||
asm!("
|
||||
xsr.intenable {0} // get mask and temporarily disable interrupts
|
||||
and {1}, {1}, {0}
|
||||
rsync
|
||||
wsr.intenable {1}
|
||||
rsync
|
||||
", inout(reg) prev, inout(reg) !mask => _dummy, options(nostack)
|
||||
);
|
||||
}
|
||||
prev
|
||||
}
|
||||
|
||||
/// Enables specific interrupts and returns the previous setting
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// - Do not call this function inside an `interrupt::free` critical section
|
||||
#[inline]
|
||||
pub unsafe fn enable_mask(mask: u32) -> u32 {
|
||||
let mut prev: u32 = 0;
|
||||
let _dummy: u32;
|
||||
asm!("
|
||||
xsr.intenable {0} // get mask and temporarily disable interrupts
|
||||
or {1}, {1}, {0}
|
||||
rsync
|
||||
wsr.intenable {1}
|
||||
rsync
|
||||
", inout(reg) prev, inout(reg) mask => _dummy, options(nostack));
|
||||
prev
|
||||
}
|
||||
|
||||
/// Get current interrupt mask
|
||||
#[inline]
|
||||
pub fn get_mask() -> u32 {
|
||||
let mask: u32;
|
||||
unsafe { asm!("rsr.intenable {0}", out(reg) mask) };
|
||||
mask
|
||||
}
|
||||
|
||||
/// Get currently active interrupts
|
||||
#[inline]
|
||||
pub fn get() -> u32 {
|
||||
let mask: u32;
|
||||
unsafe {
|
||||
asm!("rsr.interrupt {0}", out(reg) mask, options(nostack));
|
||||
}
|
||||
mask
|
||||
}
|
||||
|
||||
/// Set interrupt
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only valid for software interrupts
|
||||
#[inline]
|
||||
pub unsafe fn set(mask: u32) {
|
||||
asm!("
|
||||
wsr.intset {0}
|
||||
rsync
|
||||
",
|
||||
in(reg) mask, options(nostack)
|
||||
);
|
||||
}
|
||||
|
||||
/// Clear interrupt
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// Only valid for software and edge-triggered interrupts
|
||||
#[inline]
|
||||
pub unsafe fn clear(mask: u32) {
|
||||
asm!("
|
||||
wsr.intclear {0}
|
||||
rsync
|
||||
",
|
||||
in(reg) mask, options(nostack)
|
||||
);
|
||||
}
|
||||
|
||||
/// Get current interrupt level
|
||||
#[inline]
|
||||
pub fn get_level() -> u32 {
|
||||
let ps: u32;
|
||||
unsafe {
|
||||
asm!("rsr.ps {0}", out(reg) ps, options(nostack));
|
||||
};
|
||||
ps & 0xf
|
||||
}
|
||||
|
||||
/// Execute closure `f` in an interrupt-free context.
|
||||
///
|
||||
/// This as also known as a "critical section".
|
||||
#[inline]
|
||||
pub fn free<F, R>(f: F) -> R
|
||||
where
|
||||
F: FnOnce(&CriticalSection) -> R,
|
||||
{
|
||||
// disable interrupts and store old mask
|
||||
let old_mask = disable();
|
||||
|
||||
let r = f(unsafe { &CriticalSection::new() });
|
||||
|
||||
// enable previously disable interrupts
|
||||
unsafe {
|
||||
enable_mask(old_mask);
|
||||
}
|
||||
|
||||
r
|
||||
}
|
||||
113
xtensa-lx/src/lib.rs
Normal file
113
xtensa-lx/src/lib.rs
Normal file
@ -0,0 +1,113 @@
|
||||
//! Low-level access to Xtensa LX processors and peripherals.
|
||||
//!
|
||||
//! ## 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)]
|
||||
#![feature(asm_experimental_arch)]
|
||||
#![no_std]
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
pub mod interrupt;
|
||||
pub mod mutex;
|
||||
pub mod timer;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
|
||||
const DCR_ENABLEOCD: u32 = 0x01;
|
||||
const XDM_OCD_DCR_SET: u32 = 0x10200C;
|
||||
|
||||
/// Move the vector base
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// *This is highly unsafe!*
|
||||
/// It should be used with care, `base` MUST be a valid pointer
|
||||
#[inline(always)]
|
||||
pub unsafe fn set_vecbase(base: *const u32) {
|
||||
asm!("wsr.vecbase {0}", in(reg) base, options(nostack));
|
||||
}
|
||||
|
||||
/// Get the core stack pointer
|
||||
#[inline(always)]
|
||||
pub fn get_stack_pointer() -> *const u32 {
|
||||
let x: *const u32;
|
||||
unsafe { asm!("mov {0}, sp", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
/// Set the core stack pointer
|
||||
///
|
||||
/// `stack` pointer to the non-inclusive end of the stack (must be 16-byte
|
||||
/// aligned)
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// *This is highly unsafe!*
|
||||
/// It should be used with care at e.g. program start or when building a task
|
||||
/// scheduler
|
||||
#[inline(always)]
|
||||
pub unsafe fn set_stack_pointer(stack: *mut u32) {
|
||||
// FIXME: this function relies on it getting inlined - if it doesn't inline it
|
||||
// will try and return from this function using the adress in `a0` which has
|
||||
// just been trashed... According to https://nnethercote.github.io/perf-book/inlining.html:
|
||||
// "Inline attributes do not guarantee that a function is inlined or not
|
||||
// inlined, but in practice, #[inline(always)] will cause inlining in all but
|
||||
// the most exceptional cases." Is this good enough? Should we rewrite these
|
||||
// as a macro to guarentee inlining?
|
||||
|
||||
// NOTE: modification of the `sp` & `a0` is not typically allowed inside inline
|
||||
// asm!, but because we *need* to modify it we can do so by ommiting it from
|
||||
// the clobber
|
||||
asm!(
|
||||
"movi a0, 0", // trash return register
|
||||
"mov sp, {0}", // move stack pointer
|
||||
in(reg) stack, options(nostack)
|
||||
);
|
||||
}
|
||||
|
||||
/// Get the core current program counter
|
||||
#[inline(always)]
|
||||
pub fn get_program_counter() -> *const u32 {
|
||||
let x: *const u32;
|
||||
unsafe {
|
||||
asm!("
|
||||
mov {1}, {2}
|
||||
call0 1f
|
||||
.align 4
|
||||
1:
|
||||
mov {0}, {2}
|
||||
mov {2}, {1}
|
||||
", out(reg) x, out(reg) _, out(reg) _, options(nostack))
|
||||
};
|
||||
x
|
||||
}
|
||||
|
||||
/// Get the id of the current core
|
||||
#[inline(always)]
|
||||
pub fn get_processor_id() -> u32 {
|
||||
let mut x: u32;
|
||||
unsafe { asm!("rsr.prid {0}", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
/// Returns true if a debugger is attached
|
||||
#[inline(always)]
|
||||
pub fn is_debugger_attached() -> bool {
|
||||
let mut x: u32;
|
||||
unsafe { asm!("rer {0}, {1}", out(reg) x, in(reg) XDM_OCD_DCR_SET, options(nostack)) };
|
||||
(x & DCR_ENABLEOCD) != 0
|
||||
}
|
||||
|
||||
/// Insert debug breakpoint
|
||||
#[inline(always)]
|
||||
pub fn debug_break() {
|
||||
unsafe { asm!("break 1, 15", options(nostack)) };
|
||||
}
|
||||
52
xtensa-lx/src/macros.rs
Normal file
52
xtensa-lx/src/macros.rs
Normal file
@ -0,0 +1,52 @@
|
||||
/// Macro to create a mutable reference to a statically allocated value
|
||||
///
|
||||
/// This macro returns a value with type `Option<&'static mut $ty>`.
|
||||
/// `Some($expr)` will be returned the first time the macro is executed; further
|
||||
/// calls will return `None`. To avoid `unwrap`ping a `None` variant the caller
|
||||
/// must ensure that the macro is called from a function that's executed at most
|
||||
/// once in the whole lifetime of the program.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ``` no_run
|
||||
/// use xtensa_lx::singleton;
|
||||
///
|
||||
/// fn main() {
|
||||
/// // OK if `main` is executed only once
|
||||
/// let x: &'static mut bool = singleton!(: bool = false).unwrap();
|
||||
///
|
||||
/// let y = alias();
|
||||
/// // BAD this second call to `alias` will definitively `panic!`
|
||||
/// let y_alias = alias();
|
||||
/// }
|
||||
///
|
||||
/// fn alias() -> &'static mut bool {
|
||||
/// singleton!(: bool = false).unwrap()
|
||||
/// }
|
||||
/// ```
|
||||
#[macro_export]
|
||||
macro_rules! singleton {
|
||||
(: $ty:ty = $expr:expr) => {
|
||||
$crate::interrupt::free(|_| {
|
||||
static mut VAR: Option<$ty> = None;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
let used = unsafe { VAR.is_some() };
|
||||
if used {
|
||||
None
|
||||
} else {
|
||||
let expr = $expr;
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
VAR = Some(expr)
|
||||
}
|
||||
|
||||
#[allow(unsafe_code)]
|
||||
unsafe {
|
||||
VAR.as_mut()
|
||||
}
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
104
xtensa-lx/src/mutex.rs
Normal file
104
xtensa-lx/src/mutex.rs
Normal file
@ -0,0 +1,104 @@
|
||||
//! A series of Mutex's that also implements the `mutex-trait`.
|
||||
|
||||
use core::cell::UnsafeCell;
|
||||
|
||||
pub use mutex_trait::{self, Mutex};
|
||||
|
||||
/// A spinlock and critical section section based mutex.
|
||||
#[cfg(feature = "spin")]
|
||||
#[derive(Default)]
|
||||
pub struct CriticalSectionSpinLockMutex<T> {
|
||||
data: spin::Mutex<T>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "spin")]
|
||||
impl<T> CriticalSectionSpinLockMutex<T> {
|
||||
/// Create a new mutex
|
||||
pub const fn new(data: T) -> Self {
|
||||
CriticalSectionSpinLockMutex {
|
||||
data: spin::Mutex::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "spin")]
|
||||
impl<T> mutex_trait::Mutex for &'_ CriticalSectionSpinLockMutex<T> {
|
||||
type Data = T;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
|
||||
crate::interrupt::free(|_| f(&mut (*self.data.lock())))
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE A `Mutex` can be used as a channel so the protected data must be `Send`
|
||||
// to prevent sending non-Sendable stuff (e.g. access tokens) across different
|
||||
// execution contexts (e.g. interrupts)
|
||||
#[cfg(feature = "spin")]
|
||||
unsafe impl<T> Sync for CriticalSectionSpinLockMutex<T> where T: Send {}
|
||||
|
||||
/// A Mutex based on critical sections
|
||||
///
|
||||
/// # Safety
|
||||
///
|
||||
/// **This Mutex is only safe on single-core applications.**
|
||||
///
|
||||
/// A `CriticalSection` **is not sufficient** to ensure exclusive access across
|
||||
/// cores.
|
||||
#[derive(Default)]
|
||||
pub struct CriticalSectionMutex<T> {
|
||||
data: UnsafeCell<T>,
|
||||
}
|
||||
|
||||
impl<T> CriticalSectionMutex<T> {
|
||||
/// Create a new mutex
|
||||
pub const fn new(data: T) -> Self {
|
||||
CriticalSectionMutex {
|
||||
data: UnsafeCell::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> mutex_trait::Mutex for &'_ CriticalSectionMutex<T> {
|
||||
type Data = T;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
|
||||
crate::interrupt::free(|_| f(unsafe { &mut *self.data.get() }))
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE A `Mutex` can be used as a channel so the protected data must be `Send`
|
||||
// to prevent sending non-Sendable stuff (e.g. access tokens) across different
|
||||
// execution contexts (e.g. interrupts)
|
||||
unsafe impl<T> Sync for CriticalSectionMutex<T> where T: Send {}
|
||||
|
||||
/// A spinlock based mutex.
|
||||
#[cfg(feature = "spin")]
|
||||
#[derive(Default)]
|
||||
pub struct SpinLockMutex<T> {
|
||||
data: spin::Mutex<T>,
|
||||
}
|
||||
|
||||
#[cfg(feature = "spin")]
|
||||
impl<T> SpinLockMutex<T> {
|
||||
/// Create a new mutex
|
||||
pub const fn new(data: T) -> Self {
|
||||
SpinLockMutex {
|
||||
data: spin::Mutex::new(data),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "spin")]
|
||||
impl<T> mutex_trait::Mutex for &'_ SpinLockMutex<T> {
|
||||
type Data = T;
|
||||
|
||||
fn lock<R>(&mut self, f: impl FnOnce(&mut Self::Data) -> R) -> R {
|
||||
f(&mut (*self.data.lock()))
|
||||
}
|
||||
}
|
||||
|
||||
// NOTE A `Mutex` can be used as a channel so the protected data must be `Send`
|
||||
// to prevent sending non-Sendable stuff (e.g. access tokens) across different
|
||||
// execution contexts (e.g. interrupts)
|
||||
#[cfg(feature = "spin")]
|
||||
unsafe impl<T> Sync for SpinLockMutex<T> where T: Send {}
|
||||
90
xtensa-lx/src/timer.rs
Normal file
90
xtensa-lx/src/timer.rs
Normal file
@ -0,0 +1,90 @@
|
||||
//! Xtensa internal timers
|
||||
|
||||
use core::arch::asm;
|
||||
|
||||
#[inline]
|
||||
pub fn get_ccompare0() -> u32 {
|
||||
let x: u32;
|
||||
unsafe { asm!("rsr.ccompare0 {0}", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ccompare1() -> u32 {
|
||||
let x: u32;
|
||||
unsafe { asm!("rsr.ccompare1 {0}", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ccompare2() -> u32 {
|
||||
let x: u32;
|
||||
unsafe { asm!("rsr.ccompare2 {0}", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn get_ccompare3() -> u32 {
|
||||
let x: u32;
|
||||
unsafe { asm!("rsr.ccompare3 {0}", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ccompare0(val: u32) {
|
||||
unsafe {
|
||||
asm!("
|
||||
wsr.ccompare0 {0}
|
||||
isync
|
||||
", in(reg) val, options(nostack))
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ccompare1(val: u32) {
|
||||
unsafe {
|
||||
asm!("
|
||||
wsr.ccompare1 {0}
|
||||
isync
|
||||
", in(reg) val, options(nostack))
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ccompare2(val: u32) {
|
||||
unsafe {
|
||||
asm!("
|
||||
wsr.ccompare2 {0}
|
||||
isync
|
||||
", in(reg) val, options(nostack))
|
||||
};
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_ccompare3(val: u32) {
|
||||
unsafe {
|
||||
asm!("
|
||||
wsr.ccompare3 {0}
|
||||
isync
|
||||
", in(reg) val, options(nostack))
|
||||
};
|
||||
}
|
||||
|
||||
/// Get the core cycle count
|
||||
#[inline]
|
||||
pub fn get_cycle_count() -> u32 {
|
||||
let x: u32;
|
||||
unsafe { asm!("rsr.ccount {0}", out(reg) x, options(nostack)) };
|
||||
x
|
||||
}
|
||||
|
||||
/// cycle accurate delay using the cycle counter register
|
||||
#[inline]
|
||||
pub fn delay(clocks: u32) {
|
||||
let start = get_cycle_count();
|
||||
loop {
|
||||
if get_cycle_count().wrapping_sub(start) >= clocks {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user