Skip to content

Commit

Permalink
Crude POC for flash ESP app images instead of ELF files (esp-rs#770)
Browse files Browse the repository at this point in the history
  • Loading branch information
AVee committed Feb 17, 2025
1 parent 7b6e5e1 commit 12ca889
Show file tree
Hide file tree
Showing 17 changed files with 144 additions and 87 deletions.
33 changes: 21 additions & 12 deletions espflash/src/bin/espflash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,12 @@ use std::{
};

use clap::{Args, CommandFactory, Parser, Subcommand};
use espflash::{
cli::{self, config::Config, monitor::monitor, *},
error::Error,
flasher::parse_partition_table,
logging::initialize_logger,
targets::{Chip, XtalFrequency},
update::check_for_update,
};
use espflash::{cli::{self, config::Config, monitor::monitor, *}, error::Error, flasher::parse_partition_table, image_format, logging::initialize_logger, targets::{Chip, XtalFrequency}, update::check_for_update};
use log::{debug, info, LevelFilter};
use miette::{IntoDiagnostic, Result, WrapErr};
use xmas_elf::header;
use espflash::elf::{ElfFirmwareImage, FirmwareImage};
use espflash::esp_firmware_image::EspFirmwareImage;

#[derive(Debug, Parser)]
#[command(about, max_term_width = 100, propagate_version = true, version)]
Expand Down Expand Up @@ -259,10 +255,11 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> {
let target_xtal_freq = target.crystal_freq(flasher.connection())?;

// Read the ELF data from the build path and load it to the target.
let elf_data = fs::read(&args.image).into_diagnostic()?;
let file_data = fs::read(&args.image).into_diagnostic()?;
let image = image_from_bytes(&file_data)?;

if args.flash_args.ram {
flasher.load_elf_to_ram(&elf_data, Some(&mut EspflashProgress::default()))?;
flasher.load_elf_to_ram(image.as_ref(), Some(&mut EspflashProgress::default()))?;
} else {
let flash_data = make_flash_data(
args.flash_args.image,
Expand All @@ -281,7 +278,7 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> {
)?;
}

flash_elf_image(&mut flasher, &elf_data, flash_data, target_xtal_freq)?;
flash_elf_image(&mut flasher, image.as_ref(), flash_data, target_xtal_freq)?;
}

if args.flash_args.monitor {
Expand All @@ -299,12 +296,24 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> {

monitor_args.elf = Some(args.image);

monitor(flasher.into_serial(), Some(&elf_data), pid, monitor_args)
monitor(flasher.into_serial(), None, pid, monitor_args)
} else {
Ok(())
}
}

fn image_from_bytes<'a, 'b: 'a>(elf_data: &'b [u8]) -> Result<Box<dyn FirmwareImage<'a> + 'a>> {
if elf_data.len() < 4 {
todo!() // ERRORS
} else if elf_data.starts_with(&header::MAGIC) {
Ok(Box::new(ElfFirmwareImage::try_from(elf_data).unwrap()))
} else if elf_data[0] == image_format::ESP_MAGIC {
Ok(Box::new(EspFirmwareImage::new(elf_data)))
} else {
todo!()
}
}

fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> {
let elf_data = fs::read(&args.image)
.into_diagnostic()
Expand Down
7 changes: 4 additions & 3 deletions espflash/src/cli/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ use crate::{
},
targets::{Chip, XtalFrequency},
};
use crate::elf::FirmwareImage;

pub mod config;
pub mod monitor;
Expand Down Expand Up @@ -732,16 +733,16 @@ pub fn erase_region(args: EraseRegionArgs, config: &Config) -> Result<()> {
}

/// Write an ELF image to a target device's flash
pub fn flash_elf_image(
pub fn flash_elf_image<'a: 'b, 'b>(
flasher: &mut Flasher,
elf_data: &[u8],
image: &'b dyn FirmwareImage<'a>,
flash_data: FlashData,
xtal_freq: XtalFrequency,
) -> Result<()> {
// Load the ELF data, optionally using the provider bootloader/partition
// table/image format, to the device's flash memory.
flasher.load_elf_to_flash(
elf_data,
image,
flash_data,
Some(&mut EspflashProgress::default()),
xtal_freq,
Expand Down
32 changes: 5 additions & 27 deletions espflash/src/elf.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,21 +25,18 @@ pub trait FirmwareImage<'a> {
fn entry(&self) -> u32;

/// Firmware image segments
fn segments(&'a self) -> Box<dyn Iterator<Item = CodeSegment<'a>> + 'a>;

/// Firmware image segments, with their associated load addresses
fn segments_with_load_addresses(&'a self) -> Box<dyn Iterator<Item = CodeSegment<'a>> + 'a>;
fn segments(&self) -> Box<dyn Iterator<Item = CodeSegment<'_>> + '_>;

/// Firmware image ROM segments
fn rom_segments(&'a self, chip: Chip) -> Box<dyn Iterator<Item = CodeSegment<'a>> + 'a> {
fn rom_segments(&self, chip: Chip) -> Box<dyn Iterator<Item = CodeSegment<'_>> + '_> {
Box::new(
self.segments()
.filter(move |segment| chip.into_target().addr_is_flash(segment.addr)),
)
}

/// Firmware image RAM segments
fn ram_segments(&'a self, chip: Chip) -> Box<dyn Iterator<Item = CodeSegment<'a>> + 'a> {
fn ram_segments(&self, chip: Chip) -> Box<dyn Iterator<Item = CodeSegment<'_>> + '_> {
Box::new(
self.segments()
.filter(move |segment| !chip.into_target().addr_is_flash(segment.addr)),
Expand All @@ -59,7 +56,7 @@ impl<'a> ElfFirmwareImage<'a> {
}
}

impl<'a> TryFrom<&'a [u8]> for ElfFirmwareImage<'a> {
impl<'a, 'b: 'a> TryFrom<&'b [u8]> for ElfFirmwareImage<'a> {
type Error = Error;

fn try_from(value: &'a [u8]) -> Result<Self, Self::Error> {
Expand All @@ -76,7 +73,7 @@ impl<'a> FirmwareImage<'a> for ElfFirmwareImage<'a> {
self.elf.header.pt2.entry_point() as u32
}

fn segments(&'a self) -> Box<dyn Iterator<Item = CodeSegment<'a>> + 'a> {
fn segments(&self) -> Box<dyn Iterator<Item = CodeSegment<'_>> + '_> {
Box::new(
self.elf
.section_iter()
Expand All @@ -96,25 +93,6 @@ impl<'a> FirmwareImage<'a> for ElfFirmwareImage<'a> {
}),
)
}

fn segments_with_load_addresses(&'a self) -> Box<dyn Iterator<Item = CodeSegment<'a>> + 'a> {
Box::new(
self.elf
.program_iter()
.filter(|header| {
header.file_size() > 0
&& header.get_type() == Ok(Type::Load)
&& header.offset() > 0
})
.flat_map(move |header| {
let addr = header.physical_addr() as u32;
let from = header.offset() as usize;
let to = header.offset() as usize + header.file_size() as usize;
let data = &self.elf.input[from..to];
Some(CodeSegment::new(addr, data))
}),
)
}
}

#[derive(Eq, Clone, Default)]
Expand Down
57 changes: 57 additions & 0 deletions espflash/src/esp_firmware_image.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
use bytemuck::from_bytes;
use crate::elf::{CodeSegment, FirmwareImage};
use crate::image_format::{ImageHeader, SegmentHeader};

#[derive(Debug)]
pub struct EspFirmwareImage<'a> {
pub image_data: &'a [u8],
}

impl<'a> EspFirmwareImage<'a> {
pub fn new<'b: 'a>(image_data: &'b [u8]) -> Self {
// TODO: Validate
Self { image_data }
}
}

#[derive(Debug)]
pub struct SectionIter<'a> {
data: &'a [u8],
pos: usize,
remaining: u8,
}
impl<'a> Iterator for SectionIter<'a> {
type Item = CodeSegment<'a>;

fn next(&mut self) -> Option<Self::Item> {
if self.remaining > 0 {
self.remaining -= 1;
let segment: SegmentHeader = *from_bytes(&self.data[self.pos..self.pos + size_of::<SegmentHeader>()]);

let result = Some(CodeSegment::new(
segment.addr,
&self.data[self.pos + size_of::<SegmentHeader>() ..self.pos + size_of::<SegmentHeader>() + segment.length as usize]));
self.pos = self.pos + segment.length as usize + size_of::<SegmentHeader>();
result
}
else {
None
}
}
}

impl<'a> FirmwareImage<'a> for EspFirmwareImage<'a> {
fn entry(&self) -> u32 {
let header: ImageHeader = *from_bytes(&self.image_data[..size_of::<ImageHeader>()]);
header.entry
}

fn segments(&self) -> Box<dyn Iterator<Item=CodeSegment<'_>> + '_> {
let mut calc_bootloader_size = 0;
let bootloader_header_size = size_of::<ImageHeader>();
calc_bootloader_size += bootloader_header_size;

let header: ImageHeader = *from_bytes(&self.image_data[..size_of::<ImageHeader>()]);
Box::new(SectionIter { data: self.image_data, pos: calc_bootloader_size, remaining: header.segment_count })
}
}
16 changes: 8 additions & 8 deletions espflash/src/flasher/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ use crate::{
Connection,
Port,
},
elf::{ElfFirmwareImage, FirmwareImage, RomSegment},
elf::{FirmwareImage, RomSegment},
error::{ConnectionError, ResultExt},
flasher::stubs::{
FlashStub,
Expand Down Expand Up @@ -1089,12 +1089,12 @@ impl Flasher {
/// Load an ELF image to RAM and execute it
///
/// Note that this will not touch the flash on the device
pub fn load_elf_to_ram(
pub fn load_elf_to_ram<'a: 'b, 'b>(
&mut self,
elf_data: &[u8],
image: &'b dyn FirmwareImage<'a>,
mut progress: Option<&mut dyn ProgressCallbacks>,
) -> Result<(), Error> {
let image = ElfFirmwareImage::try_from(elf_data)?;
// let image = ElfFirmwareImage::try_from(elf_data)?;
if image.rom_segments(self.chip).next().is_some() {
return Err(Error::ElfNotRamLoadable);
}
Expand All @@ -1117,14 +1117,14 @@ impl Flasher {
}

/// Load an ELF image to flash and execute it
pub fn load_elf_to_flash(
pub fn load_elf_to_flash<'a: 'b, 'b>(
&mut self,
elf_data: &[u8],
image: &'b dyn FirmwareImage<'a>,
flash_data: FlashData,
mut progress: Option<&mut dyn ProgressCallbacks>,
xtal_freq: XtalFrequency,
) -> Result<(), Error> {
let image = ElfFirmwareImage::try_from(elf_data)?;
// let image = ElfFirmwareImage::try_from(elf_data)?;

let mut target =
self.chip
Expand All @@ -1138,7 +1138,7 @@ impl Flasher {
);

let image = self.chip.into_target().get_flash_image(
&image,
image,
flash_data,
chip_revision,
xtal_freq,
Expand Down
21 changes: 13 additions & 8 deletions espflash/src/image_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ use crate::{
};

const ESP_CHECKSUM_MAGIC: u8 = 0xef;
const ESP_MAGIC: u8 = 0xE9;
pub const ESP_MAGIC: u8 = 0xE9;
const IROM_ALIGN: u32 = 0x10000;
const SEG_HEADER_LEN: u32 = 8;
const WP_PIN_DISABLED: u8 = 0xEE;
Expand All @@ -28,17 +28,17 @@ const WP_PIN_DISABLED: u8 = 0xEE;
#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
#[doc(alias = "esp_image_header_t")]
struct ImageHeader {
pub(crate) struct ImageHeader {
magic: u8,
segment_count: u8,
pub(crate) segment_count: u8,
/// Flash read mode (esp_image_spi_mode_t)
flash_mode: u8,
/// ..4 bits are flash chip size (esp_image_flash_size_t)
/// 4.. bits are flash frequency (esp_image_spi_freq_t)
#[doc(alias = "spi_size")]
#[doc(alias = "spi_speed")]
flash_config: u8,
entry: u32,
pub(crate) entry: u32,

// extended header part
wp_pin: u8,
Expand Down Expand Up @@ -96,9 +96,9 @@ impl ImageHeader {

#[derive(Debug, Clone, Copy, Pod, Zeroable)]
#[repr(C, packed)]
struct SegmentHeader {
addr: u32,
length: u32,
pub(crate) struct SegmentHeader {
pub(crate) addr: u32,
pub(crate) length: u32,
}

/// Image format for ESP32 family chips using the second-stage bootloader from
Expand All @@ -117,7 +117,7 @@ pub struct IdfBootloaderFormat<'a> {
impl<'a> IdfBootloaderFormat<'a> {
#[allow(clippy::too_many_arguments)]
pub fn new(
image: &'a dyn FirmwareImage<'a>,
image: &dyn FirmwareImage<'a>,
chip: Chip,
min_rev_full: u16,
params: Esp32Params,
Expand Down Expand Up @@ -208,7 +208,12 @@ impl<'a> IdfBootloaderFormat<'a> {
let mut checksum = ESP_CHECKSUM_MAGIC;
let mut segment_count = 0;

eprintln!("Segments: {:#?}", flash_segments.len());
eprintln!("RAM Segments: {:#?}", ram_segments.len());
eprintln!();

for segment in flash_segments {
eprintln!("Content: {:#?}", segment.data().len());
loop {
let pad_len = get_segment_padding(data.len(), &segment);
if pad_len > 0 {
Expand Down
1 change: 1 addition & 0 deletions espflash/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ pub mod command;
#[cfg_attr(docsrs, doc(cfg(feature = "serialport")))]
pub mod connection;
pub mod elf;
pub mod esp_firmware_image;
pub mod error;
pub mod flasher;
pub mod image_format;
Expand Down
6 changes: 3 additions & 3 deletions espflash/src/targets/esp32.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,13 @@ impl Target for Esp32 {
Ok(norm_xtal)
}

fn get_flash_image<'a>(
fn get_flash_image<'a: 'b, 'b>(
&self,
image: &'a dyn FirmwareImage<'a>,
image: &'b dyn FirmwareImage<'a>,
flash_data: FlashData,
_chip_revision: Option<(u32, u32)>,
xtal_freq: XtalFrequency,
) -> Result<IdfBootloaderFormat<'a>, Error> {
) -> Result<IdfBootloaderFormat<'b>, Error> {
let booloader: &'static [u8] = match xtal_freq {
XtalFrequency::_40Mhz => {
include_bytes!("../../resources/bootloaders/esp32-bootloader.bin")
Expand Down
6 changes: 3 additions & 3 deletions espflash/src/targets/esp32c2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,13 +88,13 @@ impl Target for Esp32c2 {
HashMap::from(encodings)
}

fn get_flash_image<'a>(
fn get_flash_image<'a: 'b, 'b>(
&self,
image: &'a dyn FirmwareImage<'a>,
image: &'b dyn FirmwareImage<'a>,
flash_data: FlashData,
_chip_revision: Option<(u32, u32)>,
xtal_freq: XtalFrequency,
) -> Result<IdfBootloaderFormat<'a>, Error> {
) -> Result<IdfBootloaderFormat<'b>, Error> {
let booloader: &'static [u8] = match xtal_freq {
XtalFrequency::_40Mhz => {
debug!("Using 40MHz bootloader");
Expand Down
Loading

0 comments on commit 12ca889

Please sign in to comment.