From f625e38d9d193fe85061465ed855bd2bcd11c02e Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Mon, 5 Feb 2024 09:16:47 -0800 Subject: [PATCH 1/5] Remove the `DirectBoot` image format --- cargo-espflash/src/main.rs | 9 -- espflash/src/bin/espflash.rs | 9 -- espflash/src/cli/mod.rs | 4 - espflash/src/error.rs | 106 ++--------------------- espflash/src/flasher/mod.rs | 13 --- espflash/src/image_format/direct_boot.rs | 92 -------------------- espflash/src/image_format/mod.rs | 34 +------- espflash/src/targets/esp32.rs | 33 +++---- espflash/src/targets/esp32c2.rs | 35 +++----- espflash/src/targets/esp32c3.rs | 49 +++-------- espflash/src/targets/esp32c6.rs | 35 +++----- espflash/src/targets/esp32h2.rs | 38 +++----- espflash/src/targets/esp32p4.rs | 35 +++----- espflash/src/targets/esp32s2.rs | 33 +++---- espflash/src/targets/esp32s3.rs | 35 +++----- espflash/src/targets/mod.rs | 7 +- 16 files changed, 108 insertions(+), 459 deletions(-) delete mode 100644 espflash/src/image_format/direct_boot.rs diff --git a/cargo-espflash/src/main.rs b/cargo-espflash/src/main.rs index 2b8174b7..3f7ebbe0 100644 --- a/cargo-espflash/src/main.rs +++ b/cargo-espflash/src/main.rs @@ -16,7 +16,6 @@ use espflash::{ }, error::Error as EspflashError, flasher::{parse_partition_table, FlashData, FlashSettings}, - image_format::ImageFormatKind, logging::initialize_logger, targets::{Chip, XtalFrequency}, update::check_for_update, @@ -185,9 +184,6 @@ struct FlashArgs { #[derive(Debug, Args)] #[non_exhaustive] struct SaveImageArgs { - /// Image format to flash - #[arg(long, value_enum)] - pub format: Option, #[clap(flatten)] build_args: BuildArgs, #[clap(flatten)] @@ -327,7 +323,6 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> { bootloader, partition_table, args.flash_args.partition_table_offset, - args.flash_args.format, args.flash_args.target_app_partition, flash_settings, args.flash_args.min_chip_rev, @@ -557,9 +552,6 @@ fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> { // Since we have no `Flasher` instance and as such cannot print the board // information, we will print whatever information we _do_ have. println!("Chip type: {}", args.save_image_args.chip); - if let Some(format) = args.format { - println!("Image format: {:?}", format); - } println!("Merge: {}", args.save_image_args.merge); println!("Skip padding: {}", args.save_image_args.skip_padding); if let Some(path) = &args.save_image_args.bootloader { @@ -579,7 +571,6 @@ fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> { bootloader.as_deref(), partition_table.as_deref(), args.save_image_args.partition_table_offset, - args.format, args.save_image_args.target_app_partition, flash_settings, args.save_image_args.min_chip_rev, diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index 5780ac51..21902f04 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -15,7 +15,6 @@ use espflash::{ }, error::Error, flasher::{parse_partition_table, FlashData, FlashSettings}, - image_format::ImageFormatKind, logging::initialize_logger, targets::{Chip, XtalFrequency}, update::check_for_update, @@ -126,9 +125,6 @@ struct FlashArgs { struct SaveImageArgs { /// ELF image to flash image: PathBuf, - /// Image format to flash - #[arg(long, value_enum)] - format: Option, /// Flashing configuration #[clap(flatten)] pub flash_config_args: FlashConfigArgs, @@ -262,7 +258,6 @@ fn flash(args: FlashArgs, config: &Config) -> Result<()> { bootloader, partition_table, args.flash_args.partition_table_offset, - args.flash_args.format, args.flash_args.target_app_partition, flash_settings, args.flash_args.min_chip_rev, @@ -324,9 +319,6 @@ fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> { // Since we have no `Flasher` instance and as such cannot print the board // information, we will print whatever information we _do_ have. println!("Chip type: {}", args.save_image_args.chip); - if let Some(format) = args.format { - println!("Image format: {:?}", format); - } println!("Merge: {}", args.save_image_args.merge); println!("Skip padding: {}", args.save_image_args.skip_padding); if let Some(path) = &bootloader { @@ -346,7 +338,6 @@ fn save_image(args: SaveImageArgs, config: &Config) -> Result<()> { bootloader, partition_table, args.save_image_args.partition_table_offset, - args.format, args.save_image_args.target_app_partition, flash_settings, args.save_image_args.min_chip_rev, diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index c665badb..536e34ef 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -34,7 +34,6 @@ use crate::{ parse_partition_table, FlashData, FlashFrequency, FlashMode, FlashSize, Flasher, ProgressCallbacks, }, - image_format::ImageFormatKind, interface::Interface, targets::{Chip, XtalFrequency}, }; @@ -140,9 +139,6 @@ pub struct FlashArgs { /// Erase specified data partitions #[arg(long, value_name = "PARTS", value_enum, value_delimiter = ',')] pub erase_data_parts: Option>, - /// Image format to flash - #[arg(long, value_enum)] - pub format: Option, /// Logging format. #[arg(long, short = 'L', default_value = "serial", requires = "monitor")] pub log_format: LogFormat, diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 740c18bd..325ab9bc 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -5,6 +5,11 @@ use std::{ io, }; +use miette::Diagnostic; +use slip_codec::SlipError; +use strum::{FromRepr, VariantNames}; +use thiserror::Error; + #[cfg(feature = "cli")] use crate::cli::monitor::parser::esp_defmt::DefmtError; #[cfg(feature = "serialport")] @@ -12,15 +17,9 @@ use crate::interface::SerialConfigError; use crate::{ command::CommandType, flasher::{FlashFrequency, FlashSize}, - image_format::ImageFormatKind, targets::Chip, }; -use miette::Diagnostic; -use slip_codec::SlipError; -use strum::{FromRepr, VariantNames}; -use thiserror::Error; - /// All possible errors returned by espflash #[derive(Debug, Diagnostic, Error)] #[non_exhaustive] @@ -107,16 +106,6 @@ pub enum Error { #[diagnostic(code(espflash::invalid_bootloader_path))] InvalidBootloaderPath, - #[error("Binary is not set up correctly to support direct boot")] - #[diagnostic( - code(espflash::invalid_direct_boot), - help( - "See the following page for documentation on how to set up your binary for direct boot:\ - https://github.com/espressif/esp32c3-direct-boot-example" - ) - )] - InvalidDirectBootBinary, - #[error("The flash size '{0}' is invalid")] #[diagnostic( code(espflash::invalid_flash_size), @@ -165,13 +154,6 @@ pub enum Error { )] SerialNotFound(String), - #[error("Unrecognized image format '{0}'")] - #[diagnostic( - code(espflash::unknown_format), - help("The following image formats are supported: {}", ImageFormatKind::VARIANTS.join(", ")) - )] - UnknownImageFormat(String), - #[error("The {chip} does not support {feature}")] #[diagnostic(code(espflash::unsupported_feature))] UnsupportedFeature { chip: Chip, feature: String }, @@ -221,10 +203,6 @@ pub enum Error { #[diagnostic(transparent)] RomError(#[from] RomError), - #[error(transparent)] - #[diagnostic(transparent)] - UnsupportedImageFormat(#[from] UnsupportedImageFormatError), - #[cfg(feature = "serialport")] #[error(transparent)] #[diagnostic(transparent)] @@ -517,80 +495,6 @@ impl From<&'static str> for ElfError { } } -/// Unsupported image format error -#[derive(Debug)] -pub struct UnsupportedImageFormatError { - format: ImageFormatKind, - chip: Chip, - revision: Option<(u32, u32)>, - context: Option, -} - -impl UnsupportedImageFormatError { - pub fn new(format: ImageFormatKind, chip: Chip, revision: Option<(u32, u32)>) -> Self { - Self { - format, - chip, - revision, - context: None, - } - } - - /// Return a comma-separated list of supported image formats - fn supported_formats(&self) -> String { - self.chip - .into_target() - .supported_image_formats() - .iter() - .map(|format| format.into()) - .collect::>() - .join(", ") - } - - /// Update the context of the unsupported image format error - pub fn with_context(mut self, ctx: String) -> Self { - self.context.replace(ctx); - - self - } -} - -impl std::error::Error for UnsupportedImageFormatError {} - -impl Display for UnsupportedImageFormatError { - fn fmt(&self, f: &mut Formatter) -> std::fmt::Result { - write!( - f, - "Image format {} is not supported by the {}", - self.format, self.chip - )?; - - if let Some((major, minor)) = self.revision { - write!(f, " revision v{major}.{minor}")?; - } - - Ok(()) - } -} - -impl Diagnostic for UnsupportedImageFormatError { - fn code<'a>(&'a self) -> Option> { - Some(Box::new("espflash::unsupported_image_format")) - } - - fn help<'a>(&'a self) -> Option> { - if let Some(ref ctx) = self.context { - Some(Box::new(ctx)) - } else { - Some(Box::new(format!( - "The following image formats are supported by the {}: {}", - self.chip, - self.supported_formats() - ))) - } - } -} - pub(crate) trait ResultExt { /// Mark an error as having occurred during the flashing stage fn flashing(self) -> Self; diff --git a/espflash/src/flasher/mod.rs b/espflash/src/flasher/mod.rs index 41367b59..b65d5173 100644 --- a/espflash/src/flasher/mod.rs +++ b/espflash/src/flasher/mod.rs @@ -28,7 +28,6 @@ use crate::{ command::{Command, CommandType}, elf::{ElfFirmwareImage, FirmwareImage, RomSegment}, error::{ConnectionError, Error, ResultExt}, - image_format::ImageFormatKind, targets::{Chip, XtalFrequency}, }; #[cfg(feature = "serialport")] @@ -285,7 +284,6 @@ pub struct FlashDataBuilder<'a> { bootloader_path: Option<&'a Path>, partition_table_path: Option<&'a Path>, partition_table_offset: Option, - image_format: Option, target_app_partition: Option, flash_settings: FlashSettings, min_chip_rev: u16, @@ -297,7 +295,6 @@ impl<'a> Default for FlashDataBuilder<'a> { bootloader_path: Default::default(), partition_table_path: Default::default(), partition_table_offset: Default::default(), - image_format: Default::default(), target_app_partition: Default::default(), flash_settings: FlashSettings::default(), min_chip_rev: Default::default(), @@ -329,12 +326,6 @@ impl<'a> FlashDataBuilder<'a> { self } - /// Sets the image format. - pub fn with_image_format(mut self, image_format: ImageFormatKind) -> Self { - self.image_format = Some(image_format); - self - } - /// Sets the label of the target app partition. pub fn with_target_app_partition(mut self, target_app_partition: String) -> Self { self.target_app_partition = Some(target_app_partition); @@ -359,7 +350,6 @@ impl<'a> FlashDataBuilder<'a> { self.bootloader_path, self.partition_table_path, self.partition_table_offset, - self.image_format, self.target_app_partition, self.flash_settings, self.min_chip_rev, @@ -374,7 +364,6 @@ pub struct FlashData { pub bootloader: Option>, pub partition_table: Option, pub partition_table_offset: Option, - pub image_format: Option, pub target_app_partition: Option, pub flash_settings: FlashSettings, pub min_chip_rev: u16, @@ -385,7 +374,6 @@ impl FlashData { bootloader: Option<&Path>, partition_table: Option<&Path>, partition_table_offset: Option, - image_format: Option, target_app_partition: Option, flash_settings: FlashSettings, min_chip_rev: u16, @@ -412,7 +400,6 @@ impl FlashData { bootloader, partition_table, partition_table_offset, - image_format, target_app_partition, flash_settings, min_chip_rev, diff --git a/espflash/src/image_format/direct_boot.rs b/espflash/src/image_format/direct_boot.rs deleted file mode 100644 index 9399b12a..00000000 --- a/espflash/src/image_format/direct_boot.rs +++ /dev/null @@ -1,92 +0,0 @@ -use std::iter::once; - -use crate::{ - elf::{CodeSegment, FirmwareImage, RomSegment}, - error::Error, - image_format::ImageFormat, -}; - -/// Magic number for Direct boot which should be the first 8 bytes in flash -const DIRECT_BOOT_MAGIC: &[u8] = &[0x1d, 0x04, 0xdb, 0xae, 0x1d, 0x04, 0xdb, 0xae]; - -/// Image format for ESP32 family chips not using a second-stage bootloader -pub struct DirectBootFormat<'a> { - segment: RomSegment<'a>, -} - -impl<'a> DirectBootFormat<'a> { - pub fn new(image: &'a dyn FirmwareImage<'a>, magic_offset: usize) -> Result { - let mut segment = image - .segments_with_load_addresses() - .map(|mut segment| { - // Map the address to the first 4MB of address space - segment.addr %= 0x40_0000; - segment - }) - .fold(CodeSegment::default(), |mut a, b| { - a += &b; - a - }); - - segment.pad_align(4); - - if segment.addr != 0 - || (segment.data().len() >= magic_offset + 8 - && &segment.data()[magic_offset..][..8] != DIRECT_BOOT_MAGIC) - { - return Err(Error::InvalidDirectBootBinary); - } - - Ok(Self { - segment: segment.into(), - }) - } -} - -impl<'a> ImageFormat<'a> for DirectBootFormat<'a> { - fn flash_segments<'b>(&'b self) -> Box> + 'b> - where - 'a: 'b, - { - Box::new(once(self.segment.borrow())) - } - - fn ota_segments<'b>(&'b self) -> Box> + 'b> - where - 'a: 'b, - { - Box::new(once(self.segment.borrow())) - } - - fn app_size(&self) -> u32 { - self.segment.data.len() as u32 - } - - fn part_size(&self) -> Option { - None - } -} - -#[cfg(test)] -pub mod tests { - use std::fs; - - use super::*; - use crate::elf::ElfFirmwareImage; - - #[test] - fn test_direct_boot_format() { - let input_bytes = fs::read("tests/resources/esp32c3_hal_blinky_db").unwrap(); - let expected_bin = fs::read("tests/resources/esp32c3_hal_blinky_db.bin").unwrap(); - - let image = ElfFirmwareImage::try_from(input_bytes.as_slice()).unwrap(); - let flash_image = DirectBootFormat::new(&image, 0).unwrap(); - - let segments = flash_image.flash_segments().collect::>(); - assert_eq!(segments.len(), 1); - - let buf = segments[0].data.as_ref(); - assert_eq!(expected_bin.len(), buf.len()); - assert_eq!(expected_bin.as_slice(), buf); - } -} diff --git a/espflash/src/image_format/mod.rs b/espflash/src/image_format/mod.rs index a49d52d2..d8743081 100644 --- a/espflash/src/image_format/mod.rs +++ b/espflash/src/image_format/mod.rs @@ -1,12 +1,8 @@ //! ESP-IDF application binary image format -use std::str::FromStr; - use bytemuck::{Pod, Zeroable}; -use serde::Deserialize; -use strum::{Display, EnumVariantNames, IntoStaticStr}; -pub use self::{direct_boot::DirectBootFormat, idf_bootloader::IdfBootloaderFormat}; +pub use self::idf_bootloader::IdfBootloaderFormat; use crate::{ elf::RomSegment, error::Error, @@ -14,7 +10,6 @@ use crate::{ targets::Chip, }; -mod direct_boot; mod idf_bootloader; const ESP_CHECKSUM_MAGIC: u8 = 0xef; @@ -125,33 +120,6 @@ pub trait ImageFormat<'a>: Send { fn part_size(&self) -> Option; } -/// All supported firmware image formats -#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] -#[derive( - Debug, Copy, Clone, Eq, PartialEq, Display, IntoStaticStr, EnumVariantNames, Deserialize, -)] -#[non_exhaustive] -#[strum(serialize_all = "kebab-case")] -#[serde(rename_all = "kebab-case")] -pub enum ImageFormatKind { - /// Use the second-stage bootloader from ESP-IDF - EspBootloader, - /// Use direct boot and do not use a second-stage bootloader at all - DirectBoot, -} - -impl FromStr for ImageFormatKind { - type Err = Error; - - fn from_str(s: &str) -> Result { - match s { - "esp-bootloader" => Ok(Self::EspBootloader), - "direct-boot" => Ok(Self::DirectBoot), - _ => Err(Error::UnknownImageFormat(s.into())), - } - } -} - /// Update the checksum with the given data fn update_checksum(data: &[u8], mut checksum: u8) -> u8 { for byte in data { diff --git a/espflash/src/targets/esp32.rs b/espflash/src/targets/esp32.rs index 75acd046..b987c0d2 100644 --- a/espflash/src/targets/esp32.rs +++ b/espflash/src/targets/esp32.rs @@ -4,9 +4,9 @@ use std::ops::Range; use crate::{connection::Connection, targets::bytes_to_mac_addr}; use crate::{ elf::FirmwareImage, - error::{Error, UnsupportedImageFormatError}, + error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -156,10 +156,6 @@ impl Target for Esp32 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - let booloader: &'static [u8] = match xtal_freq { XtalFrequency::_40Mhz => { include_bytes!("../../resources/bootloaders/esp32-bootloader.bin") @@ -184,20 +180,17 @@ impl Target for Esp32 { booloader, ); - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32, - flash_data.min_chip_rev, - params, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - _ => Err(UnsupportedImageFormatError::new(image_format, Chip::Esp32, None).into()), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32, + flash_data.min_chip_rev, + params, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } #[cfg(feature = "serialport")] diff --git a/espflash/src/targets/esp32c2.rs b/espflash/src/targets/esp32c2.rs index ff707d53..65a5a249 100644 --- a/espflash/src/targets/esp32c2.rs +++ b/espflash/src/targets/esp32c2.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{DirectBootFormat, IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -89,10 +89,6 @@ impl Target for Esp32c2 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - let booloader: &'static [u8] = match xtal_freq { XtalFrequency::_40Mhz => { println!("Using 40MHz bootloader"); @@ -119,20 +115,17 @@ impl Target for Esp32c2 { booloader, ); - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32c2, - flash_data.min_chip_rev, - params, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - ImageFormatKind::DirectBoot => Ok(Box::new(DirectBootFormat::new(image, 0)?)), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32c2, + flash_data.min_chip_rev, + params, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } #[cfg(feature = "serialport")] @@ -160,10 +153,6 @@ impl Target for Esp32c2 { } } - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader, ImageFormatKind::DirectBoot] - } - fn supported_build_targets(&self) -> &[&str] { &[ "riscv32imac-unknown-none-elf", diff --git a/espflash/src/targets/esp32c3.rs b/espflash/src/targets/esp32c3.rs index 5a62d033..c9917361 100644 --- a/espflash/src/targets/esp32c3.rs +++ b/espflash/src/targets/esp32c3.rs @@ -4,9 +4,9 @@ use std::ops::Range; use crate::connection::Connection; use crate::{ elf::FirmwareImage, - error::{Error, UnsupportedImageFormatError}, + error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{DirectBootFormat, IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -80,13 +80,9 @@ impl Target for Esp32c3 { &self, image: &'a dyn FirmwareImage<'a>, flash_data: FlashData, - chip_revision: Option<(u32, u32)>, + _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32c3, @@ -94,30 +90,17 @@ impl Target for Esp32c3 { }); } - match (image_format, chip_revision) { - (ImageFormatKind::EspBootloader, _) => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32c3, - flash_data.min_chip_rev, - PARAMS, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - (ImageFormatKind::DirectBoot, None | Some((_, 3..))) => { - Ok(Box::new(DirectBootFormat::new(image, 0)?)) - } - _ => Err( - UnsupportedImageFormatError::new(image_format, Chip::Esp32c3, chip_revision) - .with_context(format!( - "The {} only supports direct-boot starting with revision 3 (v0.3)", - Chip::Esp32c3, - )) - .into(), - ), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32c3, + flash_data.min_chip_rev, + PARAMS, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } fn spi_registers(&self) -> SpiRegisters { @@ -132,10 +115,6 @@ impl Target for Esp32c3 { } } - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader, ImageFormatKind::DirectBoot] - } - fn supported_build_targets(&self) -> &[&str] { &[ "riscv32imac-unknown-none-elf", diff --git a/espflash/src/targets/esp32c6.rs b/espflash/src/targets/esp32c6.rs index 4b0587ba..7d7b278d 100644 --- a/espflash/src/targets/esp32c6.rs +++ b/espflash/src/targets/esp32c6.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{DirectBootFormat, IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -78,10 +78,6 @@ impl Target for Esp32c6 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32c6, @@ -89,20 +85,17 @@ impl Target for Esp32c6 { }); } - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32c6, - flash_data.min_chip_rev, - PARAMS, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - ImageFormatKind::DirectBoot => Ok(Box::new(DirectBootFormat::new(image, 0x0)?)), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32c6, + flash_data.min_chip_rev, + PARAMS, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } fn spi_registers(&self) -> SpiRegisters { @@ -117,10 +110,6 @@ impl Target for Esp32c6 { } } - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader, ImageFormatKind::DirectBoot] - } - fn supported_build_targets(&self) -> &[&str] { &["riscv32imac-esp-espidf", "riscv32imac-unknown-none-elf"] } diff --git a/espflash/src/targets/esp32h2.rs b/espflash/src/targets/esp32h2.rs index f62f60f7..d0471e74 100644 --- a/espflash/src/targets/esp32h2.rs +++ b/espflash/src/targets/esp32h2.rs @@ -1,5 +1,4 @@ -use std::collections::HashMap; -use std::ops::Range; +use std::{collections::HashMap, ops::Range}; #[cfg(feature = "serialport")] use crate::connection::Connection; @@ -7,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{DirectBootFormat, IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -86,10 +85,6 @@ impl Target for Esp32h2 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - if xtal_freq != XtalFrequency::_32Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32h2, @@ -97,20 +92,17 @@ impl Target for Esp32h2 { }); } - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32h2, - flash_data.min_chip_rev, - PARAMS, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - ImageFormatKind::DirectBoot => Ok(Box::new(DirectBootFormat::new(image, 0x0)?)), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32h2, + flash_data.min_chip_rev, + PARAMS, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } fn spi_registers(&self) -> SpiRegisters { @@ -125,10 +117,6 @@ impl Target for Esp32h2 { } } - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader, ImageFormatKind::DirectBoot] - } - fn supported_build_targets(&self) -> &[&str] { &["riscv32imac-esp-espidf", "riscv32imac-unknown-none-elf"] } diff --git a/espflash/src/targets/esp32p4.rs b/espflash/src/targets/esp32p4.rs index 7c93efdf..0768453c 100644 --- a/espflash/src/targets/esp32p4.rs +++ b/espflash/src/targets/esp32p4.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{DirectBootFormat, IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -76,10 +76,6 @@ impl Target for Esp32p4 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32p4, @@ -87,20 +83,17 @@ impl Target for Esp32p4 { }); } - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32p4, - flash_data.min_chip_rev, - PARAMS, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - ImageFormatKind::DirectBoot => Ok(Box::new(DirectBootFormat::new(image, 0x0)?)), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32p4, + flash_data.min_chip_rev, + PARAMS, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } fn spi_registers(&self) -> SpiRegisters { @@ -115,10 +108,6 @@ impl Target for Esp32p4 { } } - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader, ImageFormatKind::DirectBoot] - } - fn supported_build_targets(&self) -> &[&str] { &["riscv32imafc-esp-espidf", "riscv32imafc-unknown-none-elf"] } diff --git a/espflash/src/targets/esp32s2.rs b/espflash/src/targets/esp32s2.rs index 0ca7a833..8d6a4893 100644 --- a/espflash/src/targets/esp32s2.rs +++ b/espflash/src/targets/esp32s2.rs @@ -4,9 +4,9 @@ use std::ops::Range; use crate::{connection::Connection, targets::MAX_RAM_BLOCK_SIZE}; use crate::{ elf::FirmwareImage, - error::{Error, UnsupportedImageFormatError}, + error::Error, flasher::{FlashData, FlashFrequency, FLASH_WRITE_SIZE}, - image_format::{IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -151,10 +151,6 @@ impl Target for Esp32s2 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32s2, @@ -162,20 +158,17 @@ impl Target for Esp32s2 { }); } - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32s2, - flash_data.min_chip_rev, - PARAMS, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - _ => Err(UnsupportedImageFormatError::new(image_format, Chip::Esp32s2, None).into()), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32s2, + flash_data.min_chip_rev, + PARAMS, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } #[cfg(feature = "serialport")] diff --git a/espflash/src/targets/esp32s3.rs b/espflash/src/targets/esp32s3.rs index 54396e14..91531a5e 100644 --- a/espflash/src/targets/esp32s3.rs +++ b/espflash/src/targets/esp32s3.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{DirectBootFormat, IdfBootloaderFormat, ImageFormat, ImageFormatKind}, + image_format::{IdfBootloaderFormat, ImageFormat}, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -102,10 +102,6 @@ impl Target for Esp32s3 { _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, ) -> Result + 'a>, Error> { - let image_format = flash_data - .image_format - .unwrap_or(ImageFormatKind::EspBootloader); - if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32s3, @@ -113,20 +109,17 @@ impl Target for Esp32s3 { }); } - match image_format { - ImageFormatKind::EspBootloader => Ok(Box::new(IdfBootloaderFormat::new( - image, - Chip::Esp32s3, - flash_data.min_chip_rev, - PARAMS, - flash_data.partition_table, - flash_data.partition_table_offset, - flash_data.target_app_partition, - flash_data.bootloader, - flash_data.flash_settings, - )?)), - ImageFormatKind::DirectBoot => Ok(Box::new(DirectBootFormat::new(image, 0x400)?)), - } + Ok(Box::new(IdfBootloaderFormat::new( + image, + Chip::Esp32s3, + flash_data.min_chip_rev, + PARAMS, + flash_data.partition_table, + flash_data.partition_table_offset, + flash_data.target_app_partition, + flash_data.bootloader, + flash_data.flash_settings, + )?)) } fn spi_registers(&self) -> SpiRegisters { @@ -141,10 +134,6 @@ impl Target for Esp32s3 { } } - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader, ImageFormatKind::DirectBoot] - } - fn supported_build_targets(&self) -> &[&str] { &["xtensa-esp32s3-none-elf", "xtensa-esp32s3-espidf"] } diff --git a/espflash/src/targets/mod.rs b/espflash/src/targets/mod.rs index daea7cad..ad475160 100644 --- a/espflash/src/targets/mod.rs +++ b/espflash/src/targets/mod.rs @@ -29,7 +29,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency, SpiAttachParams, FLASH_WRITE_SIZE}, - image_format::{ImageFormat, ImageFormatKind}, + image_format::ImageFormat, }; /// Max partition size is 16 MB @@ -378,11 +378,6 @@ pub trait Target: ReadEFuse { /// SPI register addresses for a chip fn spi_registers(&self) -> SpiRegisters; - /// Image formats supported by a chip - fn supported_image_formats(&self) -> &[ImageFormatKind] { - &[ImageFormatKind::EspBootloader] - } - /// Build targets supported by a chip fn supported_build_targets(&self) -> &[&str]; From 1cc875427f63df59b11fa6faa6ad0892346d6188 Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Mon, 5 Feb 2024 09:29:14 -0800 Subject: [PATCH 2/5] Refactor the `ImageFormat` trait out of existence --- .../idf_bootloader.rs => image_format.rs} | 168 ++++++++++++------ espflash/src/image_format/mod.rs | 149 ---------------- espflash/src/targets/esp32.rs | 8 +- espflash/src/targets/esp32c2.rs | 8 +- espflash/src/targets/esp32c3.rs | 8 +- espflash/src/targets/esp32c6.rs | 8 +- espflash/src/targets/esp32h2.rs | 8 +- espflash/src/targets/esp32p4.rs | 8 +- espflash/src/targets/esp32s2.rs | 8 +- espflash/src/targets/esp32s3.rs | 8 +- espflash/src/targets/mod.rs | 4 +- 11 files changed, 150 insertions(+), 235 deletions(-) rename espflash/src/{image_format/idf_bootloader.rs => image_format.rs} (75%) delete mode 100644 espflash/src/image_format/mod.rs diff --git a/espflash/src/image_format/idf_bootloader.rs b/espflash/src/image_format.rs similarity index 75% rename from espflash/src/image_format/idf_bootloader.rs rename to espflash/src/image_format.rs index 3f654f54..d1d722cb 100644 --- a/espflash/src/image_format/idf_bootloader.rs +++ b/espflash/src/image_format.rs @@ -1,22 +1,104 @@ +//! ESP-IDF application binary image format + use std::{borrow::Cow, io::Write, iter::once, mem::size_of}; -use bytemuck::{bytes_of, from_bytes}; +use bytemuck::{bytes_of, from_bytes, Pod, Zeroable}; use esp_idf_part::{Partition, PartitionTable, Type}; use sha2::{Digest, Sha256}; use crate::{ elf::{CodeSegment, FirmwareImage, RomSegment}, error::Error, - flasher::FlashSettings, - image_format::{ - update_checksum, ImageFormat, ImageHeader, SegmentHeader, ESP_CHECKSUM_MAGIC, ESP_MAGIC, - WP_PIN_DISABLED, - }, + flasher::{FlashFrequency, FlashMode, FlashSettings, FlashSize}, targets::{Chip, Esp32Params}, }; +const ESP_CHECKSUM_MAGIC: u8 = 0xef; +const ESP_MAGIC: u8 = 0xE9; const IROM_ALIGN: u32 = 0x10000; const SEG_HEADER_LEN: u32 = 8; +const WP_PIN_DISABLED: u8 = 0xEE; + +/// Firmware header used by the ESP-IDF bootloader. +/// +/// ## Header documentation: +/// * [Header](https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/firmware-image-format.html#file-header) +/// * [Extended header](https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/firmware-image-format.html#extended-file-header) +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +#[doc(alias = "esp_image_header_t")] +struct ImageHeader { + magic: u8, + 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, + + // extended header part + wp_pin: u8, + clk_q_drv: u8, + d_cs_drv: u8, + gd_wp_drv: u8, + chip_id: u16, + min_rev: u8, + /// Minimum chip revision supported by image, in format: major * 100 + minor + min_chip_rev_full: u16, + /// Maximal chip revision supported by image, in format: major * 100 + minor + max_chip_rev_full: u16, + reserved: [u8; 4], + append_digest: u8, +} + +impl Default for ImageHeader { + fn default() -> Self { + Self { + magic: ESP_MAGIC, + segment_count: 3, + flash_mode: FlashMode::default() as _, + flash_config: ((FlashSize::default() as u8) << 4) | FlashFrequency::default() as u8, + entry: 0, + wp_pin: WP_PIN_DISABLED, + clk_q_drv: 0, + d_cs_drv: 0, + gd_wp_drv: 0, + chip_id: Default::default(), + min_rev: 0, + min_chip_rev_full: 0, + max_chip_rev_full: u16::MAX, + reserved: Default::default(), + append_digest: 1, + } + } +} + +impl ImageHeader { + /// Updates flash size and speed filed. + pub fn write_flash_config( + &mut self, + size: FlashSize, + freq: FlashFrequency, + chip: Chip, + ) -> Result<(), Error> { + let flash_size = size.encode_flash_size()?; + let flash_speed = freq.encode_flash_frequency(chip)?; + + // bit field + self.flash_config = (flash_size << 4) | flash_speed; + Ok(()) + } +} + +#[derive(Debug, Clone, Copy, Pod, Zeroable)] +#[repr(C, packed)] +struct SegmentHeader { + addr: u32, + length: u32, +} /// Image format for ESP32 family chips using the second-stage bootloader from /// ESP-IDF @@ -188,8 +270,9 @@ impl<'a> IdfBootloaderFormat<'a> { data: Cow::Owned(data), }; - // If the user did not specify a partition offset, we need to assume that the partition - // offset is (first partition offset) - 0x1000, since this is the most common case. + // If the user did not specify a partition offset, we need to assume that the + // partition offset is (first partition offset) - 0x1000, since this is + // the most common case. let partition_table_offset = partition_table_offset.unwrap_or_else(|| { let partitions = partition_table.partitions(); let first_partition = partitions @@ -209,10 +292,8 @@ impl<'a> IdfBootloaderFormat<'a> { partition_table_offset, }) } -} -impl<'a> ImageFormat<'a> for IdfBootloaderFormat<'a> { - fn flash_segments<'b>(&'b self) -> Box> + 'b> + pub fn flash_segments<'b>(&'b self) -> Box> + 'b> where 'a: 'b, { @@ -252,18 +333,18 @@ impl<'a> ImageFormat<'a> for IdfBootloaderFormat<'a> { ) } - fn ota_segments<'b>(&'b self) -> Box> + 'b> + pub fn ota_segments<'b>(&'b self) -> Box> + 'b> where 'a: 'b, { Box::new(once(self.flash_segment.borrow())) } - fn app_size(&self) -> u32 { + pub fn app_size(&self) -> u32 { self.app_size } - fn part_size(&self) -> Option { + pub fn part_size(&self) -> Option { Some(self.part_size) } } @@ -346,47 +427,30 @@ fn save_segment(data: &mut Vec, segment: &CodeSegment, checksum: u8) -> Resu Ok(update_checksum(segment.data(), checksum)) } -#[cfg(test)] -pub mod tests { - use std::fs; +/// Update the checksum with the given data +fn update_checksum(data: &[u8], mut checksum: u8) -> u8 { + for byte in data { + checksum ^= *byte; + } + checksum +} + +#[cfg(test)] +mod tests { use super::*; - use crate::{elf::ElfFirmwareImage, image_format::FlashFrequency}; - - // Copied from: src/targets/esp32.rs - const PARAMS: Esp32Params = Esp32Params::new( - 0x1000, - 0x1_0000, - 0x3f_0000, - 0, - FlashFrequency::_40Mhz, - include_bytes!("../../resources/bootloaders/esp32-bootloader.bin"), - ); #[test] - fn test_idf_bootloader_format() { - let input_bytes = fs::read("tests/resources/esp32_hal_blinky").unwrap(); - let expected_bin = fs::read("tests/resources/esp32_hal_blinky.bin").unwrap(); - - let image = ElfFirmwareImage::try_from(input_bytes.as_slice()).unwrap(); - let flash_image = IdfBootloaderFormat::new( - &image, - Chip::Esp32, - 0, - PARAMS, - None, - None, - None, - None, - FlashSettings::default(), - ) - .unwrap(); - - let segments = flash_image.flash_segments().collect::>(); - assert_eq!(segments.len(), 3); - - let buf = segments[2].data.as_ref(); - assert_eq!(expected_bin.len(), buf.len()); - assert_eq!(expected_bin.as_slice(), buf); + fn test_flash_config_write() { + let mut header = ImageHeader::default(); + header + .write_flash_config(FlashSize::_4Mb, FlashFrequency::_40Mhz, Chip::Esp32c3) + .unwrap(); + assert_eq!(header.flash_config, 0x20); + + header + .write_flash_config(FlashSize::_32Mb, FlashFrequency::_80Mhz, Chip::Esp32s3) + .unwrap(); + assert_eq!(header.flash_config, 0x5F); } } diff --git a/espflash/src/image_format/mod.rs b/espflash/src/image_format/mod.rs deleted file mode 100644 index d8743081..00000000 --- a/espflash/src/image_format/mod.rs +++ /dev/null @@ -1,149 +0,0 @@ -//! ESP-IDF application binary image format - -use bytemuck::{Pod, Zeroable}; - -pub use self::idf_bootloader::IdfBootloaderFormat; -use crate::{ - elf::RomSegment, - error::Error, - flasher::{FlashFrequency, FlashMode, FlashSize}, - targets::Chip, -}; - -mod idf_bootloader; - -const ESP_CHECKSUM_MAGIC: u8 = 0xef; -const ESP_MAGIC: u8 = 0xE9; -const WP_PIN_DISABLED: u8 = 0xEE; - -/// Firmware header used by the ESP-IDF bootloader. -/// -/// ## Header documentation: -/// * [Header](https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/firmware-image-format.html#file-header) -/// * [Extended header](https://docs.espressif.com/projects/esptool/en/latest/esp32c3/advanced-topics/firmware-image-format.html#extended-file-header) -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -#[repr(C, packed)] -#[doc(alias = "esp_image_header_t")] -struct ImageHeader { - magic: u8, - 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, - - // extended header part - wp_pin: u8, - clk_q_drv: u8, - d_cs_drv: u8, - gd_wp_drv: u8, - chip_id: u16, - min_rev: u8, - /// Minimum chip revision supported by image, in format: major * 100 + minor - min_chip_rev_full: u16, - /// Maximal chip revision supported by image, in format: major * 100 + minor - max_chip_rev_full: u16, - reserved: [u8; 4], - append_digest: u8, -} - -impl Default for ImageHeader { - fn default() -> Self { - Self { - magic: ESP_MAGIC, - segment_count: 3, - flash_mode: FlashMode::default() as _, - flash_config: ((FlashSize::default() as u8) << 4) | FlashFrequency::default() as u8, - entry: 0, - wp_pin: WP_PIN_DISABLED, - clk_q_drv: 0, - d_cs_drv: 0, - gd_wp_drv: 0, - chip_id: Default::default(), - min_rev: 0, - min_chip_rev_full: 0, - max_chip_rev_full: u16::MAX, - reserved: Default::default(), - append_digest: 1, - } - } -} - -impl ImageHeader { - /// Updates flash size and speed filed. - pub fn write_flash_config( - &mut self, - size: FlashSize, - freq: FlashFrequency, - chip: Chip, - ) -> Result<(), Error> { - let flash_size = size.encode_flash_size()?; - let flash_speed = freq.encode_flash_frequency(chip)?; - - // bit field - self.flash_config = (flash_size << 4) | flash_speed; - Ok(()) - } -} - -#[derive(Debug, Clone, Copy, Pod, Zeroable)] -#[repr(C, packed)] -struct SegmentHeader { - addr: u32, - length: u32, -} - -/// Operations for working with firmware image formats -pub trait ImageFormat<'a>: Send { - /// Get the ROM segments needed when flashing to device - fn flash_segments<'b>(&'b self) -> Box> + 'b> - where - 'a: 'b; - - /// Get the ROM segments to save when exporting for OTA - /// - /// Compared to `flash_segments` this excludes things like bootloader and - /// partition table - fn ota_segments<'b>(&'b self) -> Box> + 'b> - where - 'a: 'b; - - /// The size of the application binary - fn app_size(&self) -> u32; - - /// If applicable, the size of the application partition (if it can be - /// determined) - fn part_size(&self) -> Option; -} - -/// Update the checksum with the given data -fn update_checksum(data: &[u8], mut checksum: u8) -> u8 { - for byte in data { - checksum ^= *byte; - } - - checksum -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn test_flash_config_write() { - let mut header = ImageHeader::default(); - header - .write_flash_config(FlashSize::_4Mb, FlashFrequency::_40Mhz, Chip::Esp32c3) - .unwrap(); - assert_eq!(header.flash_config, 0x20); - - header - .write_flash_config(FlashSize::_32Mb, FlashFrequency::_80Mhz, Chip::Esp32s3) - .unwrap(); - assert_eq!(header.flash_config, 0x5F); - } -} diff --git a/espflash/src/targets/esp32.rs b/espflash/src/targets/esp32.rs index b987c0d2..1a05317b 100644 --- a/espflash/src/targets/esp32.rs +++ b/espflash/src/targets/esp32.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -155,7 +155,7 @@ impl Target for Esp32 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { let booloader: &'static [u8] = match xtal_freq { XtalFrequency::_40Mhz => { include_bytes!("../../resources/bootloaders/esp32-bootloader.bin") @@ -180,7 +180,7 @@ impl Target for Esp32 { booloader, ); - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32, flash_data.min_chip_rev, @@ -190,7 +190,7 @@ impl Target for Esp32 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } #[cfg(feature = "serialport")] diff --git a/espflash/src/targets/esp32c2.rs b/espflash/src/targets/esp32c2.rs index 65a5a249..f7d9b1f8 100644 --- a/espflash/src/targets/esp32c2.rs +++ b/espflash/src/targets/esp32c2.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -88,7 +88,7 @@ impl Target for Esp32c2 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { let booloader: &'static [u8] = match xtal_freq { XtalFrequency::_40Mhz => { println!("Using 40MHz bootloader"); @@ -115,7 +115,7 @@ impl Target for Esp32c2 { booloader, ); - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32c2, flash_data.min_chip_rev, @@ -125,7 +125,7 @@ impl Target for Esp32c2 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } #[cfg(feature = "serialport")] diff --git a/espflash/src/targets/esp32c3.rs b/espflash/src/targets/esp32c3.rs index c9917361..31f7cfbf 100644 --- a/espflash/src/targets/esp32c3.rs +++ b/espflash/src/targets/esp32c3.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -82,7 +82,7 @@ impl Target for Esp32c3 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32c3, @@ -90,7 +90,7 @@ impl Target for Esp32c3 { }); } - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32c3, flash_data.min_chip_rev, @@ -100,7 +100,7 @@ impl Target for Esp32c3 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } fn spi_registers(&self) -> SpiRegisters { diff --git a/espflash/src/targets/esp32c6.rs b/espflash/src/targets/esp32c6.rs index 7d7b278d..082bfcb9 100644 --- a/espflash/src/targets/esp32c6.rs +++ b/espflash/src/targets/esp32c6.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -77,7 +77,7 @@ impl Target for Esp32c6 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32c6, @@ -85,7 +85,7 @@ impl Target for Esp32c6 { }); } - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32c6, flash_data.min_chip_rev, @@ -95,7 +95,7 @@ impl Target for Esp32c6 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } fn spi_registers(&self) -> SpiRegisters { diff --git a/espflash/src/targets/esp32h2.rs b/espflash/src/targets/esp32h2.rs index d0471e74..c0a3ec57 100644 --- a/espflash/src/targets/esp32h2.rs +++ b/espflash/src/targets/esp32h2.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -84,7 +84,7 @@ impl Target for Esp32h2 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { if xtal_freq != XtalFrequency::_32Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32h2, @@ -92,7 +92,7 @@ impl Target for Esp32h2 { }); } - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32h2, flash_data.min_chip_rev, @@ -102,7 +102,7 @@ impl Target for Esp32h2 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } fn spi_registers(&self) -> SpiRegisters { diff --git a/espflash/src/targets/esp32p4.rs b/espflash/src/targets/esp32p4.rs index 0768453c..a500028f 100644 --- a/espflash/src/targets/esp32p4.rs +++ b/espflash/src/targets/esp32p4.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -75,7 +75,7 @@ impl Target for Esp32p4 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32p4, @@ -83,7 +83,7 @@ impl Target for Esp32p4 { }); } - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32p4, flash_data.min_chip_rev, @@ -93,7 +93,7 @@ impl Target for Esp32p4 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } fn spi_registers(&self) -> SpiRegisters { diff --git a/espflash/src/targets/esp32s2.rs b/espflash/src/targets/esp32s2.rs index 8d6a4893..ef9c5a4f 100644 --- a/espflash/src/targets/esp32s2.rs +++ b/espflash/src/targets/esp32s2.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency, FLASH_WRITE_SIZE}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -150,7 +150,7 @@ impl Target for Esp32s2 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32s2, @@ -158,7 +158,7 @@ impl Target for Esp32s2 { }); } - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32s2, flash_data.min_chip_rev, @@ -168,7 +168,7 @@ impl Target for Esp32s2 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } #[cfg(feature = "serialport")] diff --git a/espflash/src/targets/esp32s3.rs b/espflash/src/targets/esp32s3.rs index 91531a5e..59798c69 100644 --- a/espflash/src/targets/esp32s3.rs +++ b/espflash/src/targets/esp32s3.rs @@ -6,7 +6,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency}, - image_format::{IdfBootloaderFormat, ImageFormat}, + image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -101,7 +101,7 @@ impl Target for Esp32s3 { flash_data: FlashData, _chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error> { + ) -> Result, Error> { if xtal_freq != XtalFrequency::_40Mhz { return Err(Error::UnsupportedFeature { chip: Chip::Esp32s3, @@ -109,7 +109,7 @@ impl Target for Esp32s3 { }); } - Ok(Box::new(IdfBootloaderFormat::new( + IdfBootloaderFormat::new( image, Chip::Esp32s3, flash_data.min_chip_rev, @@ -119,7 +119,7 @@ impl Target for Esp32s3 { flash_data.target_app_partition, flash_data.bootloader, flash_data.flash_settings, - )?)) + ) } fn spi_registers(&self) -> SpiRegisters { diff --git a/espflash/src/targets/mod.rs b/espflash/src/targets/mod.rs index ad475160..99b5898f 100644 --- a/espflash/src/targets/mod.rs +++ b/espflash/src/targets/mod.rs @@ -29,7 +29,7 @@ use crate::{ elf::FirmwareImage, error::Error, flasher::{FlashData, FlashFrequency, SpiAttachParams, FLASH_WRITE_SIZE}, - image_format::ImageFormat, + image_format::IdfBootloaderFormat, }; /// Max partition size is 16 MB @@ -354,7 +354,7 @@ pub trait Target: ReadEFuse { flash_data: FlashData, chip_revision: Option<(u32, u32)>, xtal_freq: XtalFrequency, - ) -> Result + 'a>, Error>; + ) -> Result, Error>; #[cfg(feature = "serialport")] /// What is the MAC address? From c875a018e5bf5efb66c879286b73eb3bf6da2c08 Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Mon, 5 Feb 2024 09:49:16 -0800 Subject: [PATCH 3/5] Update `README.md` --- espflash/tests/README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/espflash/tests/README.md b/espflash/tests/README.md index 97269c91..aae6340c 100644 --- a/espflash/tests/README.md +++ b/espflash/tests/README.md @@ -2,20 +2,6 @@ This document describes how the test files under `tests/resources` were generated, so that they can be re-generated in the future if needed. -## Direct Boot - -```bash -$ git clone https://github.com/esp-rs/esp-hal -$ cd esp-hal/esp32c3-hal/ -$ cargo build --release --features=direct-boot --example=blinky -``` - -The ELF file is located at `target/riscv32imc-unknown-none-elf/release/examples/blinky` - -```bash -$ espflash save-image --format=direct-boot --chip=esp32c3 esp32c3_hal_blinky_db.bin esp32c3_hal_blinky_db -``` - ## IDF Bootloader ```bash From 0013bd8bc7f1101c142ac6985b36253bb751c232 Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Mon, 5 Feb 2024 09:54:32 -0800 Subject: [PATCH 4/5] Update `CHANGELOG.md` --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d981d6bd..8c01c10d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Removed - Remove support for the ESP8266 (#576) +- Remove the direct boot image format (#577) ## [2.1.0] - 2023-10-03 From cf0cf58a9accac94f2c0ce01b31ab7c688e09923 Mon Sep 17 00:00:00 2001 From: Jesse Braham Date: Tue, 6 Feb 2024 09:58:54 -0800 Subject: [PATCH 5/5] Remove test binaries for direct boot and ESP8266 --- espflash/tests/resources/esp32c3_hal_blinky_db | Bin 85156 -> 0 bytes .../tests/resources/esp32c3_hal_blinky_db.bin | Bin 31808 -> 0 bytes espflash/tests/resources/esp8266_hal_blinky | Bin 18060 -> 0 bytes espflash/tests/resources/esp8266_hal_blinky.bin | Bin 820 -> 0 bytes 4 files changed, 0 insertions(+), 0 deletions(-) delete mode 100755 espflash/tests/resources/esp32c3_hal_blinky_db delete mode 100644 espflash/tests/resources/esp32c3_hal_blinky_db.bin delete mode 100755 espflash/tests/resources/esp8266_hal_blinky delete mode 100644 espflash/tests/resources/esp8266_hal_blinky.bin diff --git a/espflash/tests/resources/esp32c3_hal_blinky_db b/espflash/tests/resources/esp32c3_hal_blinky_db deleted file mode 100755 index da70d19a9a1bdf4dd8fcc89311381729009fb00f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 85156 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 Lo`{50HLd;={$`&} diff --git a/espflash/tests/resources/esp32c3_hal_blinky_db.bin b/espflash/tests/resources/esp32c3_hal_blinky_db.bin deleted file mode 100644 index 63e88c73508190c94a9064ebdb0b7c7a473aeb66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31808 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

_7cV9lqWNu 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 Ga{mD*Y58gZ diff --git a/espflash/tests/resources/esp8266_hal_blinky.bin b/espflash/tests/resources/esp8266_hal_blinky.bin deleted file mode 100644 index 695c9eac540f0a3ddc34b65de8ef83d3c5873e9e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 820 zcmY*VT}TvB6h1TS&d!db+)Y{q6L#Ku*kDNz^`(_UT}Q|QmyCj0d$4>e>B)SUS;Dnc znvtZ|D0fUumx}%%#e!KPTWJI9A(Eh{jS31DDG-8g=dLc@w4qB>N5RoW!a}dYEI{neub5 zS#I~}hXR-*o|Y`@d&-WyD(ddo3d_d?y_@MhLhJ?Wlfu!I-jbpFZBR0Fg@cS!q-E5y zQdf?7S0xOQa>jV{Eyh!nLpkE_WcL4g-prBciQmX4h1MTb%g3}Giz-tz_}G}DjtGSo z{8!leE6%vRO_lo8c5h5mYr6O-(YnR*ylAFLw1e6=ShpbVpehRg=1sEm##I*m0~{9C z1WhbeS{9YrLJM9snP~cDj1>(NoNBBm-fUa!4d|NOWS%DdGk$>^=byU<%3+`# zYL{zA?lf*_Pn@BbT^GpU<^EGTL1t6