Skip to main content

esp_bootloader_esp_idf/
lib.rs

1//! # Bootloader Support Library supplementing esp-hal
2//!
3//! ## Overview
4//!
5//! This crate contains functionality related to the ESP-IDF 2nd stage
6//! bootloader.
7//!
8//! - populating the application-descriptor
9//! - read the partition table
10//! - conveniently use a partition to read and write flash contents
11//!
12//! ## Examples
13//!
14//! ### Populating the Application Descriptor
15//!
16//! To use the default values:
17//!
18//! ```rust, no_run
19//! #![no_std]
20//! #![no_main]
21//!
22//! #[panic_handler]
23//! fn panic(_: &core::panic::PanicInfo) -> ! {
24//!     loop {}
25//! }
26//!
27//! esp_bootloader_esp_idf::esp_app_desc!();
28//!
29//! #[esp_hal::main]
30//! fn main() -> ! {
31//!     let _peripherals = esp_hal::init(esp_hal::Config::default());
32//!
33//!     loop {}
34//! }
35//! ```
36//!
37//! If you want to customize the application descriptor:
38//!
39//! ```rust, no_run
40//! #![no_std]
41//! #![no_main]
42//!
43//! #[panic_handler]
44//! fn panic(_: &core::panic::PanicInfo) -> ! {
45//!     loop {}
46//! }
47//!
48//! esp_bootloader_esp_idf::esp_app_desc!(
49//!     // Version
50//!     "1.0.0",
51//!     // Project name
52//!     "my_project",
53//!     // Build time
54//!     "12:00:00",
55//!     // Build date
56//!     "2021-01-01",
57//!     // ESP-IDF version
58//!     "4.4",
59//!     // MMU page size
60//!     8 * 1024,
61//!     // Minimal eFuse block revision supported by image. Format: major * 100 + minor
62//!     0,
63//!     // Maximum eFuse block revision supported by image. Format: major * 100 + minor
64//!     u16::MAX,
65//!     // Secure version
66//!     0
67//! );
68//!
69//! #[esp_hal::main]
70//! fn main() -> ! {
71//!     let _peripherals = esp_hal::init(esp_hal::Config::default());
72//!
73//!     loop {}
74//! }
75//! ```
76//!
77//! ## Reclaimed memory
78//!
79//! After the bootloader has started the application, the `.dram2_uninit` region becomes available
80//! for use. This region can be used for dynamic memory allocation or other purposes, but the data
81//! placed there cannot be initialized (i.e. it must be `MaybeUninit<T>`). For convenience, you can
82//! use the `#[esp_hal::ram(reclaimed)]` attribute, which will also check that the variable can be
83//! placed in the reclaimed memory.
84#![doc = ""]
85#![cfg_attr(not(feature = "std"), doc = concat!("For ", esp_metadata_generated::chip!(), " the size of the reclaimed memory is ", esp_metadata_generated::memory_range!(size as str, "DRAM2_UNINIT")," bytes."))]
86#![doc = ""]
87//! ## Additional configuration
88//!
89//! We've exposed some configuration options that don't fit into cargo
90//! features. These can be set via environment variables, or via cargo's `[env]`
91//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
92//! for this crate:
93#![doc = ""]
94#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_bootloader_esp_idf_config_table.md"))]
95#![doc = ""]
96//! ## Feature Flags
97#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
98#![doc(html_logo_url = "https://docs.espressif.com/projects/rust/esp-rs-grey-bg.svg")]
99#![no_std]
100
101// MUST be the first module
102mod fmt;
103
104#[cfg(not(feature = "std"))]
105mod rom;
106#[cfg(not(feature = "std"))]
107pub(crate) use rom as crypto;
108
109#[cfg(feature = "std")]
110mod non_rom;
111#[cfg(embedded_test)]
112pub use crypto::Crc32 as Crc32ForTesting;
113#[cfg(feature = "std")]
114pub(crate) use non_rom as crypto;
115
116mod flash;
117
118pub mod partitions;
119
120pub mod ota;
121
122pub mod ota_updater;
123
124// We run tests on the host which happens to be MacOS machines and mach-o
125// doesn't like `link-sections` this way
126#[cfg(not(target_os = "macos"))]
127#[unsafe(link_section = ".espressif.metadata")]
128#[used]
129#[unsafe(export_name = "bootloader.NAME")]
130static OTA_FEATURE: [u8; 7] = *b"ESP-IDF";
131
132/// ESP-IDF compatible application descriptor
133///
134/// This gets populated by the [esp_app_desc] macro.
135#[repr(C)]
136pub struct EspAppDesc {
137    /// Magic word ESP_APP_DESC_MAGIC_WORD
138    magic_word: u32,
139    /// Secure version
140    secure_version: u32,
141    /// Reserved
142    reserv1: [u32; 2],
143    /// Application version
144    version: [core::ffi::c_char; 32],
145    /// Project name
146    project_name: [core::ffi::c_char; 32],
147    /// Compile time
148    time: [core::ffi::c_char; 16],
149    /// Compile date
150    date: [core::ffi::c_char; 16],
151    /// Version IDF
152    idf_ver: [core::ffi::c_char; 32],
153    /// sha256 of elf file
154    app_elf_sha256: [u8; 32],
155    /// Minimal eFuse block revision supported by image, in format: major * 100
156    /// + minor
157    min_efuse_blk_rev_full: u16,
158    /// Maximal eFuse block revision supported by image, in format: major * 100
159    /// + minor
160    max_efuse_blk_rev_full: u16,
161    /// MMU page size in log base 2 format
162    mmu_page_size: u8,
163    /// Reserved
164    reserv3: [u8; 3],
165    /// Reserved
166    reserv2: [u32; 18],
167}
168
169impl EspAppDesc {
170    /// Needs to be public since it's used by the macro
171    #[doc(hidden)]
172    #[expect(clippy::too_many_arguments, reason = "For internal use only")]
173    pub const fn new_internal(
174        version: &str,
175        project_name: &str,
176        build_time: &str,
177        build_date: &str,
178        idf_ver: &str,
179        min_efuse_blk_rev_full: u16,
180        max_efuse_blk_rev_full: u16,
181        mmu_page_size: u32,
182        secure_version: u32,
183    ) -> Self {
184        Self {
185            magic_word: ESP_APP_DESC_MAGIC_WORD,
186            secure_version,
187            reserv1: [0; 2],
188            version: str_to_cstr_array(version),
189            project_name: str_to_cstr_array(project_name),
190            time: str_to_cstr_array(build_time),
191            date: str_to_cstr_array(build_date),
192            idf_ver: str_to_cstr_array(idf_ver),
193            app_elf_sha256: [0; 32],
194            min_efuse_blk_rev_full,
195            max_efuse_blk_rev_full,
196            mmu_page_size: (mmu_page_size.ilog2()) as u8,
197            reserv3: [0; 3],
198            reserv2: [0; 18],
199        }
200    }
201
202    /// The magic word - should be `0xABCD5432`
203    pub fn magic_word(&self) -> u32 {
204        self.magic_word
205    }
206
207    /// Secure version
208    pub fn secure_version(&self) -> u32 {
209        self.secure_version
210    }
211
212    /// Application version
213    pub fn version(&self) -> &str {
214        array_to_str(&self.version)
215    }
216
217    /// Application name
218    pub fn project_name(&self) -> &str {
219        array_to_str(&self.project_name)
220    }
221
222    /// Compile time
223    pub fn time(&self) -> &str {
224        array_to_str(&self.time)
225    }
226
227    /// Compile data
228    pub fn date(&self) -> &str {
229        array_to_str(&self.date)
230    }
231
232    /// IDF version
233    pub fn idf_ver(&self) -> &str {
234        array_to_str(&self.idf_ver)
235    }
236
237    /// SHA256
238    ///
239    /// The default tooling won't populate this
240    pub fn app_elf_sha256(&self) -> &[u8; 32] {
241        &self.app_elf_sha256
242    }
243
244    /// Minimal eFuse block revision supported by image
245    ///
246    /// Format `major * 100 + minor`
247    pub fn min_efuse_blk_rev_full(&self) -> u16 {
248        self.min_efuse_blk_rev_full
249    }
250
251    /// Maximal eFuse block revision supported by image
252    ///
253    /// Format `major * 100 + minor`
254    pub fn max_efuse_blk_rev_full(&self) -> u16 {
255        self.max_efuse_blk_rev_full
256    }
257
258    /// MMU page size in bytes
259    pub fn mmu_page_size(&self) -> u32 {
260        2_u32.pow(self.mmu_page_size as u32)
261    }
262}
263
264impl core::fmt::Debug for EspAppDesc {
265    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
266        f.debug_struct("EspAppDesc")
267            .field("magic_word", &self.magic_word)
268            .field("secure_version", &self.secure_version)
269            .field("version", &self.version())
270            .field("project_name", &self.project_name())
271            .field("time", &self.time())
272            .field("date", &self.date())
273            .field("idf_ver", &self.idf_ver())
274            .field("app_elf_sha256", &self.app_elf_sha256)
275            .field("min_efuse_blk_rev_full", &self.min_efuse_blk_rev_full)
276            .field("max_efuse_blk_rev_full", &self.max_efuse_blk_rev_full)
277            .field("mmu_page_size", &self.mmu_page_size)
278            .finish()
279    }
280}
281
282#[cfg(feature = "defmt")]
283impl defmt::Format for EspAppDesc {
284    fn format(&self, fmt: defmt::Formatter) {
285        defmt::write!(
286            fmt,
287            "EspAppDesc (\
288            magic_word = {}, \
289            secure_version = {}, \
290            version = {}, \
291            project_name = {}, \
292            time = {}, \
293            date = {}, \
294            idf_ver = {}, \
295            app_elf_sha256 = {}, \
296            min_efuse_blk_rev_full = {}, \
297            max_efuse_blk_rev_full = {}, \
298            mmu_page_size = {}\
299            )",
300            self.magic_word,
301            self.secure_version,
302            self.version(),
303            self.project_name(),
304            self.time(),
305            self.date(),
306            self.idf_ver(),
307            self.app_elf_sha256,
308            self.min_efuse_blk_rev_full,
309            self.max_efuse_blk_rev_full,
310            self.mmu_page_size,
311        )
312    }
313}
314
315fn array_to_str(array: &[core::ffi::c_char]) -> &str {
316    let len = array.iter().position(|b| *b == 0).unwrap_or(array.len());
317    unsafe {
318        core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len))
319    }
320}
321
322const ESP_APP_DESC_MAGIC_WORD: u32 = 0xABCD5432;
323
324const fn str_to_cstr_array<const C: usize>(s: &str) -> [::core::ffi::c_char; C] {
325    let bytes = s.as_bytes();
326    let mut ret: [::core::ffi::c_char; C] = [0; C];
327    let mut i = 0;
328    loop {
329        ret[i] = bytes[i] as _;
330        i += 1;
331        if i >= bytes.len() || i >= C {
332            break;
333        }
334    }
335    ret
336}
337
338/// Build time
339pub const BUILD_TIME: &str = env!("ESP_BOOTLOADER_BUILD_TIME");
340
341/// Build date
342pub const BUILD_DATE: &str = env!("ESP_BOOTLOADER_BUILD_DATE");
343
344/// MMU page size in bytes
345pub const MMU_PAGE_SIZE: u32 = {
346    let mmu_page_size =
347        esp_config::esp_config_str!("ESP_BOOTLOADER_ESP_IDF_CONFIG_MMU_PAGE_SIZE").as_bytes();
348    match mmu_page_size {
349        b"8k" => 8 * 1024,
350        b"16k" => 16 * 1024,
351        b"32k" => 32 * 1024,
352        b"64k" => 64 * 1024,
353        _ => 64 * 1024,
354    }
355};
356
357/// Secure version.
358pub const SECURE_VERSION: u32 =
359    esp_config::esp_config_int!(u32, "ESP_BOOTLOADER_ESP_IDF_CONFIG_SECURE_VERSION");
360
361/// The (pretended) ESP-IDF version
362pub const ESP_IDF_COMPATIBLE_VERSION: &str =
363    esp_config::esp_config_str!("ESP_BOOTLOADER_ESP_IDF_CONFIG_ESP_IDF_VERSION");
364
365/// This macro populates the application descriptor (see [EspAppDesc]) which is
366/// available as a static named `ESP_APP_DESC`
367///
368/// In most cases you can just use the no-arguments version of this macro.
369#[macro_export]
370macro_rules! esp_app_desc {
371    () => {
372        $crate::esp_app_desc!(
373            env!("CARGO_PKG_VERSION"),
374            env!("CARGO_PKG_NAME"),
375            $crate::BUILD_TIME,
376            $crate::BUILD_DATE,
377            $crate::ESP_IDF_COMPATIBLE_VERSION,
378            $crate::MMU_PAGE_SIZE,
379            0,
380            u16::MAX,
381            $crate::SECURE_VERSION
382        );
383    };
384
385    (
386     $version: expr,
387     $project_name: expr,
388     $build_time: expr,
389     $build_date: expr,
390     $idf_ver: expr,
391     $mmu_page_size: expr,
392     $min_efuse_blk_rev_full: expr,
393     $max_efuse_blk_rev_full: expr,
394     $secure_version: expr
395    ) => {
396        #[unsafe(export_name = "esp_app_desc")]
397        #[unsafe(link_section = ".flash.appdesc")]
398        #[used]
399        /// Application metadata descriptor.
400        pub static ESP_APP_DESC: $crate::EspAppDesc = $crate::EspAppDesc::new_internal(
401            $version,
402            $project_name,
403            $build_time,
404            $build_date,
405            $idf_ver,
406            $min_efuse_blk_rev_full,
407            $max_efuse_blk_rev_full,
408            $mmu_page_size,
409            $secure_version,
410        );
411    };
412}