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, ignore
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, ignore
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//! );
66//!
67//! #[esp_hal::main]
68//! fn main() -> ! {
69//!     let _peripherals = esp_hal::init(esp_hal::Config::default());
70//!
71//!     loop {}
72//! }
73//! ```
74//!
75//! ## Additional configuration
76//!
77//! We've exposed some configuration options that don't fit into cargo
78//! features. These can be set via environment variables, or via cargo's `[env]`
79//! section inside `.cargo/config.toml`. Below is a table of tunable parameters
80//! for this crate:
81#![doc = ""]
82#![doc = include_str!(concat!(env!("OUT_DIR"), "/esp_bootloader_esp_idf_config_table.md"))]
83#![doc = ""]
84//! ## Feature Flags
85#![doc = document_features::document_features!(feature_label = r#"<span class="stab portability"><code>{feature}</code></span>"#)]
86#![doc(html_logo_url = "https://avatars.githubusercontent.com/u/46717278")]
87#![no_std]
88
89// MUST be the first module
90mod fmt;
91
92#[cfg(not(feature = "std"))]
93mod rom;
94#[cfg(not(feature = "std"))]
95pub(crate) use rom as crypto;
96
97#[cfg(feature = "std")]
98mod non_rom;
99#[cfg(embedded_test)]
100pub use crypto::Crc32 as Crc32ForTesting;
101#[cfg(feature = "std")]
102pub(crate) use non_rom as crypto;
103
104pub mod partitions;
105
106pub mod ota;
107
108// We run tests on the host which happens to be MacOS machines and mach-o
109// doesn't like `link-sections` this way
110#[cfg(not(target_os = "macos"))]
111#[unsafe(link_section = ".espressif.metadata")]
112#[used]
113#[unsafe(export_name = "bootloader.NAME")]
114static OTA_FEATURE: [u8; 7] = *b"ESP-IDF";
115
116/// ESP-IDF compatible application descriptor
117///
118/// This gets populated by the [esp_app_desc] macro.
119#[repr(C)]
120pub struct EspAppDesc {
121    /// Magic word ESP_APP_DESC_MAGIC_WORD
122    magic_word: u32,
123    /// Secure version
124    secure_version: u32,
125    /// Reserved
126    reserv1: [u32; 2],
127    /// Application version
128    version: [core::ffi::c_char; 32],
129    /// Project name
130    project_name: [core::ffi::c_char; 32],
131    /// Compile time
132    time: [core::ffi::c_char; 16],
133    /// Compile date
134    date: [core::ffi::c_char; 16],
135    /// Version IDF
136    idf_ver: [core::ffi::c_char; 32],
137    /// sha256 of elf file
138    app_elf_sha256: [u8; 32],
139    /// Minimal eFuse block revision supported by image, in format: major * 100
140    /// + minor
141    min_efuse_blk_rev_full: u16,
142    /// Maximal eFuse block revision supported by image, in format: major * 100
143    /// + minor
144    max_efuse_blk_rev_full: u16,
145    /// MMU page size in log base 2 format
146    mmu_page_size: u8,
147    /// Reserved
148    reserv3: [u8; 3],
149    /// Reserved
150    reserv2: [u32; 18],
151}
152
153impl EspAppDesc {
154    /// Needs to be public since it's used by the macro
155    #[doc(hidden)]
156    #[allow(clippy::too_many_arguments, reason = "For internal use only")]
157    pub const fn new_internal(
158        version: &str,
159        project_name: &str,
160        build_time: &str,
161        build_date: &str,
162        idf_ver: &str,
163        min_efuse_blk_rev_full: u16,
164        max_efuse_blk_rev_full: u16,
165        mmu_page_size: u32,
166    ) -> Self {
167        Self {
168            magic_word: ESP_APP_DESC_MAGIC_WORD,
169            secure_version: 0,
170            reserv1: [0; 2],
171            version: str_to_cstr_array(version),
172            project_name: str_to_cstr_array(project_name),
173            time: str_to_cstr_array(build_time),
174            date: str_to_cstr_array(build_date),
175            idf_ver: str_to_cstr_array(idf_ver),
176            app_elf_sha256: [0; 32],
177            min_efuse_blk_rev_full,
178            max_efuse_blk_rev_full,
179            mmu_page_size: (mmu_page_size.ilog2()) as u8,
180            reserv3: [0; 3],
181            reserv2: [0; 18],
182        }
183    }
184
185    /// The magic word - should be `0xABCD5432`
186    pub fn magic_word(&self) -> u32 {
187        self.magic_word
188    }
189
190    /// Secure version
191    pub fn secure_version(&self) -> u32 {
192        self.secure_version
193    }
194
195    /// Application version
196    pub fn version(&self) -> &str {
197        array_to_str(&self.version)
198    }
199
200    /// Application name
201    pub fn project_name(&self) -> &str {
202        array_to_str(&self.project_name)
203    }
204
205    /// Compile time
206    pub fn time(&self) -> &str {
207        array_to_str(&self.time)
208    }
209
210    /// Compile data
211    pub fn date(&self) -> &str {
212        array_to_str(&self.date)
213    }
214
215    /// IDF version
216    pub fn idf_ver(&self) -> &str {
217        array_to_str(&self.idf_ver)
218    }
219
220    /// SHA256
221    ///
222    /// The default tooling won't populate this
223    pub fn app_elf_sha256(&self) -> &[u8; 32] {
224        &self.app_elf_sha256
225    }
226
227    /// Minimal eFuse block revision supported by image
228    ///
229    /// Format `major * 100 + minor`
230    pub fn min_efuse_blk_rev_full(&self) -> u16 {
231        self.min_efuse_blk_rev_full
232    }
233
234    /// Maximal eFuse block revision supported by image
235    ///
236    /// Format `major * 100 + minor`
237    pub fn max_efuse_blk_rev_full(&self) -> u16 {
238        self.max_efuse_blk_rev_full
239    }
240
241    /// MMU page size in bytes
242    pub fn mmu_page_size(&self) -> u32 {
243        2_u32.pow(self.mmu_page_size as u32)
244    }
245}
246
247impl core::fmt::Debug for EspAppDesc {
248    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249        f.debug_struct("EspAppDesc")
250            .field("magic_word", &self.magic_word)
251            .field("secure_version", &self.secure_version)
252            .field("version", &self.version())
253            .field("project_name", &self.project_name())
254            .field("time", &self.time())
255            .field("date", &self.date())
256            .field("idf_ver", &self.idf_ver())
257            .field("app_elf_sha256", &self.app_elf_sha256)
258            .field("min_efuse_blk_rev_full", &self.min_efuse_blk_rev_full)
259            .field("max_efuse_blk_rev_full", &self.max_efuse_blk_rev_full)
260            .field("mmu_page_size", &self.mmu_page_size)
261            .finish()
262    }
263}
264
265#[cfg(feature = "defmt")]
266impl defmt::Format for EspAppDesc {
267    fn format(&self, fmt: defmt::Formatter) {
268        defmt::write!(
269            fmt,
270            "EspAppDesc (\
271            magic_word = {}, \
272            secure_version = {}, \
273            version = {}, \
274            project_name = {}, \
275            time = {}, \
276            date = {}, \
277            idf_ver = {}, \
278            app_elf_sha256 = {}, \
279            min_efuse_blk_rev_full = {}, \
280            max_efuse_blk_rev_full = {}, \
281            mmu_page_size = {}\
282            )",
283            self.magic_word,
284            self.secure_version,
285            self.version(),
286            self.project_name(),
287            self.time(),
288            self.date(),
289            self.idf_ver(),
290            self.app_elf_sha256,
291            self.min_efuse_blk_rev_full,
292            self.max_efuse_blk_rev_full,
293            self.mmu_page_size,
294        )
295    }
296}
297
298fn array_to_str(array: &[core::ffi::c_char]) -> &str {
299    let len = array.iter().position(|b| *b == 0).unwrap_or(array.len());
300    unsafe {
301        core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len))
302    }
303}
304
305const ESP_APP_DESC_MAGIC_WORD: u32 = 0xABCD5432;
306
307const fn str_to_cstr_array<const C: usize>(s: &str) -> [::core::ffi::c_char; C] {
308    let bytes = s.as_bytes();
309    let mut ret: [::core::ffi::c_char; C] = [0; C];
310    let mut i = 0;
311    loop {
312        ret[i] = bytes[i] as _;
313        i += 1;
314        if i >= bytes.len() {
315            break;
316        }
317    }
318    ret
319}
320
321/// Build time
322pub const BUILD_TIME: &str = env!("ESP_BOOTLOADER_BUILD_TIME");
323
324/// Build date
325pub const BUILD_DATE: &str = env!("ESP_BOOTLOADER_BUILD_DATE");
326
327/// MMU page size in bytes
328pub const MMU_PAGE_SIZE: u32 = {
329    let mmu_page_size =
330        esp_config::esp_config_str!("ESP_BOOTLOADER_ESP_IDF_CONFIG_MMU_PAGE_SIZE").as_bytes();
331    match mmu_page_size {
332        b"8k" => 8 * 1024,
333        b"16k" => 16 * 1024,
334        b"32k" => 32 * 1024,
335        b"64k" => 64 * 1024,
336        _ => 64 * 1024,
337    }
338};
339
340/// The (pretended) ESP-IDF version
341pub const ESP_IDF_COMPATIBLE_VERSION: &str =
342    esp_config::esp_config_str!("ESP_BOOTLOADER_ESP_IDF_CONFIG_MMU_PAGE_SIZE");
343
344/// This macro populates the application descriptor (see [EspAppDesc]) which is
345/// available as a static named `ESP_APP_DESC`
346///
347/// In most cases you can just use the no-arguments version of this macro.
348#[macro_export]
349macro_rules! esp_app_desc {
350    () => {
351        $crate::esp_app_desc!(
352            env!("CARGO_PKG_VERSION"),
353            env!("CARGO_PKG_NAME"),
354            $crate::BUILD_TIME,
355            $crate::BUILD_DATE,
356            $crate::ESP_IDF_COMPATIBLE_VERSION,
357            $crate::MMU_PAGE_SIZE,
358            0,
359            u16::MAX
360        );
361    };
362
363    (
364     $version: expr,
365     $project_name: expr,
366     $build_time: expr,
367     $build_date: expr,
368     $idf_ver: expr,
369     $mmu_page_size: expr,
370     $min_efuse_blk_rev_full: expr,
371     $max_efuse_blk_rev_full: expr
372    ) => {
373        #[unsafe(export_name = "esp_app_desc")]
374        #[unsafe(link_section = ".rodata_desc.appdesc")]
375        pub static ESP_APP_DESC: $crate::EspAppDesc = $crate::EspAppDesc::new_internal(
376            $version,
377            $project_name,
378            $build_time,
379            $build_date,
380            $idf_ver,
381            $min_efuse_blk_rev_full,
382            $max_efuse_blk_rev_full,
383            $mmu_page_size,
384        );
385    };
386}