Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Take 2 on #[ram] soundness #1677

Merged
merged 3 commits into from
Jul 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion esp-hal-procmacros/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ enum-dispatch = []
interrupt = []
## Provide a `#[ram]` procmacro to place functions in RAM instead of flash.
ram = []
## Indicates the target devices has RTC slow memory available.
## Indicates the target device has RTC slow memory available.
rtc_slow = []

#! ### Low-power Core Feature Flags
Expand Down
137 changes: 97 additions & 40 deletions esp-hal-procmacros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,25 +16,22 @@
//! optimized memory usage and precise handling of hardware interrupts.
//!
//! Key Components:
//! - [interrupt](attr.interrupt.html) - Attribute macro for marking interrupt
//! handlers. Interrupt handlers are used to handle specific hardware
//! interrupts generated by peripherals.<br> The macro allows users to
//! specify the interrupt name explicitly or use the function name to match
//! the interrupt.
//! - [main](attr.main.html) - Creates a new `executor`` instance and declares
//! - [`interrupt`](attr.interrupt.html) - Attribute macro for marking
//! interrupt handlers. Interrupt handlers are used to handle specific
//! hardware interrupts generated by peripherals.
//!
//! The macro allows users to specify the interrupt name explicitly or use
//! the function name to match the interrupt.
//! - [`main`](attr.main.html) - Creates a new `executor` instance and declares
//! an application entry point spawning the corresponding function body as an
//! async task.
//! - [ram](attr.ram.html) - Attribute macro for placing statics and functions
//! into specific memory sections, such as SRAM or RTC RAM (slow or fast)
//! with different initialization options. Supported options are:
//! - `rtc_fast` - Use RTC fast RAM
//! - `rtc_slow` - Use RTC slow RAM (not all targets support slow RTC RAM)
//! - `uninitialized` - Skip initialization of the memory
//! - `zeroed` - Initialize the memory to zero
//! - [`ram`](attr.ram.html) - Attribute macro for placing statics and
//! functions into specific memory sections, such as SRAM or RTC RAM (slow or
//! fast) with different initialization options. See its documentation for
//! details.
//!
//! ## Examples
//!
//!
//! #### `main` macro
//!
//! Requires the `embassy` feature to be enabled.
Expand All @@ -46,21 +43,6 @@
//! }
//! ```
//!
//! #### `ram` macro
//!
//! Requires the `ram` feature to be enabled.
//!
//! ```rust, no_run
//! #[ram(rtc_fast)]
//! static mut SOME_INITED_DATA: [u8; 2] = [0xaa, 0xbb];
//!
//! #[ram(rtc_fast, uninitialized)]
//! static mut SOME_UNINITED_DATA: [u8; 2] = [0; 2];
//!
//! #[ram(rtc_fast, zeroed)]
//! static mut SOME_ZEROED_DATA: [u8; 8] = [0; 8];
//! ```
//!
//! ## Feature Flags
#![doc = document_features::document_features!()]
#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
Expand Down Expand Up @@ -88,19 +70,64 @@ mod lp_core;
struct RamArgs {
rtc_fast: bool,
rtc_slow: bool,
uninitialized: bool,
persistent: bool,
zeroed: bool,
}

