Skip to content

Commit 93a2746

Browse files
committed
add support for flashing direct-boot images to esp32c3
1 parent 65f7873 commit 93a2746

File tree

9 files changed

+145
-46
lines changed

9 files changed

+145
-46
lines changed

cargo-espflash/src/main.rs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::{
88
use cargo_metadata::Message;
99
use clap::{App, Arg, ArgMatches, SubCommand};
1010
use error::Error;
11-
use espflash::{Chip, Config, FirmwareImage, Flasher, PartitionTable};
11+
use espflash::{Chip, Config, FirmwareImage, Flasher, ImageFormatId, PartitionTable};
1212
use miette::{IntoDiagnostic, Result, WrapErr};
1313
use monitor::monitor;
1414
use package_metadata::CargoEspFlashMeta;
@@ -17,6 +17,7 @@ use serial::{BaudRate, FlowControl, SerialPort};
1717
use crate::cargo_config::CargoConfig;
1818
use crate::error::NoTargetError;
1919
use crate::{cargo_config::parse_cargo_config, error::UnsupportedTargetError};
20+
use std::str::FromStr;
2021

2122
mod cargo_config;
2223
mod error;
@@ -42,6 +43,11 @@ fn main() -> Result<()> {
4243
.takes_value(true)
4344
.value_name("FEATURES")
4445
.help("Comma delimited list of build features"),
46+
Arg::with_name("format")
47+
.long("format")
48+
.takes_value(true)
49+
.value_name("image format")
50+
.help("Image format to flash"),
4551
];
4652
let connect_args = [Arg::with_name("serial")
4753
.takes_value(true)
@@ -228,12 +234,18 @@ fn flash(
228234
None
229235
};
230236

237+
let image_format = matches
238+
.value_of("format")
239+
.map(ImageFormatId::from_str)
240+
.transpose()
241+
.into_diagnostic()?;
242+
231243
// Read the ELF data from the build path and load it to the target.
232244
let elf_data = fs::read(path).into_diagnostic()?;
233245
if matches.is_present("ram") {
234246
flasher.load_elf_to_ram(&elf_data)?;
235247
} else {
236-
flasher.load_elf_to_flash(&elf_data, bootloader, partition_table)?;
248+
flasher.load_elf_to_flash(&elf_data, bootloader, partition_table, image_format)?;
237249
}
238250
println!("\nFlashing has completed!");
239251

@@ -266,13 +278,6 @@ fn build(
266278
cargo_config: &CargoConfig,
267279
chip: Option<Chip>,
268280
) -> Result<PathBuf> {
269-
// The 'build-std' unstable cargo feature is required to enable
270-
// cross-compilation. If it has not been set then we cannot build the
271-
// application.
272-
if !cargo_config.has_build_std() {
273-
return Err(Error::NoBuildStd.into());
274-
};
275-
276281
let target = cargo_config
277282
.target()
278283
.ok_or_else(|| NoTargetError::new(chip))?;
@@ -281,6 +286,13 @@ fn build(
281286
return Err(Error::UnsupportedTarget(UnsupportedTargetError::new(target, chip)).into());
282287
}
283288
}
289+
// The 'build-std' unstable cargo feature is required to enable
290+
// cross-compilation for xtensa targets.
291+
// If it has not been set then we cannot build the
292+
// application.
293+
if !cargo_config.has_build_std() && target.starts_with("xtensa-") {
294+
return Err(Error::NoBuildStd.into());
295+
};
284296

285297
// Build the list of arguments to pass to 'cargo build'.
286298
let mut args = vec![];
@@ -370,7 +382,13 @@ fn save_image(
370382

371383
let image = FirmwareImage::from_data(&elf_data)?;
372384

373-
let flash_image = chip.get_flash_image(&image, None, None, None)?;
385+
let image_format = matches
386+
.value_of("format")
387+
.map(ImageFormatId::from_str)
388+
.transpose()
389+
.into_diagnostic()?;
390+
391+
let flash_image = chip.get_flash_image(&image, None, None, image_format)?;
374392
let parts: Vec<_> = flash_image.ota_segments().collect();
375393

376394
let out_path = matches.value_of("file").unwrap();

espflash/src/chip/esp32/esp32c3.rs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use std::ops::Range;
22

33
use super::Esp32Params;
4+
use crate::image_format::Esp32DirectBootFormat;
45
use crate::{
56
chip::{bytes_to_mac_addr, ChipType, ReadEFuse, SpiRegisters},
67
connection::Connection,
@@ -79,9 +80,7 @@ impl ChipType for Esp32c3 {
7980
partition_table,
8081
bootloader,
8182
)?)),
82-
ImageFormatId::DirectBoot => {
83-
todo!()
84-
}
83+
ImageFormatId::DirectBoot => Ok(Box::new(Esp32DirectBootFormat::new(image)?)),
8584
}
8685
}
8786

