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