/// This attribute allows placing statics and functions into ram.
/// Sets which segment of RAM to use for a function or static and how it should
/// be initialized.
///
/// Requires the `ram` feature.
///
/// # Options
///
/// - `rtc_fast`: Use RTC fast RAM.
/// - `rtc_slow`: Use RTC slow RAM. **Note**: not available on all targets
/// - `persistent`: Persist the contents of the `static` across resets. See [the
/// section below](#persistent) for details.
/// - `zeroed`: Initialize the memory of the `static` to zero. The initializer
/// expression will be discarded. Types used must implement
/// [`bytemuck::Zeroable`].
///
/// Using both `rtc_fast` and `rtc_slow` or `persistent` and `zeroed` together
/// is an error.
///
/// ## `persistent`
///
/// Initialize the memory to zero after the initial boot. Thereafter,
/// initialization is skipped to allow communication across `software_reset()`,
/// deep sleep, watchdog timeouts, etc.
///
/// Types used must implement [`bytemuck::AnyBitPattern`].
///
/// Options that can be specified are rtc_slow or rtc_fast to use the
/// RTC slow or RTC fast ram instead of the normal SRAM.
/// ### Warnings
///
/// The uninitialized option will skip initialization of the memory
/// (e.g. to persist it across resets or deep sleep mode for the RTC RAM)
/// - A system-level or lesser reset occurring before the ram has been zeroed
/// *could* skip initialization and start the application with the static
/// filled with random bytes.
/// - There is no way to keep some kinds of resets from happening while updating
/// a persistent static—not even a critical section.
///
/// Not all targets support RTC slow ram.
/// If these are issues for your application, consider adding a checksum
/// alongside the data.
///
/// # Examples
///
/// ```rust, no_run
/// #[ram(rtc_fast)]
/// static mut SOME_INITED_DATA: [u8; 2] = [0xaa, 0xbb];
///
/// #[ram(rtc_fast, persistent)]
/// static mut SOME_PERSISTENT_DATA: [u8; 2] = [0; 2];
///
/// #[ram(rtc_fast, zeroed)]
/// static mut SOME_ZEROED_DATA: [u8; 8] = [0; 8];
/// ```
///
/// See the `ram` example in the esp-hal repository for a full usage example.
///
/// [`bytemuck::AnyBitPattern`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.AnyBitPattern.html
/// [`bytemuck::Zeroable`]: https://docs.rs/bytemuck/1.9.0/bytemuck/trait.Zeroable.html
#[cfg(feature = "ram")]
#[proc_macro_attribute]
#[proc_macro_error::proc_macro_error]
Expand All @@ -120,7 +147,7 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
let RamArgs {
rtc_fast,
rtc_slow,
uninitialized,
persistent,
zeroed,
} = match FromMeta::from_list(&attr_args) {
Ok(v) => v,
Expand All @@ -140,19 +167,19 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
}

