Skip to main content

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}