espflash/src/elf.rs

Lines changed: 39 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use crate::error::{ElfError, Error};
66
use crate::flasher::FlashSize;
77
use std::fmt::{Debug, Formatter};
88
use std::mem::take;
9+
use std::ops::AddAssign;
910
use xmas_elf::sections::{SectionData, ShType};
1011
use xmas_elf::ElfFile;
1112

@@ -88,7 +89,7 @@ impl<'a> FirmwareImage<'a> {
8889
}
8990
}
9091

91-
#[derive(Eq, Clone)]
92+
#[derive(Eq, Clone, Default)]
9293
/// A segment of code from the source elf
9394
pub struct CodeSegment<'a> {
9495
pub addr: u32,
@@ -97,21 +98,12 @@ pub struct CodeSegment<'a> {
9798

9899
impl<'a> CodeSegment<'a> {
99100
pub fn new(addr: u32, data: &'a [u8]) -> Self {
100-
// pad to 4 byte
101-
let padding = (4 - data.len() % 4) % 4;
102-
if padding == 0 {
103-
CodeSegment {
104-
addr,
105-
data: Cow::Borrowed(data),
106-
}
107-
} else {
108-
let mut data = data.to_vec();
109-
data.extend_from_slice(&[0; 4][0..padding]);
110-
CodeSegment {
111-
addr,
112-
data: Cow::Owned(data),
113-
}
114-
}
101+
let mut segment = CodeSegment {
102+
addr,
103+
data: Cow::Borrowed(data),
104+
};
105+
segment.pad_align(4);
106+
segment
115107
}
116108

117109
/// Split of the first `count` bytes into a new segment, adjusting the remaining segment as needed
@@ -142,19 +134,41 @@ impl<'a> CodeSegment<'a> {
142134
}
143135
}
144136

145-
pub fn add(&mut self, extend: &[u8]) {
146-
let mut data = take(&mut self.data).into_owned();
147-
data.extend_from_slice(extend);
148-
self.data = Cow::Owned(data);
149-
}
150-
151137
pub fn size(&self) -> u32 {
152138
self.data.len() as u32
153139
}
154140

155141
pub fn data(&self) -> &[u8] {
156142
self.data.as_ref()
157143
}
144+
145+
pub fn pad_align(&mut self, align: usize) {
146+
let padding = (align - self.data.len() % align) % align;
147+
if padding > 0 {
148+
let mut data = take(&mut self.data).into_owned();
149+
data.extend_from_slice(&[0; 4][0..padding]);
150+
self.data = Cow::Owned(data);
151+
}
152+
}
153+
}
154+
155+
impl<'a> AddAssign<&'_ [u8]> for CodeSegment<'a> {
156+
fn add_assign(&mut self, rhs: &'_ [u8]) {
157+
let mut data = take(&mut self.data).into_owned();
158+
data.extend_from_slice(rhs);
159+
self.data = Cow::Owned(data);
160+
}
161+
}
162+
163+
impl<'a> AddAssign<&'_ CodeSegment<'_>> for CodeSegment<'a> {
164+
fn add_assign(&mut self, rhs: &'_ CodeSegment<'_>) {
165+
let mut data = take(&mut self.data).into_owned();
166+
// pad or truncate
167+
#[allow(clippy::suspicious_op_assign_impl)]
168+
data.resize((rhs.addr - self.addr) as usize, 0);
169+
data.extend_from_slice(rhs.data());
170+
self.data = Cow::Owned(data);
171+
}
158172
}
159173