let is_fn = matches!(item, Item::Fn(_));
let section_name = match (is_fn, rtc_fast, rtc_slow, uninitialized, zeroed) {
let section_name = match (is_fn, rtc_fast, rtc_slow, persistent, zeroed) {
(true, false, false, false, false) => Ok(".rwtext"),
(true, true, false, false, false) => Ok(".rtc_fast.text"),
(true, false, true, false, false) => Ok(".rtc_slow.text"),

(false, false, false, false, false) => Ok(".data"),

(false, true, false, false, false) => Ok(".rtc_fast.data"),
(false, true, false, true, false) => Ok(".rtc_fast.noinit"),
(false, true, false, true, false) => Ok(".rtc_fast.persistent"),
(false, true, false, false, true) => Ok(".rtc_fast.bss"),

(false, false, true, false, false) => Ok(".rtc_slow.data"),
(false, false, true, true, false) => Ok(".rtc_slow.noinit"),
(false, false, true, true, false) => Ok(".rtc_slow.persistent"),
(false, false, true, false, true) => Ok(".rtc_slow.bss"),

_ => Err(()),
Expand All @@ -171,9 +198,39 @@ pub fn ram(args: TokenStream, input: TokenStream) -> TokenStream {
}
};

let trait_check = if zeroed {
Some("zeroable")
} else if persistent {
Some("persistable")
} else {
None
};
let trait_check = trait_check.map(|name| {
use proc_macro_crate::{crate_name, FoundCrate};

let hal = proc_macro2::Ident::new(
if let Ok(FoundCrate::Name(ref name)) = crate_name("esp-hal") {
&name
} else {
"crate"
},
Span::call_site().into(),
);

let assertion = quote::format_ident!("assert_is_{name}");
let Item::Static(ref item) = item else {
abort!(item, "Expected a `static`");
};
let ty = &item.ty;
quote::quote! {
const _: () = #hal::__macro_implementation::#assertion::<#ty>();
}
});

let output = quote::quote! {
#section
#item
#trait_check
};

output.into()
Expand Down
3 changes: 3 additions & 0 deletions esp-hal/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add support for GPIO wake-up source (#1724)
- dma: add Mem2Mem to support memory to memory transfer (#1738)
- Add `uart` wake source (#1727)
- `#[ram(persistent)]` option to replace the unsound `uninitialized` option (#1677)
- uart: Make `rx_timeout` optional in Config struct (#1759)
- Add interrupt related functions to `PeriodicTimer`/`OneShotTimer`, added `ErasedTimer` (#1753)

Expand All @@ -31,6 +32,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Fix `sleep_light` for ESP32-C6 (#1720)
- ROM Functions: Fix address of `ets_update_cpu_frequency_rom` (#1722)
- Fix `regi2c_*` functions for `esp32h2` (#1737)
- Improved `#[ram(zeroed)]` soundness by adding a `bytemuck::Zeroable` type bound (#1677)
- EESP32-S2 / ESP32-S3: Fix UsbDm and UsbDp for Gpio19 and Gpio20

### Changed
Expand All @@ -45,6 +47,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Removed
- uart: Removed `configure_pins` methods (#1592)
- Removed `DmaError::Exhausted` error by improving the implementation of the `pop` function (#1664)
- Unsound `#[ram(uninitialized)]` option in favor of the new `persistent` option (#1677)

## [0.18.0] - 2024-06-04

Expand Down
15 changes: 5 additions & 10 deletions esp-hal/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ rustdoc-args = ["--cfg", "docsrs"]

[dependencies]
bitflags = "2.5.0"
bytemuck = "1.0.0"
bitfield = "0.15.0"
cfg-if = "1.0.0"
critical-section = "1.1.2"
Expand Down Expand Up @@ -77,7 +78,7 @@ serde = { version = "1.0.203", features = ["derive"] }
[features]
default = ["embedded-hal"]

riscv = ["dep:riscv", "critical-section/restore-state-u8", "esp-riscv-rt/zero-bss"]
riscv = ["dep:riscv", "critical-section/restore-state-u8"]
xtensa = ["dep:xtensa-lx", "critical-section/restore-state-u32"]

bluetooth = []
Expand Down Expand Up @@ -105,11 +106,11 @@ esp32 = ["dep:esp32", "xtensa", "xtensa-lx/spin", "xtensa-lx-rt/esp32"]
# Target the ESP32-C2.
esp32c2 = ["dep:esp32c2", "riscv", "portable-atomic/unsafe-assume-single-core"]
# Target the ESP32-C3.
esp32c3 = ["dep:esp32c3", "riscv", "portable-atomic/unsafe-assume-single-core", "rv-zero-rtc-bss"]
esp32c3 = ["dep:esp32c3", "riscv", "portable-atomic/unsafe-assume-single-core", "esp-riscv-rt/rtc-ram"]
# Target the ESP32-C6.
esp32c6 = ["dep:esp32c6", "riscv", "procmacros/has-lp-core", "rv-zero-rtc-bss"]
esp32c6 = ["dep:esp32c6", "riscv", "procmacros/has-lp-core", "esp-riscv-rt/rtc-ram"]
# Target the ESP32-H2.
esp32h2 = ["dep:esp32h2", "riscv", "rv-zero-rtc-bss"]
esp32h2 = ["dep:esp32h2", "riscv", "esp-riscv-rt/rtc-ram"]
# Target the ESP32-S2.
esp32s2 = ["dep:esp32s2", "xtensa", "portable-atomic/critical-section", "procmacros/has-ulp-core", "xtensa-lx-rt/esp32s2", "usb-otg"]
# Target the ESP32-S3.
Expand All @@ -119,12 +120,6 @@ esp32s3 = ["dep:esp32s3", "xtensa", "procmacros/has-ulp-core", "xtensa-lx/spin",
## Move the stack to start of RAM to get zero-cost stack overflow protection
## (ESP32-C6 and ESPS32-H2 only!).
flip-link = ["esp-riscv-rt/fix-sp"]
## Initialize the `.data` section of memory.
rv-init-data = ["esp-riscv-rt/init-data", "esp-riscv-rt/init-rw-text"]
## Zero the `.bss` section of low-power memory.
rv-zero-rtc-bss = ["esp-riscv-rt/zero-rtc-fast-bss"]
## Initialize the `.data` section of low-power memory.
rv-init-rtc-data = ["esp-riscv-rt/init-rtc-fast-data", "esp-riscv-rt/init-rtc-fast-text"]

#! ### Trait Implementation Feature Flags
## Enable support for asynchronous operation, with interfaces provided by
Expand Down
6 changes: 4 additions & 2 deletions esp-hal/ld/sections/rtc_fast.x
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ SECTIONS {
. = ALIGN(4);
} > RTC_FAST_RWDATA

.rtc_fast.noinit (NOLOAD) :
.rtc_fast.persistent (NOLOAD) :
{
. = ALIGN(4);
*(.rtc_fast.noinit .rtc_fast.noinit.*)
_rtc_fast_persistent_start = ABSOLUTE(.);
*(.rtc_fast.persistent .rtc_fast.persistent.*)
_rtc_fast_persistent_end = ABSOLUTE(.);
. = ALIGN(4);
} > RTC_FAST_RWDATA
}
6 changes: 4 additions & 2 deletions esp-hal/ld/sections/rtc_slow.x
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,12 @@ SECTIONS {
. = ALIGN(4);
} > rtc_slow_seg

.rtc_slow.noinit (NOLOAD) :
.rtc_slow.persistent (NOLOAD) :
{
. = ALIGN(4);
*(.rtc_slow.noinit .rtc_slow.noinit.*)
_rtc_slow_persistent_start = ABSOLUTE(.);
*(.rtc_slow.persistent .rtc_slow.persistent.*)
_rtc_slow_persistent_end = ABSOLUTE(.);
. = ALIGN(4);
} > rtc_slow_seg
}
33 changes: 33 additions & 0 deletions esp-hal/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,39 @@ pub(crate) mod private {
pub struct Internal;
}

/// Marker trait for types that can be safely used in `#[ram(persistent)]`.
///
/// # Safety
///
/// - The type must be inhabited
/// - The type must be valid for any bit pattern of its backing memory in case a
/// reset occurs during a write or a reset interrupts the zero initialization
/// on first boot.
/// - Structs must contain only `Persistable` fields and padding
pub unsafe trait Persistable: Sized {}

macro_rules! impl_persistable {
($($t:ty),+) => {$(
unsafe impl Persistable for $t {}
)+};
(atomic $($t:ident),+) => {$(
unsafe impl Persistable for portable_atomic::$t {}
)+};
}

impl_persistable!(u8, i8, u16, i16, u32, i32, u64, i64, u128, i128, usize, isize, f32, f64);
impl_persistable!(atomic AtomicU8, AtomicI8, AtomicU16, AtomicI16, AtomicU32, AtomicI32, AtomicUsize, AtomicIsize);

unsafe impl<T: Persistable, const N: usize> Persistable for [T; N] {}

#[doc(hidden)]
pub mod __macro_implementation {
//! Unstable private implementation details of esp-hal-procmacros.

pub const fn assert_is_zeroable<T: bytemuck::Zeroable>() {}
pub const fn assert_is_persistable<T: super::Persistable>() {}
}

/// Available CPU cores
///
/// The actual number of available cores depends on the target.
Expand Down
22 changes: 21 additions & 1 deletion esp-hal/src/soc/esp32/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@
use core::ptr::addr_of_mut;

use self::peripherals::{LPWR, TIMG0, TIMG1};
use crate::{rtc_cntl::Rtc, timer::timg::Wdt};
use crate::{
rtc_cntl::{Rtc, SocResetReason},
timer::timg::Wdt,
};

pub mod cpu_control;
pub mod efuse;
Expand Down Expand Up @@ -55,9 +58,13 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
extern "C" {
static mut _rtc_fast_bss_start: u32;
static mut _rtc_fast_bss_end: u32;
static mut _rtc_fast_persistent_start: u32;
static mut _rtc_fast_persistent_end: u32;

static mut _rtc_slow_bss_start: u32;
static mut _rtc_slow_bss_end: u32;
static mut _rtc_slow_persistent_start: u32;
static mut _rtc_slow_persistent_end: u32;

static mut _stack_start_cpu0: u32;

Expand All @@ -79,6 +86,19 @@ pub unsafe extern "C" fn ESP32Reset() -> ! {
addr_of_mut!(_rtc_slow_bss_start),
addr_of_mut!(_rtc_slow_bss_end),
);
if matches!(
crate::reset::get_reset_reason(),
None | Some(SocResetReason::ChipPowerOn)
) {
xtensa_lx_rt::zero_bss(
addr_of_mut!(_rtc_fast_persistent_start),
addr_of_mut!(_rtc_fast_persistent_end),
);
xtensa_lx_rt::zero_bss(
addr_of_mut!(_rtc_slow_persistent_start),
addr_of_mut!(_rtc_slow_persistent_end),
);
}

unsafe {
let stack_chk_guard = core::ptr::addr_of_mut!(__stack_chk_guard);
Expand Down
Loading