|
1 | 1 | //! ESP-IDF application binary image format
|
2 | 2 |
|
3 |
| -use std::{borrow::Cow, io::Write, iter::once, mem::size_of}; |
| 3 | +use std::{borrow::Cow, ffi::c_char, io::Write, iter::once, mem::size_of}; |
4 | 4 |
|
5 |
| -use bytemuck::{bytes_of, from_bytes, Pod, Zeroable}; |
| 5 | +use bytemuck::{bytes_of, from_bytes, pod_read_unaligned, Pod, Zeroable}; |
6 | 6 | use esp_idf_part::{AppType, DataType, Partition, PartitionTable, SubType, Type};
|
7 |
| -use object::{read::elf::ElfFile32 as ElfFile, Endianness}; |
| 7 | +use log::warn; |
| 8 | +use object::{read::elf::ElfFile32 as ElfFile, Endianness, Object, ObjectSection}; |
8 | 9 | use sha2::{Digest, Sha256};
|
9 | 10 |
|
10 | 11 | use super::{ram_segments, rom_segments, Segment};
|
11 | 12 | use crate::{
|
| 13 | + error::AppDescriptorError, |
12 | 14 | flasher::{FlashData, FlashFrequency, FlashMode, FlashSize},
|
13 | 15 | targets::{Chip, Esp32Params},
|
14 | 16 | Error,
|
@@ -104,6 +106,46 @@ struct SegmentHeader {
|
104 | 106 | length: u32,
|
105 | 107 | }
|
106 | 108 |
|
| 109 | +/// Application descriptor used by the ESP-IDF bootloader. |
| 110 | +/// |
| 111 | +/// [Documentation](https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html#application-description) |
| 112 | +#[derive(Debug, Clone, Copy, Pod, Zeroable)] |
| 113 | +#[repr(C, packed)] |
| 114 | +#[doc(alias = "esp_app_desc_t")] |
| 115 | +struct AppDescriptor { |
| 116 | + /// Magic word ESP_APP_DESC_MAGIC_WORD |
| 117 | + magic_word: u32, |
| 118 | + /// Secure version |
| 119 | + secure_version: u32, |
| 120 | + reserv1: [u32; 2], |
| 121 | + /// Application version |
| 122 | + version: [c_char; 32], |
| 123 | + /// Project name |
| 124 | + project_name: [c_char; 32], |
| 125 | + /// Compile time |
| 126 | + time: [c_char; 16], |
| 127 | + /// Compile date |
| 128 | + date: [c_char; 16], |
| 129 | + /// Version IDF |
| 130 | + idf_ver: [c_char; 32], |
| 131 | + /// sha256 of elf file |
| 132 | + app_elf_sha256: [u8; 32], |
| 133 | + /// Minimal eFuse block revision supported by image, |
| 134 | + /// in format: major * 100 + minor |
| 135 | + min_efuse_blk_rev_full: u16, |
| 136 | + /// Maximal eFuse block revision supported by image, |
| 137 | + /// in format: major * 100 + minor |
| 138 | + max_efuse_blk_rev_full: u16, |
| 139 | + /// MMU page size in log base 2 format |
| 140 | + mmu_page_size: u8, |
| 141 | + reserv3: [u8; 3], |
| 142 | + reserv2: [u32; 18], |
| 143 | +} |
| 144 | + |
| 145 | +impl AppDescriptor { |
| 146 | + const ESP_APP_DESC_MAGIC_WORD: u32 = 0xABCD5432; |
| 147 | +} |
| 148 | + |
107 | 149 | /// Image format for ESP32 family chips using the second-stage bootloader from
|
108 | 150 | /// ESP-IDF
|
109 | 151 | #[derive(Debug)]
|
@@ -217,17 +259,108 @@ impl<'a> IdfBootloaderFormat<'a> {
|
217 | 259 | // alignment by padding segments might result in overlapping segments. We
|
218 | 260 | // need to merge adjacent segments first to avoid the possibility of them
|
219 | 261 | // overlapping, and then do the padding.
|
220 |
| - let flash_segments: Vec<_> = |
| 262 | + let mut flash_segments: Vec<_> = |
221 | 263 | pad_align_segments(merge_adjacent_segments(rom_segments(chip, &elf).collect()));
|
222 | 264 | let mut ram_segments: Vec<_> =
|
223 | 265 | pad_align_segments(merge_adjacent_segments(ram_segments(chip, &elf).collect()));
|
224 | 266 |
|
225 | 267 | let mut checksum = ESP_CHECKSUM_MAGIC;
|
226 | 268 | let mut segment_count = 0;
|
227 | 269 |
|
| 270 | + // Find and bubble the app descriptor segment to the first position. We do this |
| 271 | + // after merging/padding the segments, so it should be okay to reorder them now. |
| 272 | + let app_desc_addr = if let Some(appdesc) = elf.section_by_name(".flash.appdesc") { |
| 273 | + let address = appdesc.address() as u32; |
| 274 | + let Some(segment_position) = flash_segments |
| 275 | + .iter_mut() |
| 276 | + .position(|s| s.addr <= address && s.addr + s.size() > address) |
| 277 | + else { |
| 278 | + unreachable!("appdesc segment not found"); |
| 279 | + }; |
| 280 | + |
| 281 | + // We need to place the segment to the first position |
| 282 | + flash_segments[0..=segment_position].rotate_right(1); |
| 283 | + Some(address) |
| 284 | + } else { |
| 285 | + None |
| 286 | + }; |
| 287 | + |
| 288 | + let valid_page_sizes = params.mmu_page_sizes.unwrap_or(&[IROM_ALIGN]); |
| 289 | + let valid_page_sizes_string = valid_page_sizes |
| 290 | + .iter() |
| 291 | + .map(|size| format!("{:#x}", size)) |
| 292 | + .collect::<Vec<_>>() |
| 293 | + .join(", "); |
| 294 | + let app_desc_mmu_page_size = if let Some(address) = app_desc_addr { |
| 295 | + let segment = &flash_segments[0]; |
| 296 | + |
| 297 | + let offset = (address - segment.addr) as usize; |
| 298 | + let app_descriptor_size = size_of::<AppDescriptor>(); |
| 299 | + |
| 300 | + let segment_data = segment.data(); |
| 301 | + let app_descriptor_bytes = &segment_data[offset..][..app_descriptor_size]; |
| 302 | + let app_descriptor: AppDescriptor = pod_read_unaligned(app_descriptor_bytes); |
| 303 | + |
| 304 | + if app_descriptor.magic_word != AppDescriptor::ESP_APP_DESC_MAGIC_WORD { |
| 305 | + return Err( |
| 306 | + AppDescriptorError::MagicWordMismatch(app_descriptor.magic_word).into(), |
| 307 | + ); |
| 308 | + } |
| 309 | + |
| 310 | + if app_descriptor.mmu_page_size != 0 { |
| 311 | + // Read page size from the app descriptor |
| 312 | + Some(1 << app_descriptor.mmu_page_size) |
| 313 | + } else { |
| 314 | + // Infer from the app descriptor alignment |
| 315 | + |
| 316 | + // Subtract image + extended header (24 bytes) and segment header (8 bytes) |
| 317 | + let address = address - 32; |
| 318 | + |
| 319 | + // Page sizes are defined in ascenting order |
| 320 | + let mut page_size = None; |
| 321 | + for size in valid_page_sizes.iter().rev().copied() { |
| 322 | + if address % size == 0 { |
| 323 | + page_size = Some(size); |
| 324 | + break; |
| 325 | + } |
| 326 | + } |
| 327 | + |
| 328 | + if page_size.is_none() { |
| 329 | + warn!( |
| 330 | + "The app descriptor is placed at {:#x} which is not aligned to any of the \ |
| 331 | + supported page sizes: {}", |
| 332 | + address, valid_page_sizes_string |
| 333 | + ); |
| 334 | + return Err(AppDescriptorError::IncorrectDescriptorAlignment.into()); |
| 335 | + } |
| 336 | + |
| 337 | + page_size |
| 338 | + } |
| 339 | + } else { |
| 340 | + None |
| 341 | + }; |
| 342 | + |
| 343 | + // Precedence is: |
| 344 | + // - user input (unimplemented) |
| 345 | + // - app descriptor |
| 346 | + // - value based on app descriptor alignment |
| 347 | + // - default value |
| 348 | + let mmu_page_size = flash_data |
| 349 | + .mmu_page_size |
| 350 | + .or(app_desc_mmu_page_size) |
| 351 | + .unwrap_or(IROM_ALIGN); |
| 352 | + |
| 353 | + if !valid_page_sizes.contains(&mmu_page_size) { |
| 354 | + warn!( |
| 355 | + "MMU page size {:#x} is not supported. Supported page sizes are: {}", |
| 356 | + mmu_page_size, valid_page_sizes_string |
| 357 | + ); |
| 358 | + return Err(AppDescriptorError::IncorrectDescriptorAlignment.into()); |
| 359 | + }; |
| 360 | + |
228 | 361 | for segment in flash_segments {
|
229 | 362 | loop {
|
230 |
| - let pad_len = segment_padding(data.len(), &segment); |
| 363 | + let pad_len = segment_padding(data.len(), &segment, mmu_page_size); |
231 | 364 | if pad_len > 0 {
|
232 | 365 | if pad_len > SEG_HEADER_LEN {
|
233 | 366 | if let Some(ram_segment) = ram_segments.first_mut() {
|
@@ -259,7 +392,7 @@ impl<'a> IdfBootloaderFormat<'a> {
|
259 | 392 | }
|
260 | 393 | }
|
261 | 394 |
|
262 |
| - checksum = save_flash_segment(&mut data, segment, checksum)?; |
| 395 | + checksum = save_flash_segment(&mut data, segment, checksum, mmu_page_size)?; |
263 | 396 | segment_count += 1;
|
264 | 397 | }
|
265 | 398 |
|
@@ -420,16 +553,16 @@ fn default_partition_table(params: &Esp32Params, flash_size: Option<u32>) -> Par
|
420 | 553 | ///
|
421 | 554 | /// (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is
|
422 | 555 | /// aligned IROM_ALIGN+0x18 to account for the binary file header)
|
423 |
| -fn segment_padding(offset: usize, segment: &Segment<'_>) -> u32 { |
424 |
| - let align_past = (segment.addr - SEG_HEADER_LEN) % IROM_ALIGN; |
425 |
| - let pad_len = ((IROM_ALIGN - ((offset as u32) % IROM_ALIGN)) + align_past) % IROM_ALIGN; |
| 556 | +fn segment_padding(offset: usize, segment: &Segment<'_>, align_to: u32) -> u32 { |
| 557 | + let align_past = (segment.addr - SEG_HEADER_LEN) % align_to; |
| 558 | + let pad_len = ((align_to - ((offset as u32) % align_to)) + align_past) % align_to; |
426 | 559 |
|
427 |
| - if pad_len % IROM_ALIGN == 0 { |
| 560 | + if pad_len % align_to == 0 { |
428 | 561 | 0
|
429 | 562 | } else if pad_len > SEG_HEADER_LEN {
|
430 | 563 | pad_len - SEG_HEADER_LEN
|
431 | 564 | } else {
|
432 |
| - pad_len + IROM_ALIGN - SEG_HEADER_LEN |
| 565 | + pad_len + align_to - SEG_HEADER_LEN |
433 | 566 | }
|
434 | 567 | }
|
435 | 568 |
|
@@ -462,17 +595,18 @@ fn save_flash_segment(
|
462 | 595 | data: &mut Vec<u8>,
|
463 | 596 | mut segment: Segment<'_>,
|
464 | 597 | checksum: u8,
|
| 598 | + mmu_page_size: u32, |
465 | 599 | ) -> Result<u8, Error> {
|
466 | 600 | let end_pos = (data.len() + segment.data().len()) as u32 + SEG_HEADER_LEN;
|
467 |
| - let segment_reminder = end_pos % IROM_ALIGN; |
| 601 | + let segment_remainder = end_pos % mmu_page_size; |
468 | 602 |
|
469 |
| - if segment_reminder < 0x24 { |
| 603 | + if segment_remainder < 0x24 { |
470 | 604 | // Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the
|
471 | 605 | // last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page
|
472 | 606 | // boundary.
|
473 | 607 | static PADDING: [u8; 0x24] = [0; 0x24];
|
474 | 608 |
|
475 |
| - segment += &PADDING[0..(0x24 - segment_reminder as usize)]; |
| 609 | + segment += &PADDING[0..(0x24 - segment_remainder as usize)]; |
476 | 610 | }
|
477 | 611 |
|
478 | 612 | let checksum = save_segment(data, &segment, checksum)?;
|
|
0 commit comments