diff --git a/CHANGELOG.md b/CHANGELOG.md index eff04734..6f67d082 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,7 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Non-linux-musl: Only list the available USB Ports by default (#590) - `FlashData::new` now returns `crate::Error` (#591) - Moved `reset_after_flash` method to `reset` module (#594) -- Moved `parse_partition_table, DeviceInfo, FlashSettings, FlashData, FlashDataBuilder, FlashFrequency, FlashMode, FlashSize, SpiSetParams and SpiAttachParams` to `flash_data` (#599) +- `flasher::{parse_partition_table, DeviceInfo, FlashSettings, FlashData, FlashDataBuilder, FlashFrequency, FlashMode, FlashSize, SpiSetParams, SpiAttachParams and ProgressCallbacks}` are now available without `serialport`. I think. (#599) - Moved `ProgressCallbacks` to `progress` (#599) ### Removed diff --git a/espflash/src/bin/espflash.rs b/espflash/src/bin/espflash.rs index 5792189a..0bc6f87e 100644 --- a/espflash/src/bin/espflash.rs +++ b/espflash/src/bin/espflash.rs @@ -14,7 +14,7 @@ use espflash::{ EspflashProgress, FlashConfigArgs, MonitorArgs, PartitionTableArgs, ReadFlashArgs, }, error::Error, - flash_data::{parse_partition_table, FlashData, FlashSettings}, + flasher::{parse_partition_table, FlashData, FlashSettings}, logging::initialize_logger, targets::{Chip, XtalFrequency}, update::check_for_update, diff --git a/espflash/src/cli/mod.rs b/espflash/src/cli/mod.rs index 1412ab6d..e4803fcc 100644 --- a/espflash/src/cli/mod.rs +++ b/espflash/src/cli/mod.rs @@ -30,10 +30,8 @@ use crate::{ connection::reset::{ResetAfterOperation, ResetBeforeOperation}, elf::ElfFirmwareImage, error::{Error, MissingPartition, MissingPartitionTable}, - flash_data::{parse_partition_table, FlashData, FlashFrequency, FlashMode, FlashSize}, - flasher::Flasher, - progress::ProgressCallbacks, - targets::{Chip, XtalFrequency}, + flasher::{parse_partition_table, FlashData, FlashFrequency, FlashMode, FlashSize, Flasher}, + targets::{Chip, ProgressCallbacks, XtalFrequency}, }; pub mod config; @@ -453,7 +451,7 @@ pub fn save_elf_as_image( elf_data: &[u8], chip: Chip, image_path: PathBuf, - flash_data: FlashData, + flasher: FlashData, merge: bool, skip_padding: bool, xtal_freq: XtalFrequency, @@ -463,9 +461,9 @@ pub fn save_elf_as_image( if merge { // To get a chip revision, the connection is needed // For simplicity, the revision None is used - let image = - chip.into_target() - .get_flash_image(&image, flash_data.clone(), None, xtal_freq)?; + let image = chip + .into_target() + .get_flash_image(&image, flasher.clone(), None, xtal_freq)?; display_image_size(image.app_size(), image.part_size()); @@ -490,8 +488,7 @@ pub fn save_elf_as_image( // Take flash_size as input parameter, if None, use default value of 4Mb let padding_bytes = vec![ 0xffu8; - flash_data.flash_settings.size.unwrap_or_default().size() - as usize + flasher.flash_settings.size.unwrap_or_default().size() as usize - file.metadata().into_diagnostic()?.len() as usize ]; file.write_all(&padding_bytes).into_diagnostic()?; @@ -499,7 +496,7 @@ pub fn save_elf_as_image( } else { let image = chip .into_target() - .get_flash_image(&image, flash_data, None, xtal_freq)?; + .get_flash_image(&image, flasher, None, xtal_freq)?; display_image_size(image.app_size(), image.part_size()); diff --git a/espflash/src/command.rs b/espflash/src/command.rs index 85687753..4d38f9a9 100644 --- a/espflash/src/command.rs +++ b/espflash/src/command.rs @@ -5,8 +5,7 @@ use std::{io::Write, mem::size_of, time::Duration}; use bytemuck::{bytes_of, Pod, Zeroable}; use strum::Display; -use crate::flash_data::{SpiAttachParams, SpiSetParams, CHECKSUM_INIT}; -use crate::flasher::checksum; +use crate::flasher::{SpiAttachParams, SpiSetParams}; const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); const ERASE_REGION_TIMEOUT_PER_MB: Duration = Duration::from_secs(30); @@ -514,3 +513,13 @@ fn data_command( } Ok(()) } + +const CHECKSUM_INIT: u8 = 0xEF; + +fn checksum(data: &[u8], mut checksum: u8) -> u8 { + for byte in data { + checksum ^= *byte; + } + + checksum +} diff --git a/espflash/src/connection/reset.rs b/espflash/src/connection/reset.rs index b11b088b..d20288f4 100644 --- a/espflash/src/connection/reset.rs +++ b/espflash/src/connection/reset.rs @@ -15,7 +15,7 @@ use crate::{ command::{Command, CommandType}, connection::{Connection, Port, USB_SERIAL_JTAG_PID}, error::Error, - flash_data::FLASH_WRITE_SIZE, + flasher::stubs::FLASH_WRITE_SIZE, }; /// Default time to wait before releasing the boot pin after a reset diff --git a/espflash/src/error.rs b/espflash/src/error.rs index 55febd67..14c9badd 100644 --- a/espflash/src/error.rs +++ b/espflash/src/error.rs @@ -1,5 +1,6 @@ //! Library and application errors +#[cfg(feature = "serialport")] use std::fmt::{Display, Formatter}; use miette::Diagnostic; @@ -12,7 +13,7 @@ use thiserror::Error; use crate::cli::monitor::parser::esp_defmt::DefmtError; #[cfg(feature = "serialport")] use crate::command::CommandType; -use crate::flash_data::{FlashFrequency, FlashSize}; +use crate::flasher::{FlashFrequency, FlashSize}; use crate::targets::Chip; /// All possible errors returned by espflash diff --git a/espflash/src/flash_data.rs b/espflash/src/flash_data.rs deleted file mode 100644 index 3177b924..00000000 --- a/espflash/src/flash_data.rs +++ /dev/null @@ -1,482 +0,0 @@ -use std::{fs, path::Path, str::FromStr}; - -use esp_idf_part::PartitionTable; -use serde::{Deserialize, Serialize}; -use strum::{Display, EnumIter, IntoEnumIterator, VariantNames}; - -use crate::{ - error::Error, - targets::{Chip, XtalFrequency}, -}; - -pub(crate) const CHECKSUM_INIT: u8 = 0xEF; -pub(crate) const FLASH_SECTOR_SIZE: usize = 0x1000; -pub(crate) const FLASH_WRITE_SIZE: usize = 0x400; - -/// Parameters of the attached SPI flash chip (sizes, etc). -/// -/// See https://github.com/espressif/esptool/blob/da31d9d7a1bb496995f8e30a6be259689948e43e/esptool.py#L655 -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub struct SpiSetParams { - /// Flash chip ID - fl_id: u32, - /// Total size in bytes - total_size: u32, - /// Block size - block_size: u32, - /// Sector size - sector_size: u32, - /// Page size - page_size: u32, - /// Status mask - status_mask: u32, -} - -impl SpiSetParams { - pub const fn default(size: u32) -> Self { - SpiSetParams { - fl_id: 0, - total_size: size, - block_size: 64 * 1024, - sector_size: 4 * 1024, - page_size: 256, - status_mask: 0xFFFF, - } - } - - /// Encode the parameters into a byte array - pub fn encode(&self) -> Vec { - let mut encoded: Vec = Vec::new(); - encoded.extend_from_slice(&self.fl_id.to_le_bytes()); - encoded.extend_from_slice(&self.total_size.to_le_bytes()); - encoded.extend_from_slice(&self.block_size.to_le_bytes()); - encoded.extend_from_slice(&self.sector_size.to_le_bytes()); - encoded.extend_from_slice(&self.page_size.to_le_bytes()); - encoded.extend_from_slice(&self.status_mask.to_le_bytes()); - encoded - } -} - -/// Supported flash frequencies -/// -/// Note that not all frequencies are supported by each target device. -#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] -#[derive( - Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Display, VariantNames, Serialize, Deserialize, -)] -#[non_exhaustive] -#[repr(u8)] -pub enum FlashFrequency { - /// 12 MHz - _12Mhz, - /// 15 MHz - _15Mhz, - /// 16 MHz - _16Mhz, - /// 20 MHz - _20Mhz, - /// 24 MHz - _24Mhz, - /// 26 MHz - _26Mhz, - /// 30 MHz - _30Mhz, - /// 40 MHz - #[default] - _40Mhz, - /// 48 MHz - _48Mhz, - /// 60 MHz - _60Mhz, - /// 80 MHz - _80Mhz, -} - -impl FlashFrequency { - /// Encodes flash frequency into the format used by the bootloader. - pub fn encode_flash_frequency(self: FlashFrequency, chip: Chip) -> Result { - let encodings = chip.into_target().flash_frequency_encodings(); - if let Some(&f) = encodings.get(&self) { - Ok(f) - } else { - Err(Error::UnsupportedFlashFrequency { - chip, - frequency: self, - }) - } - } -} - -/// Supported flash modes -#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] -#[derive(Copy, Clone, Debug, Default, VariantNames, Serialize, Deserialize)] -#[non_exhaustive] -#[strum(serialize_all = "lowercase")] -pub enum FlashMode { - /// Quad I/O (4 pins used for address & data) - Qio, - /// Quad Output (4 pins used for data) - Qout, - /// Dual I/O (2 pins used for address & data) - #[default] - Dio, - /// Dual Output (2 pins used for data) - Dout, -} - -/// Supported flash sizes -/// -/// Note that not all sizes are supported by each target device. -#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] -#[derive( - Clone, - Copy, - Debug, - Default, - Eq, - PartialEq, - Display, - VariantNames, - EnumIter, - Serialize, - Deserialize, -)] -#[non_exhaustive] -#[repr(u8)] -#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] -#[doc(alias("esp_image_flash_size_t"))] -pub enum FlashSize { - /// 256 KB - _256Kb, - /// 512 KB - _512Kb, - /// 1 MB - _1Mb, - /// 2 MB - _2Mb, - /// 4 MB - #[default] - _4Mb, - /// 8 MB - _8Mb, - /// 16 MB - _16Mb, - /// 32 MB - _32Mb, - /// 64 MB - _64Mb, - /// 128 MB - _128Mb, - /// 256 MB - _256Mb, -} - -impl FlashSize { - /// Encodes flash size into the format used by the bootloader. - /// - /// ## Values: - /// - /// * https://docs.espressif.com/projects/esptool/en/latest/esp32s3/advanced-topics/firmware-image-format.html#file-header - pub const fn encode_flash_size(self: FlashSize) -> Result { - use FlashSize::*; - - let encoded = match self { - _1Mb => 0, - _2Mb => 1, - _4Mb => 2, - _8Mb => 3, - _16Mb => 4, - _32Mb => 5, - _64Mb => 6, - _128Mb => 7, - _256Mb => 8, - _ => return Err(Error::UnsupportedFlash(self as u8)), - }; - - Ok(encoded) - } - - /// Create a [FlashSize] from an [u8] - /// - /// [source](https://github.com/espressif/esptool/blob/f4d2510e2c897621884f433ef3f191e8fc5ff184/esptool/cmds.py#L42) - pub const fn from_detected(value: u8) -> Result { - match value { - 0x12 | 0x32 => Ok(FlashSize::_256Kb), - 0x13 | 0x33 => Ok(FlashSize::_512Kb), - 0x14 | 0x34 => Ok(FlashSize::_1Mb), - 0x15 | 0x35 => Ok(FlashSize::_2Mb), - 0x16 | 0x36 => Ok(FlashSize::_4Mb), - 0x17 | 0x37 => Ok(FlashSize::_8Mb), - 0x18 | 0x38 => Ok(FlashSize::_16Mb), - 0x19 | 0x39 => Ok(FlashSize::_32Mb), - 0x20 | 0x1A | 0x3A => Ok(FlashSize::_64Mb), - 0x21 | 0x1B => Ok(FlashSize::_128Mb), - 0x22 | 0x1C => Ok(FlashSize::_256Mb), - _ => Err(Error::UnsupportedFlash(value)), - } - } - - /// Returns the flash size in bytes - pub const fn size(self) -> u32 { - match self { - FlashSize::_256Kb => 0x0040000, - FlashSize::_512Kb => 0x0080000, - FlashSize::_1Mb => 0x0100000, - FlashSize::_2Mb => 0x0200000, - FlashSize::_4Mb => 0x0400000, - FlashSize::_8Mb => 0x0800000, - FlashSize::_16Mb => 0x1000000, - FlashSize::_32Mb => 0x2000000, - FlashSize::_64Mb => 0x4000000, - FlashSize::_128Mb => 0x8000000, - FlashSize::_256Mb => 0x10000000, - } - } -} - -impl FromStr for FlashSize { - type Err = Error; - /// Create a [FlashSize] from a string - fn from_str(s: &str) -> Result { - let upper = s.to_uppercase(); - FlashSize::VARIANTS - .iter() - .copied() - .zip(FlashSize::iter()) - .find(|(name, _)| *name == upper) - .map(|(_, variant)| variant) - .ok_or_else(|| Error::InvalidFlashSize(s.to_string())) - } -} - -/// Information about the connected device -#[derive(Debug, Clone)] -pub struct DeviceInfo { - /// The chip being used - pub chip: Chip, - /// The revision of the chip - pub revision: Option<(u32, u32)>, - /// The crystal frequency of the chip - pub crystal_frequency: XtalFrequency, - /// The total available flash size - pub flash_size: FlashSize, - /// Device features - pub features: Vec, - /// MAC address - pub mac_address: String, -} - -/// Flash settings to use when flashing a device -#[derive(Copy, Clone, Debug)] -#[non_exhaustive] -pub struct FlashSettings { - pub mode: Option, - pub size: Option, - pub freq: Option, -} - -impl FlashSettings { - pub const fn default() -> Self { - FlashSettings { - mode: None, - size: None, - freq: None, - } - } - - pub fn new( - mode: Option, - size: Option, - freq: Option, - ) -> Self { - FlashSettings { mode, size, freq } - } -} - -/// Builder interface to create [`FlashData`] objects. -pub struct FlashDataBuilder<'a> { - bootloader_path: Option<&'a Path>, - partition_table_path: Option<&'a Path>, - partition_table_offset: Option, - target_app_partition: Option, - flash_settings: FlashSettings, - min_chip_rev: u16, -} - -impl<'a> Default for FlashDataBuilder<'a> { - fn default() -> Self { - Self { - bootloader_path: Default::default(), - partition_table_path: Default::default(), - partition_table_offset: Default::default(), - target_app_partition: Default::default(), - flash_settings: FlashSettings::default(), - min_chip_rev: Default::default(), - } - } -} - -impl<'a> FlashDataBuilder<'a> { - /// Creates a new [`FlashDataBuilder`] object. - pub fn new() -> Self { - Self::default() - } - - /// Sets the bootloader path. - pub fn with_bootloader(mut self, bootloader_path: &'a Path) -> Self { - self.bootloader_path = Some(bootloader_path); - self - } - - /// Sets the partition table path. - pub fn with_partition_table(mut self, partition_table_path: &'a Path) -> Self { - self.partition_table_path = Some(partition_table_path); - self - } - - /// Sets the partition table offset. - pub fn with_partition_table_offset(mut self, partition_table_offset: u32) -> Self { - self.partition_table_offset = Some(partition_table_offset); - 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); - self - } - - /// Sets the flash settings. - pub fn with_flash_settings(mut self, flash_settings: FlashSettings) -> Self { - self.flash_settings = flash_settings; - self - } - - /// Sets the minimum chip revision. - pub fn with_min_chip_rev(mut self, min_chip_rev: u16) -> Self { - self.min_chip_rev = min_chip_rev; - self - } - - /// Builds a [`FlashData`] object. - pub fn build(self) -> Result { - FlashData::new( - self.bootloader_path, - self.partition_table_path, - self.partition_table_offset, - self.target_app_partition, - self.flash_settings, - self.min_chip_rev, - ) - } -} - -/// Flash data and configuration -#[derive(Debug, Clone)] -#[non_exhaustive] -pub struct FlashData { - pub bootloader: Option>, - pub partition_table: Option, - pub partition_table_offset: Option, - pub target_app_partition: Option, - pub flash_settings: FlashSettings, - pub min_chip_rev: u16, -} - -impl FlashData { - pub fn new( - bootloader: Option<&Path>, - partition_table: Option<&Path>, - partition_table_offset: Option, - target_app_partition: Option, - flash_settings: FlashSettings, - min_chip_rev: u16, - ) -> Result { - // If the '--bootloader' option is provided, load the binary file at the - // specified path. - let bootloader = if let Some(path) = bootloader { - let data = fs::canonicalize(path) - .and_then(fs::read) - .map_err(|e| Error::FileOpenError(path.display().to_string(), e))?; - - Some(data) - } else { - None - }; - - // If the '-T' option is provided, load the partition table from - // the CSV or binary file at the specified path. - let partition_table = match partition_table { - Some(path) => Some(parse_partition_table(path)?), - None => None, - }; - - Ok(FlashData { - bootloader, - partition_table, - partition_table_offset, - target_app_partition, - flash_settings, - min_chip_rev, - }) - } -} - -/// Parameters for attaching to a target devices SPI flash -#[derive(Copy, Clone, Debug)] -#[repr(C)] -pub struct SpiAttachParams { - clk: u8, - q: u8, - d: u8, - hd: u8, - cs: u8, -} - -impl SpiAttachParams { - pub const fn default() -> Self { - SpiAttachParams { - clk: 0, - q: 0, - d: 0, - hd: 0, - cs: 0, - } - } - - // Default SPI parameters for ESP32-PICO-D4 - pub const fn esp32_pico_d4() -> Self { - SpiAttachParams { - clk: 6, - q: 17, - d: 8, - hd: 11, - cs: 16, - } - } - - /// Encode the parameters into a byte array - pub fn encode(self, stub: bool) -> Vec { - let packed = ((self.hd as u32) << 24) - | ((self.cs as u32) << 18) - | ((self.d as u32) << 12) - | ((self.q as u32) << 6) - | (self.clk as u32); - - let mut encoded: Vec = packed.to_le_bytes().to_vec(); - - if !stub { - encoded.append(&mut vec![0u8; 4]); - } - - encoded - } -} - -/// Parse a [PartitionTable] from the provided path -pub fn parse_partition_table(path: &Path) -> Result { - let data = fs::read(path).map_err(|e| Error::FileOpenError(path.display().to_string(), e))?; - - Ok(PartitionTable::try_from(data)?) -} diff --git a/espflash/src/flasher/mod.rs b/espflash/src/flasher/mod.rs index fe5797fc..8e54230a 100644 --- a/espflash/src/flasher/mod.rs +++ b/espflash/src/flasher/mod.rs @@ -4,36 +4,516 @@ //! application to a target device. It additionally provides some operations to //! read information from the target device. -use std::{borrow::Cow, fs, io::Write, path::PathBuf, thread::sleep, time::Duration}; +use std::{fs, path::Path, str::FromStr}; +#[cfg(feature = "serialport")] +use std::{borrow::Cow, io::Write, path::PathBuf, thread::sleep, time::Duration}; + +use esp_idf_part::PartitionTable; +use serde::{Deserialize, Serialize}; +use strum::{Display, EnumIter, IntoEnumIterator, VariantNames}; + +#[cfg(feature = "serialport")] use log::{debug, info, warn}; +#[cfg(feature = "serialport")] use md5::{Digest, Md5}; #[cfg(feature = "serialport")] use serialport::UsbPortInfo; -use self::stubs::FlashStub; -#[cfg(feature = "serialport")] -use crate::connection::{ - reset::{ResetAfterOperation, ResetBeforeOperation}, - Connection, Port, +use crate::{ + error::Error, + targets::{Chip, XtalFrequency}, }; + +#[cfg(feature = "serialport")] use crate::{ command::{Command, CommandType}, + connection::{ + reset::{ResetAfterOperation, ResetBeforeOperation}, + Connection, Port, + }, elf::{ElfFirmwareImage, FirmwareImage, RomSegment}, - error::{ConnectionError, Error, ResultExt}, - flash_data::{DeviceInfo, FlashData, FlashSize, SpiAttachParams, SpiSetParams}, - progress::ProgressCallbacks, - targets::{Chip, XtalFrequency}, + error::{ConnectionError, ResultExt}, + flasher::stubs::{ + FlashStub, CHIP_DETECT_MAGIC_REG_ADDR, DEFAULT_TIMEOUT, EXPECTED_STUB_HANDSHAKE, + }, + targets::ProgressCallbacks, }; -mod stubs; +#[cfg(feature = "serialport")] +pub(crate) mod stubs; + +/// Parameters of the attached SPI flash chip (sizes, etc). +/// +/// See https://github.com/espressif/esptool/blob/da31d9d7a1bb496995f8e30a6be259689948e43e/esptool.py#L655 +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct SpiSetParams { + /// Flash chip ID + fl_id: u32, + /// Total size in bytes + total_size: u32, + /// Block size + block_size: u32, + /// Sector size + sector_size: u32, + /// Page size + page_size: u32, + /// Status mask + status_mask: u32, +} + +impl SpiSetParams { + pub const fn default(size: u32) -> Self { + SpiSetParams { + fl_id: 0, + total_size: size, + block_size: 64 * 1024, + sector_size: 4 * 1024, + page_size: 256, + status_mask: 0xFFFF, + } + } + + /// Encode the parameters into a byte array + pub fn encode(&self) -> Vec { + let mut encoded: Vec = Vec::new(); + encoded.extend_from_slice(&self.fl_id.to_le_bytes()); + encoded.extend_from_slice(&self.total_size.to_le_bytes()); + encoded.extend_from_slice(&self.block_size.to_le_bytes()); + encoded.extend_from_slice(&self.sector_size.to_le_bytes()); + encoded.extend_from_slice(&self.page_size.to_le_bytes()); + encoded.extend_from_slice(&self.status_mask.to_le_bytes()); + encoded + } +} + +/// Supported flash frequencies +/// +/// Note that not all frequencies are supported by each target device. +#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] +#[derive( + Debug, Default, Clone, Copy, Hash, PartialEq, Eq, Display, VariantNames, Serialize, Deserialize, +)] +#[non_exhaustive] +#[repr(u8)] +pub enum FlashFrequency { + /// 12 MHz + _12Mhz, + /// 15 MHz + _15Mhz, + /// 16 MHz + _16Mhz, + /// 20 MHz + _20Mhz, + /// 24 MHz + _24Mhz, + /// 26 MHz + _26Mhz, + /// 30 MHz + _30Mhz, + /// 40 MHz + #[default] + _40Mhz, + /// 48 MHz + _48Mhz, + /// 60 MHz + _60Mhz, + /// 80 MHz + _80Mhz, +} + +impl FlashFrequency { + /// Encodes flash frequency into the format used by the bootloader. + pub fn encode_flash_frequency(self: FlashFrequency, chip: Chip) -> Result { + let encodings = chip.into_target().flash_frequency_encodings(); + if let Some(&f) = encodings.get(&self) { + Ok(f) + } else { + Err(Error::UnsupportedFlashFrequency { + chip, + frequency: self, + }) + } + } +} + +/// Supported flash modes +#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] +#[derive(Copy, Clone, Debug, Default, VariantNames, Serialize, Deserialize)] +#[non_exhaustive] +#[strum(serialize_all = "lowercase")] +pub enum FlashMode { + /// Quad I/O (4 pins used for address & data) + Qio, + /// Quad Output (4 pins used for data) + Qout, + /// Dual I/O (2 pins used for address & data) + #[default] + Dio, + /// Dual Output (2 pins used for data) + Dout, +} + +/// Supported flash sizes +/// +/// Note that not all sizes are supported by each target device. +#[cfg_attr(feature = "cli", derive(clap::ValueEnum))] +#[derive( + Clone, + Copy, + Debug, + Default, + Eq, + PartialEq, + Display, + VariantNames, + EnumIter, + Serialize, + Deserialize, +)] +#[non_exhaustive] +#[repr(u8)] +#[strum(serialize_all = "SCREAMING_SNAKE_CASE")] +#[doc(alias("esp_image_flash_size_t"))] +pub enum FlashSize { + /// 256 KB + _256Kb, + /// 512 KB + _512Kb, + /// 1 MB + _1Mb, + /// 2 MB + _2Mb, + /// 4 MB + #[default] + _4Mb, + /// 8 MB + _8Mb, + /// 16 MB + _16Mb, + /// 32 MB + _32Mb, + /// 64 MB + _64Mb, + /// 128 MB + _128Mb, + /// 256 MB + _256Mb, +} + +impl FlashSize { + /// Encodes flash size into the format used by the bootloader. + /// + /// ## Values: + /// + /// * https://docs.espressif.com/projects/esptool/en/latest/esp32s3/advanced-topics/firmware-image-format.html#file-header + pub const fn encode_flash_size(self: FlashSize) -> Result { + use FlashSize::*; + + let encoded = match self { + _1Mb => 0, + _2Mb => 1, + _4Mb => 2, + _8Mb => 3, + _16Mb => 4, + _32Mb => 5, + _64Mb => 6, + _128Mb => 7, + _256Mb => 8, + _ => return Err(Error::UnsupportedFlash(self as u8)), + }; + + Ok(encoded) + } -const CHIP_DETECT_MAGIC_REG_ADDR: u32 = 0x40001000; -const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); -const EXPECTED_STUB_HANDSHAKE: &str = "OHAI"; + /// Create a [FlashSize] from an [u8] + /// + /// [source](https://github.com/espressif/esptool/blob/f4d2510e2c897621884f433ef3f191e8fc5ff184/esptool/cmds.py#L42) + pub const fn from_detected(value: u8) -> Result { + match value { + 0x12 | 0x32 => Ok(FlashSize::_256Kb), + 0x13 | 0x33 => Ok(FlashSize::_512Kb), + 0x14 | 0x34 => Ok(FlashSize::_1Mb), + 0x15 | 0x35 => Ok(FlashSize::_2Mb), + 0x16 | 0x36 => Ok(FlashSize::_4Mb), + 0x17 | 0x37 => Ok(FlashSize::_8Mb), + 0x18 | 0x38 => Ok(FlashSize::_16Mb), + 0x19 | 0x39 => Ok(FlashSize::_32Mb), + 0x20 | 0x1A | 0x3A => Ok(FlashSize::_64Mb), + 0x21 | 0x1B => Ok(FlashSize::_128Mb), + 0x22 | 0x1C => Ok(FlashSize::_256Mb), + _ => Err(Error::UnsupportedFlash(value)), + } + } + + /// Returns the flash size in bytes + pub const fn size(self) -> u32 { + match self { + FlashSize::_256Kb => 0x0040000, + FlashSize::_512Kb => 0x0080000, + FlashSize::_1Mb => 0x0100000, + FlashSize::_2Mb => 0x0200000, + FlashSize::_4Mb => 0x0400000, + FlashSize::_8Mb => 0x0800000, + FlashSize::_16Mb => 0x1000000, + FlashSize::_32Mb => 0x2000000, + FlashSize::_64Mb => 0x4000000, + FlashSize::_128Mb => 0x8000000, + FlashSize::_256Mb => 0x10000000, + } + } +} + +impl FromStr for FlashSize { + type Err = Error; + /// Create a [FlashSize] from a string + fn from_str(s: &str) -> Result { + let upper = s.to_uppercase(); + FlashSize::VARIANTS + .iter() + .copied() + .zip(FlashSize::iter()) + .find(|(name, _)| *name == upper) + .map(|(_, variant)| variant) + .ok_or_else(|| Error::InvalidFlashSize(s.to_string())) + } +} + +/// Information about the connected device +#[derive(Debug, Clone)] +pub struct DeviceInfo { + /// The chip being used + pub chip: Chip, + /// The revision of the chip + pub revision: Option<(u32, u32)>, + /// The crystal frequency of the chip + pub crystal_frequency: XtalFrequency, + /// The total available flash size + pub flash_size: FlashSize, + /// Device features + pub features: Vec, + /// MAC address + pub mac_address: String, +} + +/// Flash settings to use when flashing a device +#[derive(Copy, Clone, Debug)] +#[non_exhaustive] +pub struct FlashSettings { + pub mode: Option, + pub size: Option, + pub freq: Option, +} + +impl FlashSettings { + pub const fn default() -> Self { + FlashSettings { + mode: None, + size: None, + freq: None, + } + } + + pub fn new( + mode: Option, + size: Option, + freq: Option, + ) -> Self { + FlashSettings { mode, size, freq } + } +} +/// Builder interface to create [`FlashData`] objects. +pub struct FlashDataBuilder<'a> { + bootloader_path: Option<&'a Path>, + partition_table_path: Option<&'a Path>, + partition_table_offset: Option, + target_app_partition: Option, + flash_settings: FlashSettings, + min_chip_rev: u16, +} + +impl<'a> Default for FlashDataBuilder<'a> { + fn default() -> Self { + Self { + bootloader_path: Default::default(), + partition_table_path: Default::default(), + partition_table_offset: Default::default(), + target_app_partition: Default::default(), + flash_settings: FlashSettings::default(), + min_chip_rev: Default::default(), + } + } +} + +impl<'a> FlashDataBuilder<'a> { + /// Creates a new [`FlashDataBuilder`] object. + pub fn new() -> Self { + Self::default() + } + + /// Sets the bootloader path. + pub fn with_bootloader(mut self, bootloader_path: &'a Path) -> Self { + self.bootloader_path = Some(bootloader_path); + self + } + + /// Sets the partition table path. + pub fn with_partition_table(mut self, partition_table_path: &'a Path) -> Self { + self.partition_table_path = Some(partition_table_path); + self + } + + /// Sets the partition table offset. + pub fn with_partition_table_offset(mut self, partition_table_offset: u32) -> Self { + self.partition_table_offset = Some(partition_table_offset); + 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); + self + } + + /// Sets the flash settings. + pub fn with_flash_settings(mut self, flash_settings: FlashSettings) -> Self { + self.flash_settings = flash_settings; + self + } + + /// Sets the minimum chip revision. + pub fn with_min_chip_rev(mut self, min_chip_rev: u16) -> Self { + self.min_chip_rev = min_chip_rev; + self + } + + /// Builds a [`FlashData`] object. + pub fn build(self) -> Result { + FlashData::new( + self.bootloader_path, + self.partition_table_path, + self.partition_table_offset, + self.target_app_partition, + self.flash_settings, + self.min_chip_rev, + ) + } +} + +/// Flash data and configuration +#[derive(Debug, Clone)] +#[non_exhaustive] +pub struct FlashData { + pub bootloader: Option>, + pub partition_table: Option, + pub partition_table_offset: Option, + pub target_app_partition: Option, + pub flash_settings: FlashSettings, + pub min_chip_rev: u16, +} + +impl FlashData { + pub fn new( + bootloader: Option<&Path>, + partition_table: Option<&Path>, + partition_table_offset: Option, + target_app_partition: Option, + flash_settings: FlashSettings, + min_chip_rev: u16, + ) -> Result { + // If the '--bootloader' option is provided, load the binary file at the + // specified path. + let bootloader = if let Some(path) = bootloader { + let data = fs::canonicalize(path) + .and_then(fs::read) + .map_err(|e| Error::FileOpenError(path.display().to_string(), e))?; + + Some(data) + } else { + None + }; + + // If the '-T' option is provided, load the partition table from + // the CSV or binary file at the specified path. + let partition_table = match partition_table { + Some(path) => Some(parse_partition_table(path)?), + None => None, + }; + + Ok(FlashData { + bootloader, + partition_table, + partition_table_offset, + target_app_partition, + flash_settings, + min_chip_rev, + }) + } +} + +/// Parameters for attaching to a target devices SPI flash +#[derive(Copy, Clone, Debug)] +#[repr(C)] +pub struct SpiAttachParams { + clk: u8, + q: u8, + d: u8, + hd: u8, + cs: u8, +} + +impl SpiAttachParams { + pub const fn default() -> Self { + SpiAttachParams { + clk: 0, + q: 0, + d: 0, + hd: 0, + cs: 0, + } + } + + // Default SPI parameters for ESP32-PICO-D4 + pub const fn esp32_pico_d4() -> Self { + SpiAttachParams { + clk: 6, + q: 17, + d: 8, + hd: 11, + cs: 16, + } + } + + /// Encode the parameters into a byte array + pub fn encode(self, stub: bool) -> Vec { + let packed = ((self.hd as u32) << 24) + | ((self.cs as u32) << 18) + | ((self.d as u32) << 12) + | ((self.q as u32) << 6) + | (self.clk as u32); + + let mut encoded: Vec = packed.to_le_bytes().to_vec(); + + if !stub { + encoded.append(&mut vec![0u8; 4]); + } + + encoded + } +} + +/// Parse a [PartitionTable] from the provided path +pub fn parse_partition_table(path: &Path) -> Result { + let data = fs::read(path).map_err(|e| Error::FileOpenError(path.display().to_string(), e))?; + + Ok(PartitionTable::try_from(data)?) +} + +#[cfg(feature = "serialport")] /// List of SPI parameters to try while detecting flash size -const TRY_SPI_PARAMS: [SpiAttachParams; 2] = +pub(crate) const TRY_SPI_PARAMS: [SpiAttachParams; 2] = [SpiAttachParams::default(), SpiAttachParams::esp32_pico_d4()]; #[cfg(feature = "serialport")] @@ -690,11 +1170,3 @@ impl Flasher { Ok(()) } } - -pub(crate) fn checksum(data: &[u8], mut checksum: u8) -> u8 { - for byte in data { - checksum ^= *byte; - } - - checksum -} diff --git a/espflash/src/flasher/stubs.rs b/espflash/src/flasher/stubs.rs index bee3c3c0..035b5489 100644 --- a/espflash/src/flasher/stubs.rs +++ b/espflash/src/flasher/stubs.rs @@ -1,3 +1,5 @@ +use std::time::Duration; + use base64::{engine::general_purpose, Engine as _}; use serde::{Deserialize, Serialize}; @@ -18,6 +20,13 @@ pub struct FlashStub { data_start: u32, } +pub(crate) const CHIP_DETECT_MAGIC_REG_ADDR: u32 = 0x40001000; +pub(crate) const DEFAULT_TIMEOUT: Duration = Duration::from_secs(3); +pub(crate) const EXPECTED_STUB_HANDSHAKE: &str = "OHAI"; + +pub(crate) const FLASH_SECTOR_SIZE: usize = 0x1000; +pub(crate) const FLASH_WRITE_SIZE: usize = 0x400; + // Include stub objects in binary const STUB_32: &str = include_str!("../../resources/stubs/stub_flasher_32.toml"); const STUB_32C2: &str = include_str!("../../resources/stubs/stub_flasher_32c2.toml"); diff --git a/espflash/src/image_format.rs b/espflash/src/image_format.rs index a11234bf..d1d722cb 100644 --- a/espflash/src/image_format.rs +++ b/espflash/src/image_format.rs @@ -9,7 +9,7 @@ use sha2::{Digest, Sha256}; use crate::{ elf::{CodeSegment, FirmwareImage, RomSegment}, error::Error, - flash_data::{FlashFrequency, FlashMode, FlashSettings, FlashSize}, + flasher::{FlashFrequency, FlashMode, FlashSettings, FlashSize}, targets::{Chip, Esp32Params}, }; diff --git a/espflash/src/lib.rs b/espflash/src/lib.rs index 309318b6..194a37ef 100644 --- a/espflash/src/lib.rs +++ b/espflash/src/lib.rs @@ -38,8 +38,6 @@ pub mod command; pub mod connection; pub mod elf; pub mod error; -pub mod flash_data; -#[cfg(feature = "serialport")] pub mod flasher; pub mod image_format; pub mod progress; diff --git a/espflash/src/progress.rs b/espflash/src/progress.rs index ba38791d..8b137891 100644 --- a/espflash/src/progress.rs +++ b/espflash/src/progress.rs @@ -1,9 +1 @@ -/// Progress update callbacks -pub trait ProgressCallbacks { - /// Initialize some progress report - fn init(&mut self, addr: u32, total: usize); - /// Update some progress report - fn update(&mut self, current: usize); - /// Finish some progress report - fn finish(&mut self); -} + diff --git a/espflash/src/targets/esp32.rs b/espflash/src/targets/esp32.rs index 8ef05422..7f5aeec0 100644 --- a/espflash/src/targets/esp32.rs +++ b/espflash/src/targets/esp32.rs @@ -5,7 +5,7 @@ use crate::{connection::Connection, targets::bytes_to_mac_addr}; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -18,10 +18,12 @@ const FLASH_RANGES: &[Range] = &[ ]; // UART0_BASE_REG + 0x14 -const UART_CLKDIV_REG: u32 = 0x3ff4_0014; -const UART_CLKDIV_MASK: u32 = 0xfffff; - -const XTAL_CLK_DIVIDER: u32 = 1; +#[cfg(feature = "serialport")] +pub const UART_CLKDIV_REG: u32 = 0x3ff4_0014; +#[cfg(feature = "serialport")] +pub const UART_CLKDIV_MASK: u32 = 0xfffff; +#[cfg(feature = "serialport")] +pub const XTAL_CLK_DIVIDER: u32 = 1; /// ESP32 Target pub struct Esp32; diff --git a/espflash/src/targets/esp32c2.rs b/espflash/src/targets/esp32c2.rs index 823bcfa0..45b42a76 100644 --- a/espflash/src/targets/esp32c2.rs +++ b/espflash/src/targets/esp32c2.rs @@ -5,7 +5,7 @@ use crate::{connection::Connection, targets::bytes_to_mac_addr}; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -21,9 +21,12 @@ const FLASH_RANGES: &[Range] = &[ ]; // UART0_BASE_REG + 0x14 +#[cfg(feature = "serialport")] const UART_CLKDIV_REG: u32 = 0x6000_0014; +#[cfg(feature = "serialport")] const UART_CLKDIV_MASK: u32 = 0xfffff; +#[cfg(feature = "serialport")] const XTAL_CLK_DIVIDER: u32 = 1; /// ESP32-C2 Target diff --git a/espflash/src/targets/esp32c3.rs b/espflash/src/targets/esp32c3.rs index eece7d0d..31f7cfbf 100644 --- a/espflash/src/targets/esp32c3.rs +++ b/espflash/src/targets/esp32c3.rs @@ -5,7 +5,7 @@ use crate::connection::Connection; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; diff --git a/espflash/src/targets/esp32c6.rs b/espflash/src/targets/esp32c6.rs index c8a260d1..082bfcb9 100644 --- a/espflash/src/targets/esp32c6.rs +++ b/espflash/src/targets/esp32c6.rs @@ -5,7 +5,7 @@ use crate::connection::Connection; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; diff --git a/espflash/src/targets/esp32h2.rs b/espflash/src/targets/esp32h2.rs index 968e1131..c0a3ec57 100644 --- a/espflash/src/targets/esp32h2.rs +++ b/espflash/src/targets/esp32h2.rs @@ -5,7 +5,7 @@ use crate::connection::Connection; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; diff --git a/espflash/src/targets/esp32p4.rs b/espflash/src/targets/esp32p4.rs index 7e4c1f76..a500028f 100644 --- a/espflash/src/targets/esp32p4.rs +++ b/espflash/src/targets/esp32p4.rs @@ -5,7 +5,7 @@ use crate::connection::Connection; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; diff --git a/espflash/src/targets/esp32s2.rs b/espflash/src/targets/esp32s2.rs index dd0f92b9..8ead773f 100644 --- a/espflash/src/targets/esp32s2.rs +++ b/espflash/src/targets/esp32s2.rs @@ -1,11 +1,13 @@ use std::ops::Range; #[cfg(feature = "serialport")] -use crate::{connection::Connection, flash_data::FLASH_WRITE_SIZE, targets::MAX_RAM_BLOCK_SIZE}; +use crate::{ + connection::Connection, flasher::stubs::FLASH_WRITE_SIZE, targets::MAX_RAM_BLOCK_SIZE, +}; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; @@ -17,6 +19,7 @@ const FLASH_RANGES: &[Range] = &[ 0x3f00_0000..0x3f3f_0000, // DROM ]; +#[cfg(feature = "serialport")] const MAX_USB_BLOCK_SIZE: usize = 0x800; const PARAMS: Esp32Params = Esp32Params::new( diff --git a/espflash/src/targets/esp32s3.rs b/espflash/src/targets/esp32s3.rs index 97d12e61..59798c69 100644 --- a/espflash/src/targets/esp32s3.rs +++ b/espflash/src/targets/esp32s3.rs @@ -5,7 +5,7 @@ use crate::connection::Connection; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, targets::{Chip, Esp32Params, ReadEFuse, SpiRegisters, Target, XtalFrequency}, }; diff --git a/espflash/src/targets/flash_target/esp32.rs b/espflash/src/targets/flash_target/esp32.rs index 699769d1..1d25ce96 100644 --- a/espflash/src/targets/flash_target/esp32.rs +++ b/espflash/src/targets/flash_target/esp32.rs @@ -11,13 +11,12 @@ use md5::{Digest, Md5}; use crate::{ command::{Command, CommandType}, connection::{Connection, USB_SERIAL_JTAG_PID}, - progress::ProgressCallbacks, - targets::FlashTarget, + targets::{FlashTarget, ProgressCallbacks}, }; use crate::{ elf::RomSegment, error::Error, - flash_data::{SpiAttachParams, FLASH_SECTOR_SIZE}, + flasher::{stubs::FLASH_SECTOR_SIZE, SpiAttachParams}, targets::Chip, }; diff --git a/espflash/src/targets/flash_target/mod.rs b/espflash/src/targets/flash_target/mod.rs index 5f5479f0..7b434931 100644 --- a/espflash/src/targets/flash_target/mod.rs +++ b/espflash/src/targets/flash_target/mod.rs @@ -1,12 +1,20 @@ pub(crate) use self::ram::MAX_RAM_BLOCK_SIZE; pub use self::{esp32::Esp32Target, ram::RamTarget}; -#[cfg(feature = "serialport")] -use crate::{connection::Connection, elf::RomSegment, error::Error, progress::ProgressCallbacks}; +use crate::{connection::Connection, elf::RomSegment, error::Error}; mod esp32; mod ram; -#[cfg(feature = "serialport")] +/// Progress update callbacks +pub trait ProgressCallbacks { + /// Initialize some progress report + fn init(&mut self, addr: u32, total: usize); + /// Update some progress report + fn update(&mut self, current: usize); + /// Finish some progress report + fn finish(&mut self); +} + /// Operations for interacting with a flash target pub trait FlashTarget { /// Begin the flashing operation diff --git a/espflash/src/targets/flash_target/ram.rs b/espflash/src/targets/flash_target/ram.rs index 4d990ef7..74231f31 100644 --- a/espflash/src/targets/flash_target/ram.rs +++ b/espflash/src/targets/flash_target/ram.rs @@ -2,8 +2,7 @@ use crate::{ command::{Command, CommandType}, connection::Connection, - progress::ProgressCallbacks, - targets::FlashTarget, + targets::{FlashTarget, ProgressCallbacks}, }; use crate::{elf::RomSegment, error::Error}; diff --git a/espflash/src/targets/mod.rs b/espflash/src/targets/mod.rs index 0468f865..0fba62b4 100644 --- a/espflash/src/targets/mod.rs +++ b/espflash/src/targets/mod.rs @@ -10,28 +10,25 @@ use esp_idf_part::{AppType, DataType, Partition, PartitionTable, SubType, Type}; use serde::{Deserialize, Serialize}; use strum::{Display, EnumIter, EnumString, VariantNames}; -#[cfg(feature = "serialport")] -use self::flash_target::{FlashTarget, MAX_RAM_BLOCK_SIZE}; -pub use self::{ - esp32::Esp32, - esp32c2::Esp32c2, - esp32c3::Esp32c3, - esp32c6::Esp32c6, - esp32h2::Esp32h2, - esp32p4::Esp32p4, - esp32s2::Esp32s2, - esp32s3::Esp32s3, - flash_target::{Esp32Target, RamTarget}, -}; -#[cfg(feature = "serialport")] -use crate::connection::Connection; -#[cfg(feature = "serialport")] -use crate::flash_data::{SpiAttachParams, FLASH_WRITE_SIZE}; use crate::{ elf::FirmwareImage, error::Error, - flash_data::{FlashData, FlashFrequency}, + flasher::{FlashData, FlashFrequency}, image_format::IdfBootloaderFormat, + targets::{ + esp32::Esp32, esp32c2::Esp32c2, esp32c3::Esp32c3, esp32c6::Esp32c6, esp32h2::Esp32h2, + esp32p4::Esp32p4, esp32s2::Esp32s2, esp32s3::Esp32s3, + }, +}; + +#[cfg(feature = "serialport")] +pub use self::flash_target::{Esp32Target, RamTarget}; + +#[cfg(feature = "serialport")] +use crate::{ + connection::Connection, + flasher::{stubs::FLASH_WRITE_SIZE, SpiAttachParams}, + targets::flash_target::{FlashTarget, MAX_RAM_BLOCK_SIZE}, }; /// Max partition size is 16 MB @@ -45,8 +42,13 @@ mod esp32h2; mod esp32p4; mod esp32s2; mod esp32s3; + +#[cfg(feature = "serialport")] mod flash_target; +#[cfg(feature = "serialport")] +pub use flash_target::ProgressCallbacks; + /// Supported crystal frequencies /// /// Note that not all frequencies are supported by each target device.