ssh_stamp_esp32/boot.rs
1// SPDX-FileCopyrightText: 2026 Roman Valls Guimera <brainstorm@nopcode.org>
2// SPDX-FileCopyrightText: 2026 Julio Beltran Ortega <jubeormk1@gmail.com>
3// SPDX-FileCopyrightText: 2026 Angus Gratton <gus@projectgus.com>
4// SPDX-FileCopyrightText: 2026 Sergio Gasquez <sergio.gasquez@gmail.com>
5// SPDX-FileCopyrightText: 2026 pancake <pancake@nopcode.org>
6// SPDX-FileCopyrightText: 2026 Gabriel Ku Wei Bin <gabriel.ku@fsfe.org>
7// SPDX-FileCopyrightText: 2026 Anthony Tambasco <anthony.tambasco@fastmail.com>
8// SPDX-FileCopyrightText: 2026 Marko Malenic <mmalenic1@gmail.com>
9//
10// SPDX-License-Identifier: GPL-3.0-or-later
11
12//! The boot sequence macros which are used when initializing the device. These
13//! are separate macros so that they remain testable. Everything that consumes
14//! `Peripherals` must be a macro because fields like `TIMG1` or `SYSTIMER` are
15//! different per-chip, and cannot be resolved in a single function in the
16//! library crate.
17
18use embassy_executor::SendSpawner;
19use esp_hal::interrupt::Priority;
20use esp_hal::peripherals::FROM_CPU_INTR1;
21use esp_rtos::embassy::InterruptExecutor;
22use static_cell::StaticCell;
23
24/// Creates the global heap allocator using [`ssh_stamp::settings::HEAP_SIZE`].
25///
26/// The calling crate needs `ssh-stamp` and `esp-hal` as dependencies.
27#[macro_export]
28macro_rules! init_heap {
29 () => {
30 // TODO: This heap size will crash at runtime (only for the ESP32S2);
31 // see https://github.com/brainstorm/ssh-stamp/pull/41#issuecomment-2964775170
32 #[cfg(feature = "esp32s2")]
33 $crate::esp_alloc::heap_allocator!(#[$crate::esp_hal::ram(reclaimed)] size: $crate::ssh_stamp::settings::HEAP_SIZE);
34 #[cfg(not(feature = "esp32s2"))]
35 $crate::esp_alloc::heap_allocator!(size: $crate::ssh_stamp::settings::HEAP_SIZE);
36 };
37}
38
39/// Starts the esp-rtos scheduler on `TIMG1` for original ESP32 and `SYSTIMER` everywhere
40/// else. This registers the embassy time driver, so it must run before anything that
41/// uses `embassy-time`.
42///
43/// The scheduler uses `FROM_CPU_INTR0` for context switching, so the macro
44/// consumes that singleton and hands back [`FROM_CPU_INTR1`] for the
45/// interrupt executor.
46#[macro_export]
47macro_rules! start_rtos {
48 ($peripherals:ident) => {{
49 #[cfg(feature = "esp32")]
50 $crate::esp_rtos::start(
51 $crate::esp_hal::timer::timg::TimerGroup::new($peripherals.TIMG1).timer0,
52 $peripherals.FROM_CPU_INTR0,
53 );
54 #[cfg(not(feature = "esp32"))]
55 $crate::esp_rtos::start(
56 $crate::esp_hal::timer::systimer::SystemTimer::new($peripherals.SYSTIMER).alarm0,
57 $peripherals.FROM_CPU_INTR0,
58 );
59 $peripherals.FROM_CPU_INTR1
60 }};
61}
62
63/// The ssh-stamp boot sequence, which does heap allocation, `esp_hal::init`,
64/// configures the entropy source, flash storage and the esp-rtos scheduler.
65///
66/// The `Peripherals` struct cannot be returned once fields have been moved
67/// out of it, so the caller names the bindings and the macro introduces
68/// them into scope, e.g:
69///
70/// ```ignore
71/// ssh_stamp_esp32::boot!(peripherals, rng, entropy_source, sw_int1);
72/// ```
73#[macro_export]
74macro_rules! boot {
75 ($peripherals:ident, $rng:ident, $entropy_source:ident, $sw_int1:ident) => {
76 $crate::init_heap!();
77 $crate::esp_bootloader_esp_idf::esp_app_desc!();
78 $crate::esp_println::logger::init_logger_from_env();
79 $crate::bench::log_heap("boot");
80 $crate::log::debug!("HSM: initialising peripherals");
81
82 // Note that benches do depend on a stable clock speed across comparisons. The default
83 // shouldn't change much, but theoretically an upgrade could change it.
84 let $peripherals = $crate::esp_hal::init($crate::esp_hal::Config::default());
85
86 // Enable true random number generation before the config is created, so
87 // the WiFi password and SSH host key have cryptographically secure values.
88 let ($rng, $entropy_source) = $crate::init_entropy!($peripherals);
89
90 $crate::flash_init($peripherals.FLASH);
91 let $sw_int1 = $crate::start_rtos!($peripherals);
92 };
93}
94
95/// Starts the `InterruptExecutor` on the [`FROM_CPU_INTR1`] left over from
96/// [`start_rtos!`](macro@crate::start_rtos), and returns its spawner.
97pub fn start_interrupt_executor(sw_int1: FROM_CPU_INTR1<'static>) -> SendSpawner {
98 static INT_EXECUTOR: StaticCell<InterruptExecutor<1>> = StaticCell::new(); // 0 is used for esp_rtos
99
100 let interrupt_executor = INT_EXECUTOR.init_with(|| InterruptExecutor::new(sw_int1));
101 cfg_if::cfg_if! {
102 if #[cfg(any(feature = "esp32", feature = "esp32s2", feature = "esp32s3"))] {
103 let interrupt_spawner = interrupt_executor.start(Priority::Priority3);
104 } else if #[cfg(feature = "esp32c6")] {
105 let interrupt_spawner = interrupt_executor.start(Priority::Priority10);
106 } else {
107 let interrupt_spawner = interrupt_executor.start(Priority::Priority1);
108 }
109 }
110 interrupt_spawner
111}