160174
impl Debug for CodeSegment<'_> {
@@ -184,6 +198,7 @@ impl Ord for CodeSegment<'_> {
184198
}
185199
}
186200

201+
#[derive(Clone)]
187202
/// A segment of data to write to the flash
188203
pub struct RomSegment<'a> {
189204
pub addr: u32,
@@ -219,14 +234,14 @@ pub fn update_checksum(data: &[u8], mut checksum: u8) -> u8 {
219234
checksum
220235
}
221236

222-
pub fn merge_segments(mut segments: Vec<CodeSegment>) -> Vec<CodeSegment> {
237+
pub fn merge_adjacent_segments(mut segments: Vec<CodeSegment>) -> Vec<CodeSegment> {
223238
segments.sort();
224239

225240
let mut merged: Vec<CodeSegment> = Vec::with_capacity(segments.len());
226241
for segment in segments {
227242
match merged.last_mut() {
228243
Some(last) if last.addr + last.size() == segment.addr => {
229-
last.add(segment.data());
244+
*last += segment.data();
230245
}
231246
_ => {
232247
merged.push(segment);

espflash/src/flasher.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ use bytemuck::{__core::time::Duration, bytes_of, Pod, Zeroable};
44
use serial::{BaudRate, SystemPort};
55
use strum_macros::Display;
66

7+
use crate::image_format::ImageFormatId;
78
use crate::{
89
chip::Chip,
910
connection::Connection,
@@ -533,16 +534,17 @@ impl Flasher {
533534
elf_data: &[u8],
534535
bootloader: Option<Vec<u8>>,
535536
partition_table: Option<PartitionTable>,
537+
image_format: Option<ImageFormatId>,
536538
) -> Result<(), Error> {
537539
let mut image = FirmwareImage::from_data(elf_data)?;
538540
image.flash_size = self.flash_size();
539541

540542
let mut target = self.chip.flash_target(self.spi_params);
541543
target.begin(&mut self.connection, &image).flashing()?;
542544

543-
let flash_image = self
544-
.chip
545-
.get_flash_image(&image, bootloader, partition_table, None)?;
545+
let flash_image =
546+
self.chip
547+
.get_flash_image(&image, bootloader, partition_table, image_format)?;
546548

547549
for segment in flash_image.flash_segments() {
548550
target

espflash/src/image_format/esp32bootloader.rs

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use sha2::{Digest, Sha256};
66
use crate::{
77
chip::Esp32Params,
88
elf::{
9-
merge_segments, update_checksum, CodeSegment, FirmwareImage, RomSegment, ESP_CHECKSUM_MAGIC,
9+
merge_adjacent_segments, update_checksum, CodeSegment, FirmwareImage, RomSegment,
10+
ESP_CHECKSUM_MAGIC,
1011
},
1112
error::{Error, FlashDetectError},
1213
flasher::FlashSize,
@@ -62,8 +63,8 @@ impl<'a> Esp32BootloaderFormat<'a> {
6263

6364
let mut checksum = ESP_CHECKSUM_MAGIC;
6465

65-
let flash_segments: Vec<_> = merge_segments(image.rom_segments(chip).collect());
66-
let mut ram_segments: Vec<_> = merge_segments(image.ram_segments(chip).collect());
66+
let flash_segments: Vec<_> = merge_adjacent_segments(image.rom_segments(chip).collect());
67+
let mut ram_segments: Vec<_> = merge_adjacent_segments(image.ram_segments(chip).collect());
6768

6869
let mut segment_count = 0;
6970

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
use crate::elf::CodeSegment;
2+
use crate::{
3+
elf::{FirmwareImage, RomSegment},
4+
error::Error,
5+
image_format::ImageFormat,
6+
};
7+
use std::iter::once;
8+
9+
/// Image format for esp32 family chips using a 2nd stage bootloader
10+
pub struct Esp32DirectBootFormat<'a> {
11+
segment: RomSegment<'a>,
12+
}
13+
14+
impl<'a> Esp32DirectBootFormat<'a> {
15+
pub fn new(image: &'a FirmwareImage) -> Result<Self, Error> {
16+
let mut segment = image
17+
.segments()
18+
.map(|mut segment| {
19+
// map address to the first 4MB
20+
segment.addr %= 0x400000;
21+
segment
22+
})
23+
.fold(CodeSegment::default(), |mut a, b| {
24+
a += &b;
25+
a
26+
});
27+
segment.pad_align(4);
28+
29+
Ok(Self {
30+
segment: segment.into(),
31+
})
32+
}
33+
}
34+
35+
impl<'a> ImageFormat<'a> for Esp32DirectBootFormat<'a> {
36+
fn flash_segments<'b>(&'b self) -> Box<dyn Iterator<Item = RomSegment<'b>> + 'b>
37+
where
38+
'a: 'b,
39+
{
40+
Box::new(once(self.segment.borrow()))
41+
}
42+
43+
fn ota_segments<'b>(&'b self) -> Box<dyn Iterator<Item = RomSegment<'b>> + 'b>
44+
where
45+
'a: 'b,
46+
{
47+
Box::new(once(self.segment.borrow()))
48+
}
49+
}

espflash/src/image_format/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
mod esp32bootloader;
2+
mod esp32directboot;
23
mod esp8266;
34

45
use crate::elf::RomSegment;
56
use bytemuck::{Pod, Zeroable};
67
pub use esp32bootloader::*;
8+
pub use esp32directboot::*;
79
pub use esp8266::*;
810

911
use strum_macros::{AsStaticStr, Display, EnumString};

espflash/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ pub use config::Config;
1414
pub use elf::FirmwareImage;
1515
pub use error::Error;
1616
pub use flasher::Flasher;
17+
pub use image_format::ImageFormatId;
1718
pub use partition_table::PartitionTable;

espflash/src/main.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
use std::fs::{read, read_to_string};
22

3-
use espflash::{Config, Error, Flasher, PartitionTable};
3+
use espflash::{Config, Error, Flasher, ImageFormatId, PartitionTable};
44
use miette::{IntoDiagnostic, Result, WrapErr};
55
use pico_args::Arguments;
66
use serial::{BaudRate, FlowControl, SerialPort};
7+
use std::str::FromStr;
78

89
#[allow(clippy::unnecessary_wraps)]
910
fn help() -> Result<()> {
10-
println!("Usage: espflash [--board-info] [--ram] [--partition-table partition.csv] [--bootloader boot.bin] <serial> <elf image>");
11+
println!("Usage: espflash [--board-info] [--ram] [--partition-table partition.csv] [--bootloader boot.bin] [--format <bootloader|direct-boot> <serial> <elf image>");
1112
Ok(())
1213
}
1314

@@ -27,6 +28,9 @@ fn main() -> Result<()> {
2728
let partition_table_path = args
2829
.opt_value_from_str::<_, String>("--partition-table")
2930
.into_diagnostic()?;
31+
let image_format_string = args
32+
.opt_value_from_str::<_, String>("--format")
33+
.into_diagnostic()?;
3034

3135
let mut serial: Option<String> = args.opt_free_from_str().into_diagnostic()?;
3236
let mut elf: Option<String> = args.opt_free_from_str().into_diagnostic()?;
@@ -83,6 +87,14 @@ fn main() -> Result<()> {
8387
bootloader_path.unwrap()
8488
)
8589
})?;
90+
let image_format = image_format_string
91+
.as_deref()
92+
.map(ImageFormatId::from_str)
93+
.transpose()
94+
.into_diagnostic()
95+
.wrap_err_with(|| {
96+
format!("Unrecognized image format {}", image_format_string.unwrap())
97+
})?;
8698
let partition_table = partition_table_path
8799
.as_deref()
88100
.map(|path| {
@@ -97,7 +109,7 @@ fn main() -> Result<()> {
97109
partition_table_path.unwrap()
98110
)
99111
})?;
100-
flasher.load_elf_to_flash(&input_bytes, bootloader, partition_table)?;
112+
flasher.load_elf_to_flash(&input_bytes, bootloader, partition_table, image_format)?;
101113
}
102114

103115
Ok(())

0 commit comments

Comments
 (0)