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:
Jesse Braham 2024-07-17 08:32:52 +00:00 committed by GitHub
parent 14baad4625
commit e33b060734
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
27 changed files with 7362 additions and 3 deletions

View File

@ -21,4 +21,7 @@ exclude = [
"extras/esp-wifishark",
"extras/ieee802154-sniffer",
"hil-test",
"xtensa-lx",
"xtensa-lx-rt",
"xtensa-lx-rt/procmacros",
]

View File

@ -48,6 +48,8 @@ pub enum Package {
EspWifi,
Examples,
HilTest,
XtensaLx,
XtensaLxRt,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, EnumIter, ValueEnum, serde::Serialize)]

View File

@ -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
View 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
View File

@ -0,0 +1,39 @@
# `xtensa-lx-rt`
[![Crates.io](https://img.shields.io/crates/v/xtensa-lx-rt?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/xtensa-lx-rt)
[![docs.rs](https://img.shields.io/docsrs/xtensa-lx-rt?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/xtensa-lx-rt)
![Crates.io](https://img.shields.io/crates/l/xtensa-lx-rt?labelColor=1C2C2E&style=flat-square)
[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org)
Minimal runtime/startup for Xtensa LX processors. This crate currently supports the following CPU's:
| Feature | Supported CPUs |
| --------- | ---------------- |
| `esp32` | ESP32 (_LX6_) |
| `esp32s2` | ESP32-S2 (_LX7_) |
| `esp32s3` | ESP32-S3 (_LX7_) |
## I get linker errors when I build for debug
Xtensa only provides a small code space for exceptions to fit inside, when building an unoptimized build the code size of a exception handler may exceed that size, causing a linker error. To fix this, you should always optimize this crate, even in debug builds. Adding the following to your projects `Cargo.toml` should do the trick.
```toml
[profile.dev.package.xtensa-lx-rt]
opt-level = 'z'
```
## License
Licensed under either of
- Apache License, Version 2.0 ([LICENSE-APACHE](../LICENSE-APACHE) or
http://www.apache.org/licenses/LICENSE-2.0)
- MIT license ([LICENSE-MIT](../LICENSE-MIT) or http://opensource.org/licenses/MIT)
at your option.
### Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the
work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any
additional terms or conditions.

243
xtensa-lx-rt/build.rs Normal file
View File

@ -0,0 +1,243 @@
use std::{
collections::{HashMap, HashSet},
env,
fs::{self, File},
io::Write,
path::{Path, PathBuf},
};
use anyhow::Result;
use enum_as_inner::EnumAsInner;
use minijinja::{context, Environment};
use serde::Deserialize;
use strum::{Display, EnumIter, EnumString};
/// The chips which are present in the xtensa-overlays repository
///
/// When `.to_string()` is called on a variant, the resulting string is the path
/// to the chip's corresponding directory.
#[derive(Debug, Clone, Copy, PartialEq, Display, EnumIter, Deserialize)]
enum Chip {
#[strum(to_string = "xtensa_esp32")]
Esp32,
#[strum(to_string = "xtensa_esp32s2")]
Esp32s2,
#[strum(to_string = "xtensa_esp32s3")]
Esp32s3,
}
/// The valid interrupt types declared in the `core-isa.h` headers
#[derive(Debug, Clone, Copy, PartialEq, EnumString, Deserialize)]
enum InterruptType {
#[strum(serialize = "XTHAL_INTTYPE_EXTERN_EDGE")]
ExternEdge,
#[strum(serialize = "XTHAL_INTTYPE_EXTERN_LEVEL")]
ExternLevel,
#[strum(serialize = "XTHAL_INTTYPE_NMI")]
Nmi,
#[strum(serialize = "XTHAL_INTTYPE_PROFILING")]
Profiling,
#[strum(serialize = "XTHAL_INTTYPE_SOFTWARE")]
Software,
#[strum(serialize = "XTHAL_INTTYPE_TIMER")]
Timer,
#[strum(serialize = "XTHAL_TIMER_UNCONFIGURED")]
TimerUnconfigured,
}
/// The allowable value types for definitions
#[derive(Debug, Clone, PartialEq, EnumAsInner, Deserialize)]
enum Value {
Integer(i64),
Interrupt(InterruptType),
String(String),
}
fn main() -> Result<()> {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
// Put the linker script somewhere the linker can find it
println!("cargo:rustc-link-search={}", out.display());
File::create(out.join("link.x"))?.write_all(include_bytes!("xtensa.in.x"))?;
handle_esp32()?;
// Only re-run the build script when xtensa.in.x is changed,
// instead of when any part of the source code changes.
println!("cargo:rerun-if-changed=xtensa.in.x");
Ok(())
}
fn handle_esp32() -> Result<()> {
let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
let rustflags = env::var_os("CARGO_ENCODED_RUSTFLAGS")
.unwrap()
.into_string()
.unwrap();
let mut features_to_disable: HashSet<String> = HashSet::new();
// Users can pass -Ctarget-feature to the compiler multiple times, so we have to
// handle that
let target_flags = rustflags
.split(0x1f as char)
.filter(|s| s.starts_with("target-feature="))
.filter_map(|s| s.strip_prefix("target-feature="));
for tf in target_flags {
tf.split(',')
.map(|s| s.trim())
.filter(|s| s.starts_with('-'))
.filter_map(|s| s.strip_prefix('-'))
.filter_map(rustc_feature_to_xchal_have)
.for_each(|s| {
features_to_disable.insert(s.to_owned());
})
}
let chip = match (
cfg!(feature = "esp32"),
cfg!(feature = "esp32s2"),
cfg!(feature = "esp32s3"),
) {
(true, false, false) => Chip::Esp32,
(false, true, false) => Chip::Esp32s2,
(false, false, true) => Chip::Esp32s3,
_ => panic!("Either the esp32, esp32s2, esp32s3 feature must be enabled"),
};
let isa_toml = fs::read_to_string(format!("config/{chip}.toml"))?;
let isa_config: HashMap<String, Value> = toml::from_str(&isa_toml)?;
inject_cfgs(&isa_config, &features_to_disable);
inject_cpu_cfgs(&isa_config);
generate_exception_x(out, &isa_config)?;
generate_interrupt_level_masks(out, &isa_config)?;
Ok(())
}
fn generate_interrupt_level_masks(out: &Path, isa_config: &HashMap<String, Value>) -> Result<()> {
let exception_source_template = include_str!("interrupt_level_masks.rs.jinja");
let mut env = Environment::new();
env.add_template("interrupt_level_masks.rs", exception_source_template)?;
let template = env.get_template("interrupt_level_masks.rs").unwrap();
let exception_source = template.render(context! {
XCHAL_INTLEVEL1_MASK => isa_config.get("XCHAL_INTLEVEL1_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL2_MASK => isa_config.get("XCHAL_INTLEVEL2_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL3_MASK => isa_config.get("XCHAL_INTLEVEL3_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL4_MASK => isa_config.get("XCHAL_INTLEVEL4_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL5_MASK => isa_config.get("XCHAL_INTLEVEL5_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL6_MASK => isa_config.get("XCHAL_INTLEVEL6_MASK").unwrap().as_integer(),
XCHAL_INTLEVEL7_MASK => isa_config.get("XCHAL_INTLEVEL7_MASK").unwrap().as_integer(),
})?;
File::create(out.join("interrupt_level_masks.rs"))?.write_all(exception_source.as_bytes())?;
Ok(())
}
fn generate_exception_x(out: &Path, isa_config: &HashMap<String, Value>) -> Result<()> {
let exception_source_template = include_str!("exception-esp32.x.jinja");
let mut env = Environment::new();
env.add_template("exception.x", exception_source_template)?;
let template = env.get_template("exception.x")?;
let exception_source = template.render(
context! {
XCHAL_WINDOW_OF4_VECOFS => isa_config.get("XCHAL_WINDOW_OF4_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_UF4_VECOFS => isa_config.get("XCHAL_WINDOW_UF4_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_OF8_VECOFS => isa_config.get("XCHAL_WINDOW_OF8_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_UF8_VECOFS => isa_config.get("XCHAL_WINDOW_UF8_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_OF12_VECOFS => isa_config.get("XCHAL_WINDOW_OF12_VECOFS").unwrap().as_integer(),
XCHAL_WINDOW_UF12_VECOFS => isa_config.get("XCHAL_WINDOW_UF12_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL2_VECOFS => isa_config.get("XCHAL_INTLEVEL2_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL3_VECOFS => isa_config.get("XCHAL_INTLEVEL3_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL4_VECOFS => isa_config.get("XCHAL_INTLEVEL4_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL5_VECOFS => isa_config.get("XCHAL_INTLEVEL5_VECOFS").unwrap().as_integer(),
XCHAL_INTLEVEL6_VECOFS => isa_config.get("XCHAL_INTLEVEL6_VECOFS").unwrap().as_integer(),
XCHAL_NMI_VECOFS => isa_config.get("XCHAL_NMI_VECOFS").unwrap().as_integer(),
XCHAL_KERNEL_VECOFS => isa_config.get("XCHAL_KERNEL_VECOFS").unwrap().as_integer(),
XCHAL_USER_VECOFS => isa_config.get("XCHAL_USER_VECOFS").unwrap().as_integer(),
XCHAL_DOUBLEEXC_VECOFS => isa_config.get("XCHAL_DOUBLEEXC_VECOFS").unwrap().as_integer(),
}
)?;
File::create(out.join("exception.x"))?.write_all(exception_source.as_bytes())?;
Ok(())
}
fn inject_cfgs(isa_config: &HashMap<String, Value>, disabled_features: &HashSet<String>) {
for (key, value) in isa_config {
if key.starts_with("XCHAL_HAVE")
&& *value.as_integer().unwrap_or(&0) != 0
&& !disabled_features.contains(key)
{
println!("cargo:rustc-cfg={}", key);
}
}
}
fn inject_cpu_cfgs(isa_config: &HashMap<String, Value>) {
for (key, value) in isa_config {
if (key.starts_with("XCHAL_TIMER")
|| key.starts_with("XCHAL_PROFILING")
|| key.starts_with("XCHAL_NMI"))
&& value.as_integer().is_some()
{
let mut s = String::from(key.trim_end_matches("_INTERRUPT"));
let first = s.chars().position(|c| c == '_').unwrap() + 1;
s.insert_str(first, "HAVE_");
println!("cargo:rustc-cfg={}", s);
}
}
if let Some(value) = isa_config
.get("XCHAL_INTTYPE_MASK_SOFTWARE")
.and_then(|v| v.as_integer())
{
for i in 0..value.count_ones() {
println!("cargo:rustc-cfg=XCHAL_HAVE_SOFTWARE{}", i);
}
}
}
fn rustc_feature_to_xchal_have(s: &str) -> Option<&str> {
// List of rustc features taken from here:
// https://github.com/esp-rs/rust/blob/84ecb3f010525cb1b2e7d4da306099c2eaa3e6cd/compiler/rustc_codegen_ssa/src/target_features.rs#L278
// unlikely to change
Some(match s {
"fp" => "XCHAL_HAVE_FP",
"windowed" => "XCHAL_HAVE_WINDOWED",
"bool" => "XCHAL_HAVE_BOOLEANS",
"loop" => "XCHAL_HAVE_LOOPS",
"sext" => "XCHAL_HAVE_SEXT",
"nsa" => "XCHAL_HAVE_NSA",
"mul32" => "XCHAL_HAVE_MUL32",
"mul32high" => "XCHAL_HAVE_MUL32_HIGH",
"div32" => "XCHAL_HAVE_DIV32",
"mac16" => "XCHAL_HAVE_MAC16",
"dfpaccel" => "XCHAL_HAVE_DFP",
"s32c1i" => "XCHAL_HAVE_S32C1I",
"threadptr" => "XCHAL_HAVE_THREADPTR",
"extendedl32r" => "XCHAL_HAVE_ABSOLUTE_LITERALS",
"debug" => "XCHAL_HAVE_DEBUG",
"exception" => "XCHAL_HAVE_EXCEPTIONS",
"highpriinterrupts" => "XCHAL_HAVE_HIGHPRI_INTERRUPTS",
"coprocessor" => "XCHAL_HAVE_CP",
"interrupt" => "XCHAL_HAVE_INTERRUPTS",
"rvector" => "XCHAL_HAVE_VECTOR_SELECT",
"prid" => "XCHAL_HAVE_PRID",
"regprotect" => "XCHAL_HAVE_MIMIC_CACHEATTR",
"miscsr" => return None, // XCHAL_NUM_MISC_REGS
"timerint" => return None, // XCHAL_NUM_TIMERS
"atomctl" => return None,
"memctl" => return None,
_ => return None,
})
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,96 @@
/* exception vector for the ESP32, requiring high priority interrupts and register window support */
/* high level exception/interrupt routines, which can be override with Rust functions */
PROVIDE(__exception = __default_exception);
PROVIDE(__user_exception = __default_user_exception);
PROVIDE(__double_exception = __default_double_exception);
PROVIDE(__level_1_interrupt = __default_interrupt);
PROVIDE(__level_2_interrupt = __default_interrupt);
PROVIDE(__level_3_interrupt = __default_interrupt);
PROVIDE(__level_4_interrupt = __default_interrupt);
PROVIDE(__level_5_interrupt = __default_interrupt);
PROVIDE(__level_6_interrupt = __default_interrupt);
PROVIDE(__level_7_interrupt = __default_interrupt);
/* high level CPU interrupts */
PROVIDE(Timer0 = __default_user_exception);
PROVIDE(Timer1 = __default_user_exception);
PROVIDE(Timer2 = __default_user_exception);
PROVIDE(Timer3 = __default_user_exception);
PROVIDE(Profiling = __default_user_exception);
PROVIDE(NMI = __default_user_exception);
PROVIDE(Software0 = __default_user_exception);
PROVIDE(Software1 = __default_user_exception);
/* low level exception/interrupt, which must be overridden using naked functions */
PROVIDE(__naked_user_exception = __default_naked_exception);
PROVIDE(__naked_kernel_exception = __default_naked_exception);
PROVIDE(__naked_double_exception = __default_naked_double_exception);
PROVIDE(__naked_level_2_interrupt = __default_naked_level_2_interrupt);
PROVIDE(__naked_level_3_interrupt = __default_naked_level_3_interrupt);
PROVIDE(__naked_level_4_interrupt = __default_naked_level_4_interrupt);
PROVIDE(__naked_level_5_interrupt = __default_naked_level_5_interrupt);
PROVIDE(__naked_level_6_interrupt = __default_naked_level_6_interrupt);
PROVIDE(__naked_level_7_interrupt = __default_naked_level_7_interrupt);
/* needed to force inclusion of the vectors */
EXTERN(__default_exception);
EXTERN(__default_double_exception);
EXTERN(__default_interrupt);
EXTERN(__default_naked_exception);
EXTERN(__default_naked_double_exception);
EXTERN(__default_naked_level_2_interrupt);
EXTERN(__default_naked_level_3_interrupt);
EXTERN(__default_naked_level_4_interrupt);
EXTERN(__default_naked_level_5_interrupt);
EXTERN(__default_naked_level_6_interrupt);
EXTERN(__default_naked_level_7_interrupt);
/* Define output sections */
SECTIONS {
.vectors :
{
/*
Each vector has 64 bytes that it must fit inside. For each vector we calculate the size of the previous one,
and subtract that from 64 and start the new vector there.
*/
_init_start = ABSOLUTE(.);
. = ALIGN(64);
KEEP(*(.WindowOverflow4.text));
. = ALIGN(64);
KEEP(*(.WindowUnderflow4.text));
. = ALIGN(64);
KEEP(*(.WindowOverflow8.text));
. = ALIGN(64);
KEEP(*(.WindowUnderflow8.text));
. = ALIGN(64);
KEEP(*(.WindowOverflow12.text));
. = ALIGN(64);
KEEP(*(.WindowUnderflow12.text));
. = ALIGN(64);
KEEP(*(.Level2InterruptVector.text));
. = ALIGN(64);
KEEP(*(.Level3InterruptVector.text));
. = ALIGN(64);
KEEP(*(.Level4InterruptVector.text));
. = ALIGN(64);
KEEP(*(.Level5InterruptVector.text));
. = ALIGN(64);
KEEP(*(.DebugExceptionVector.text));
. = ALIGN(64);
KEEP(*(.NMIExceptionVector.text));
. = ALIGN(64);
KEEP(*(.KernelExceptionVector.text));
. = ALIGN(64);
KEEP(*(.UserExceptionVector.text));
. = ALIGN(128);
KEEP(*(.DoubleExceptionVector.text));
. = ALIGN(64);
. = ALIGN(0x400);
_init_end = ABSOLUTE(.);
} > vectors_seg
}

View File

@ -0,0 +1,23 @@
pub enum CpuInterruptLevel {
Level1,
Level2,
Level3,
Level4,
Level5,
Level6,
Level7,
}
impl CpuInterruptLevel {
pub fn mask(&self) -> u32 {
match &self {
CpuInterruptLevel::Level1 => {{ XCHAL_INTLEVEL1_MASK }}u32,
CpuInterruptLevel::Level2 => {{ XCHAL_INTLEVEL2_MASK }}u32,
CpuInterruptLevel::Level3 => {{ XCHAL_INTLEVEL3_MASK }}u32,
CpuInterruptLevel::Level4 => {{ XCHAL_INTLEVEL4_MASK }}u32,
CpuInterruptLevel::Level5 => {{ XCHAL_INTLEVEL5_MASK }}u32,
CpuInterruptLevel::Level6 => {{ XCHAL_INTLEVEL6_MASK }}u32,
CpuInterruptLevel::Level7 => {{ XCHAL_INTLEVEL7_MASK }}u32,
}
}
}

View 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"] }

View 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)
}

View File

@ -0,0 +1,121 @@
//! Exception handling
//!
//! Currently specialized for ESP32 (LX6) configuration: which extra registers
//! to store, how many interrupt levels etc.
//!
//! First level interrupts and exceptions save full processor state to the user
//! stack. This includes the coprocessor registers contrary to the esp-idf where
//! these are lazily saved. (Kernel mode option is currently not used.)
//!
//! WindowUnder/Overflow and AllocA use default Xtensa implementation.
//!
//! LoadStoreError and Unaligned are not (yet) implemented: so all accesses to
//! IRAM must be word sized and aligned.
//!
//! Syscall 0 is not (yet) implemented: it doesn't seem to be used in rust.
//!
//! Double Exceptions can only occur during the early setup of the exception
//! handler. Afterwards PS.EXCM is set to 0 to be able to handle
//! WindowUnderflow/Overflow and recursive exceptions will happen instead.
//!
//! In various places call0 are used as long jump: `j.l` syntax is not supported
//! and `call0` can always be expanded to `mov a0,label; call a0`. Care must be
//! taken since A0 is overwritten.
mod asm;
mod context;
pub use context::Context;
/// EXCCAUSE register values
///
/// General Exception Causes. (Values of EXCCAUSE special register set by
/// general exceptions, which vector to the user, kernel, or double-exception
/// vectors).
#[allow(unused)]
#[derive(Debug)]
#[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,
}

View 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));
}

View 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)
);
}

View File

@ -0,0 +1,3 @@
//! Interrupts
include!(concat!(env!("OUT_DIR"), "/interrupt_level_masks.rs"));

172
xtensa-lx-rt/src/lib.rs Normal file
View File

@ -0,0 +1,172 @@
//! Minimal startup/runtime for Xtensa LX CPUs.
//!
//! ## Minimum Supported Rust Version (MSRV)
//!
//! This crate is guaranteed to compile on stable Rust 1.65 and up. It might
//! compile with older versions but that may change in any new patch release.
//!
//! ## Feature Flags
#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
#![allow(asm_sub_register, named_asm_labels)]
#![feature(asm_experimental_arch, naked_functions)]
#![no_std]
use core::{
arch::asm,
ptr::{addr_of, addr_of_mut},
};
pub use macros::{entry, exception, interrupt, pre_init};
pub use r0::{init_data, zero_bss};
pub use xtensa_lx;
pub mod exception;
pub mod interrupt;
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn DefaultPreInit() {}
#[doc(hidden)]
#[no_mangle]
pub unsafe extern "C" fn Reset() -> ! {
// These symbols come from `link.x`
extern "C" {
static mut _bss_start: u32;
static mut _bss_end: u32;
static mut _data_start: u32;
static mut _data_end: u32;
static _sidata: u32;
static mut _init_start: u32;
}
extern "Rust" {
// This symbol will be provided by the user via `#[entry]`
fn main() -> !;
// This symbol will be provided by the user via `#[pre_init]`
fn __pre_init();
fn __post_init();
fn __zero_bss() -> bool;
fn __init_data() -> bool;
}
__pre_init();
if __zero_bss() {
r0::zero_bss(addr_of_mut!(_bss_start), addr_of_mut!(_bss_end));
}
if __init_data() {
r0::init_data(addr_of_mut!(_data_start), addr_of_mut!(_data_end), &_sidata);
}
// Copy of data segment is done by bootloader
// According to 4.4.6.2 of the xtensa isa, ccount and compare are undefined on
// reset, set all values to zero to disable
reset_internal_timers();
// move vec table
set_vecbase(addr_of!(_init_start));
__post_init();
main();
}
#[doc(hidden)]
#[no_mangle]
#[rustfmt::skip]
pub unsafe extern "Rust" fn default_post_init() {}
// We redefine these functions to avoid pulling in `xtensa-lx` as a dependency:
#[doc(hidden)]
#[inline]
unsafe fn reset_internal_timers() {
#[cfg(any(
XCHAL_HAVE_TIMER0,
XCHAL_HAVE_TIMER1,
XCHAL_HAVE_TIMER2,
XCHAL_HAVE_TIMER3
))]
{
let value = 0;
cfg_asm!(
{
#[cfg(XCHAL_HAVE_TIMER0)]
"wsr.ccompare0 {0}",
#[cfg(XCHAL_HAVE_TIMER1)]
"wsr.ccompare1 {0}",
#[cfg(XCHAL_HAVE_TIMER2)]
"wsr.ccompare2 {0}",
#[cfg(XCHAL_HAVE_TIMER3)]
"wsr.ccompare3 {0}",
"isync",
}, in(reg) value, options(nostack));
}
}
// CPU Interrupts
extern "C" {
#[cfg(XCHAL_HAVE_TIMER0)]
pub fn Timer0(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER1)]
pub fn Timer1(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER2)]
pub fn Timer2(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_TIMER3)]
pub fn Timer3(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_PROFILING)]
pub fn Profiling(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_SOFTWARE0)]
pub fn Software0(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_SOFTWARE1)]
pub fn Software1(level: u32, save_frame: &mut crate::exception::Context);
#[cfg(XCHAL_HAVE_NMI)]
pub fn NMI(level: u32, save_frame: &mut crate::exception::Context);
}
#[doc(hidden)]
#[inline]
unsafe fn set_vecbase(base: *const u32) {
asm!("wsr.vecbase {0}", in(reg) base, options(nostack));
}
#[doc(hidden)]
#[no_mangle]
#[rustfmt::skip]
pub extern "Rust" fn default_mem_hook() -> bool {
true // default to zeroing bss & initializing data
}
#[doc(hidden)]
#[macro_export]
macro_rules! cfg_asm {
(@inner, [$($x:tt)*], [$($opts:tt)*], ) => {
asm!($($x)* $($opts)*)
};
(@inner, [$($x:tt)*], [$($opts:tt)*], #[cfg($meta:meta)] $asm:literal, $($rest:tt)*) => {
#[cfg($meta)]
cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*);
#[cfg(not($meta))]
cfg_asm!(@inner, [$($x)*], [$($opts)*], $($rest)*)
};
(@inner, [$($x:tt)*], [$($opts:tt)*], $asm:literal, $($rest:tt)*) => {
cfg_asm!(@inner, [$($x)* $asm,], [$($opts)*], $($rest)*)
};
({$($asms:tt)*}, $($opts:tt)*) => {
cfg_asm!(@inner, [], [$($opts)*], $($asms)*)
};
}

