Skip to content

Commit 79bbd67

Browse files
authored
Fix MMU page size handling (#835)
* Extract IROM_ALIGN uses * Add possible MMU page sizes * Read MMU page size from app descriptor, add --mmu-page-size * Validate final page size * Fix PR number in changelog * Clippy * Add test * Undo Segment changes
1 parent e6af2af commit 79bbd67

File tree

16 files changed

+211
-15
lines changed

16 files changed

+211
-15
lines changed

CHANGELOG.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
2323
- `espflash` can detect the chip from ESP-HAL metadata to prevent flashing firmware built for a different device. Reqires `esp-hal` 1.0.0-beta.0 (presumably, yet to be released) (#816)
2424
- `espflash` no longer allows flashing a too-big partition table (#830)
2525
- Allow specifying a partition label for `write-bin`, add `--partition-table`. (#828)
26+
- `--mmu-page-size` parameter for `flash` and `save-image` (#835)
2627

2728
### Changed
2829

@@ -48,6 +49,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4849
- Fix `read-flash` which didn't work with some lengths (#804)
4950
- espflash can now flash an ESP32-S2 in download mode over USB (#813)
5051
- Fixed a case where esplash transformed the firmware elf in a way that made it unbootable (#831)
52+
- The app descriptor is now correctly placed in the front of the bianry (#835)
53+
- espflash now extracts the MMU page size from the app descriptor (#835)
5154

5255
### Removed
5356

espflash/src/cli/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,9 @@ pub struct ImageArgs {
249249
/// Minimum chip revision supported by image, in format: major.minor
250250
#[arg(long, default_value = "0.0", value_parser = parse_chip_rev)]
251251
pub min_chip_rev: u16,
252+
/// MMU page size.
253+
#[arg(long, value_name = "MMU_PAGE_SIZE", value_parser = parse_u32)]
254+
pub mmu_page_size: Option<u32>,
252255
}
253256

254257
#[derive(Debug, Args)]
@@ -1027,6 +1030,7 @@ pub fn make_flash_data(
10271030
image_args.target_app_partition,
10281031
flash_settings,
10291032
image_args.min_chip_rev,
1033+
image_args.mmu_page_size,
10301034
)
10311035
}
10321036

espflash/src/error.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,10 @@ pub enum Error {
183183
)]
184184
InvalidElf(#[from] object::Error),
185185

186+
#[error("Supplied ELF image contains an invalid application descriptor")]
187+
#[diagnostic(code(espflash::invalid_app_descriptor))]
188+
InvalidAppDescriptor(#[from] AppDescriptorError),
189+
186190
#[error("The bootloader returned an error")]
187191
#[cfg(feature = "serialport")]
188192
#[diagnostic(transparent)]
@@ -259,6 +263,22 @@ impl From<SlipError> for Error {
259263
}
260264
}
261265

266+
/// App descriptor errors
267+
#[derive(Debug, Diagnostic, Error)]
268+
#[non_exhaustive]
269+
pub enum AppDescriptorError {
270+
#[error("Invalid app descriptor magic word: {0:#x}")]
271+
#[diagnostic(code(espflash::invalid_app_descriptor_magic_word))]
272+
MagicWordMismatch(u32),
273+
274+
#[error("The app description segment is not aligned to any valid MMU page size.")]
275+
#[diagnostic(
276+
code(espflash::invalid_app_descriptor_alignment),
277+
help("Try specifying the MMU page size manually.")
278+
)]
279+
IncorrectDescriptorAlignment,
280+
}
281+
262282
/// Connection-related errors
263283
#[derive(Debug, Diagnostic, Error)]
264284
#[non_exhaustive]

espflash/src/flasher/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,7 @@ pub struct FlashData {
476476
pub target_app_partition: Option<String>,
477477
pub flash_settings: FlashSettings,
478478
pub min_chip_rev: u16,
479+
pub mmu_page_size: Option<u32>,
479480
}
480481

481482
impl FlashData {
@@ -486,6 +487,7 @@ impl FlashData {
486487
target_app_partition: Option<String>,
487488
flash_settings: FlashSettings,
488489
min_chip_rev: u16,
490+
mmu_page_size: Option<u32>,
489491
) -> Result<Self, Error> {
490492
// If the '--bootloader' option is provided, load the binary file at the
491493
// specified path.
@@ -517,6 +519,7 @@ impl FlashData {
517519
target_app_partition,
518520
flash_settings,
519521
min_chip_rev,
522+
mmu_page_size,
520523
})
521524
}
522525
}

espflash/src/image_format/esp_idf.rs

Lines changed: 148 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
//! ESP-IDF application binary image format
22
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};
44

5-
use bytemuck::{bytes_of, from_bytes, Pod, Zeroable};
5+
use bytemuck::{bytes_of, from_bytes, pod_read_unaligned, Pod, Zeroable};
66
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};
89
use sha2::{Digest, Sha256};
910

1011
use super::{ram_segments, rom_segments, Segment};
1112
use crate::{
13+
error::AppDescriptorError,
1214
flasher::{FlashData, FlashFrequency, FlashMode, FlashSize},
1315
targets::{Chip, Esp32Params},
1416
Error,
@@ -104,6 +106,46 @@ struct SegmentHeader {
104106
length: u32,
105107
}
106108

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+
107149
/// Image format for ESP32 family chips using the second-stage bootloader from
108150
/// ESP-IDF
109151
#[derive(Debug)]
@@ -217,17 +259,108 @@ impl<'a> IdfBootloaderFormat<'a> {
217259
// alignment by padding segments might result in overlapping segments. We
218260
// need to merge adjacent segments first to avoid the possibility of them
219261
// overlapping, and then do the padding.
220-
let flash_segments: Vec<_> =
262+
let mut flash_segments: Vec<_> =
221263
pad_align_segments(merge_adjacent_segments(rom_segments(chip, &elf).collect()));
222264
let mut ram_segments: Vec<_> =
223265
pad_align_segments(merge_adjacent_segments(ram_segments(chip, &elf).collect()));
224266

225267
let mut checksum = ESP_CHECKSUM_MAGIC;
226268
let mut segment_count = 0;
227269

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+
228361
for segment in flash_segments {
229362
loop {
230-
let pad_len = segment_padding(data.len(), &segment);
363+
let pad_len = segment_padding(data.len(), &segment, mmu_page_size);
231364
if pad_len > 0 {
232365
if pad_len > SEG_HEADER_LEN {
233366
if let Some(ram_segment) = ram_segments.first_mut() {
@@ -259,7 +392,7 @@ impl<'a> IdfBootloaderFormat<'a> {
259392
}
260393
}
261394

262-
checksum = save_flash_segment(&mut data, segment, checksum)?;
395+
checksum = save_flash_segment(&mut data, segment, checksum, mmu_page_size)?;
263396
segment_count += 1;
264397
}
265398

@@ -420,16 +553,16 @@ fn default_partition_table(params: &Esp32Params, flash_size: Option<u32>) -> Par
420553
///
421554
/// (this is because the segment's vaddr may not be IROM_ALIGNed, more likely is
422555
/// 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;
426559

427-
if pad_len % IROM_ALIGN == 0 {
560+
if pad_len % align_to == 0 {
428561
0
429562
} else if pad_len > SEG_HEADER_LEN {
430563
pad_len - SEG_HEADER_LEN
431564
} else {
432-
pad_len + IROM_ALIGN - SEG_HEADER_LEN
565+
pad_len + align_to - SEG_HEADER_LEN
433566
}
434567
}
435568

@@ -462,17 +595,18 @@ fn save_flash_segment(
462595
data: &mut Vec<u8>,
463596
mut segment: Segment<'_>,
464597
checksum: u8,
598+
mmu_page_size: u32,
465599
) -> Result<u8, Error> {
466600
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;
468602

469-
if segment_reminder < 0x24 {
603+
if segment_remainder < 0x24 {
470604
// Work around a bug in ESP-IDF 2nd stage bootloader, that it didn't map the
471605
// last MMU page, if an IROM/DROM segment was < 0x24 bytes over the page
472606
// boundary.
473607
static PADDING: [u8; 0x24] = [0; 0x24];
474608

475-
segment += &PADDING[0..(0x24 - segment_reminder as usize)];
609+
segment += &PADDING[0..(0x24 - segment_remainder as usize)];
476610
}
477611

478612
let checksum = save_segment(data, &segment, checksum)?;

espflash/src/targets/esp32.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,7 @@ impl Target for Esp32 {
182182
CHIP_ID,
183183
FlashFrequency::_40Mhz,
184184
bootloader,
185+
None,
185186
);
186187

187188
IdfBootloaderFormat::new(elf_data, Chip::Esp32, flash_data, params)

espflash/src/targets/esp32c2.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,7 @@ impl Target for Esp32c2 {
120120
CHIP_ID,
121121
FlashFrequency::_30Mhz,
122122
bootloader,
123+
Some(&[16 * 1024, 32 * 1024, 64 * 1024]),
123124
);
124125

125126
IdfBootloaderFormat::new(elf_data, Chip::Esp32c2, flash_data, params)

espflash/src/targets/esp32c3.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ const PARAMS: Esp32Params = Esp32Params::new(
3030
CHIP_ID,
3131
FlashFrequency::_40Mhz,
3232
include_bytes!("../../resources/bootloaders/esp32c3-bootloader.bin"),
33+
None,
3334
);
3435

3536
/// ESP32-C3 Target

espflash/src/targets/esp32c6.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const PARAMS: Esp32Params = Esp32Params::new(
2525
CHIP_ID,
2626
FlashFrequency::_40Mhz,
2727
include_bytes!("../../resources/bootloaders/esp32c6-bootloader.bin"),
28+
Some(&[8 * 1024, 16 * 1024, 32 * 1024, 64 * 1024]),
2829
);
2930

3031
/// ESP32-C6 Target

espflash/src/targets/esp32h2.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const PARAMS: Esp32Params = Esp32Params::new(
2525
CHIP_ID,
2626
FlashFrequency::_24Mhz,
2727
include_bytes!("../../resources/bootloaders/esp32h2-bootloader.bin"),
28+
Some(&[8 * 1024, 16 * 1024, 32 * 1024, 64 * 1024]),
2829
);
2930

3031
/// ESP32-H2 Target

0 commit comments

Comments
 (0)