79
xtensa-lx-rt/xtensa.in.x Normal file
View File

@ -0,0 +1,79 @@
/* before memory.x to allow override */
ENTRY(Reset)
INCLUDE memory.x
/* after memory.x to allow override */
PROVIDE(__pre_init = DefaultPreInit);
PROVIDE(__zero_bss = default_mem_hook);
PROVIDE(__init_data = default_mem_hook);
PROVIDE(__post_init = default_post_init);
INCLUDE exception.x
SECTIONS {
.text : ALIGN(4)
{
_stext = .;
. = ALIGN (4);
_text_start = ABSOLUTE(.);
. = ALIGN (4);
*(.literal .text .literal.* .text.*)
_text_end = ABSOLUTE(.);
_etext = .;
} > ROTEXT
.rodata : ALIGN(4)
{
_rodata_start = ABSOLUTE(.);
. = ALIGN (4);
*(.rodata .rodata.*)
_rodata_end = ABSOLUTE(.);
} > RODATA
.data : ALIGN(4)
{
_data_start = ABSOLUTE(.);
. = ALIGN (4);
*(.data .data.*)
_data_end = ABSOLUTE(.);
} > RWDATA AT > RODATA
/* LMA of .data */
_sidata = LOADADDR(.data);
.bss (NOLOAD) : ALIGN(4)
{
_bss_start = ABSOLUTE(.);
. = ALIGN (4);
*(.bss .bss.* COMMON)
_bss_end = ABSOLUTE(.);
} > RWDATA
.noinit (NOLOAD) : ALIGN(4)
{
. = ALIGN(4);
*(.noinit .noinit.*)
} > RWDATA
.rwtext : ALIGN(4)
{
. = ALIGN (4);
*(.rwtext.literal .rwtext .rwtext.literal.* .rwtext.*)
} > RWTEXT
/* must be last segment using RWTEXT */
.text_heap_start (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_text_heap_start = ABSOLUTE(.);
} > RWTEXT
/* must be last segment using RWDATA */
.heap_start (NOLOAD) : ALIGN(4)
{
. = ALIGN (4);
_heap_start = ABSOLUTE(.);
} > RWDATA
}

24
xtensa-lx/Cargo.toml Normal file
View 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
View File

@ -0,0 +1,32 @@
# `xtensa-lx`
[![Crates.io](https://img.shields.io/crates/v/xtensa-lx?labelColor=1C2C2E&color=C96329&logo=Rust&style=flat-square)](https://crates.io/crates/xtensa-lx)
[![docs.rs](https://img.shields.io/docsrs/xtensa-lx?labelColor=1C2C2E&color=C96329&logo=rust&style=flat-square)](https://docs.rs/xtensa-lx)
![Crates.io](https://img.shields.io/crates/l/xtensa-lx?labelColor=1C2C2E&style=flat-square)
[![Matrix](https://img.shields.io/matrix/esp-rs:matrix.org?label=join%20matrix&labelColor=1C2C2E&color=BEC5C9&logo=matrix&style=flat-square)](https://matrix.to/#/#esp-rs:matrix.org)
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
View 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
View 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
View 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
View 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
View 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
View 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;
}
}
}