diff --git a/esp-bootloader-esp-idf/Cargo.toml b/esp-bootloader-esp-idf/Cargo.toml index 96b8a39ab87..e129a3a3db9 100644 --- a/esp-bootloader-esp-idf/Cargo.toml +++ b/esp-bootloader-esp-idf/Cargo.toml @@ -42,6 +42,7 @@ strum = { version = "0.27", default-features = false, features = ["derive"] } crc = { version = "3.3.0", optional = true } md-5 = { version = "0.10.6", default-features = false, optional = true } +zerocopy = { version = "0.8.33", features = ["derive"] } [build-dependencies] jiff = { version = "0.2.13", default-features = false, features = ["std"] } diff --git a/esp-bootloader-esp-idf/src/ota.rs b/esp-bootloader-esp-idf/src/ota.rs index 3adc181d36d..82567180e29 100644 --- a/esp-bootloader-esp-idf/src/ota.rs +++ b/esp-bootloader-esp-idf/src/ota.rs @@ -355,6 +355,8 @@ where #[cfg(test)] mod tests { + use zerocopy::FromBytes; + use super::*; use crate::partitions::PartitionEntry; @@ -421,16 +423,14 @@ mod tests { fn test_initial_state_and_next_slot() { let mut binary = PARTITION_RAW; - let mock_entry = PartitionEntry { - binary: &mut binary, - }; + let mock_entry = PartitionEntry::ref_from_bytes(&binary).unwrap(); let mut mock_flash = MockFlash { data: [0xff; 0x2000], }; let mock_region = FlashRegion { - raw: mock_entry, + raw: mock_entry.clone(), flash: &mut mock_flash, }; @@ -468,9 +468,7 @@ mod tests { fn test_slot0_valid_next_slot() { let mut binary = PARTITION_RAW; - let mock_entry = PartitionEntry { - binary: &mut binary, - }; + let mock_entry = PartitionEntry::ref_from_bytes(&binary).unwrap(); let mut mock_flash = MockFlash { data: [0xff; 0x2000], @@ -480,7 +478,7 @@ mod tests { mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_INITIAL); let mock_region = FlashRegion { - raw: mock_entry, + raw: mock_entry.clone(), flash: &mut mock_flash, }; @@ -508,9 +506,7 @@ mod tests { fn test_slot1_new_next_slot() { let mut binary = PARTITION_RAW; - let mock_entry = PartitionEntry { - binary: &mut binary, - }; + let mock_entry = PartitionEntry::ref_from_bytes(&binary).unwrap(); let mut mock_flash = MockFlash { data: [0xff; 0x2000], @@ -520,7 +516,7 @@ mod tests { mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_COUNT_2_NEW); let mock_region = FlashRegion { - raw: mock_entry, + raw: mock_entry.clone(), flash: &mut mock_flash, }; @@ -549,16 +545,14 @@ mod tests { fn test_multi_updates() { let mut binary = PARTITION_RAW; - let mock_entry = PartitionEntry { - binary: &mut binary, - }; + let mock_entry = PartitionEntry::ref_from_bytes(&binary).unwrap(); let mut mock_flash = MockFlash { data: [0xff; 0x2000], }; let mock_region = FlashRegion { - raw: mock_entry, + raw: mock_entry.clone(), flash: &mut mock_flash, }; @@ -615,16 +609,14 @@ mod tests { fn test_multi_updates_4_apps() { let mut binary = PARTITION_RAW; - let mock_entry = PartitionEntry { - binary: &mut binary, - }; + let mock_entry = PartitionEntry::ref_from_bytes(&binary).unwrap(); let mut mock_flash = MockFlash { data: [0xff; 0x2000], }; let mock_region = FlashRegion { - raw: mock_entry, + raw: mock_entry.clone(), flash: &mut mock_flash, }; @@ -700,16 +692,14 @@ mod tests { fn test_multi_updates_skip_parts() { let mut binary = PARTITION_RAW; - let mock_entry = PartitionEntry { - binary: &mut binary, - }; + let mock_entry = PartitionEntry::ref_from_bytes(&binary).unwrap(); let mut mock_flash = MockFlash { data: [0xff; 0x2000], }; let mock_region = FlashRegion { - raw: mock_entry, + raw: mock_entry.clone(), flash: &mut mock_flash, }; diff --git a/esp-bootloader-esp-idf/src/ota_updater.rs b/esp-bootloader-esp-idf/src/ota_updater.rs index b564c6749b6..be44bb96759 100644 --- a/esp-bootloader-esp-idf/src/ota_updater.rs +++ b/esp-bootloader-esp-idf/src/ota_updater.rs @@ -79,7 +79,7 @@ where crate::partitions::DataPartitionSubType::Ota, ))?; if let Some(ota_part) = ota_part { - let ota_part = ota_part.as_embedded_storage(self.flash); + let ota_part = ota_part.clone().as_embedded_storage(self.flash); let ota = crate::ota::Ota::new(ota_part, self.ota_count)?; Ok(ota) } else { @@ -152,6 +152,7 @@ where .pt .find_partition(crate::partitions::PartitionType::App(next_slot))? .ok_or(Error::Invalid)? + .clone() .as_embedded_storage(self.flash); Ok((flash_region, next_slot)) diff --git a/esp-bootloader-esp-idf/src/partitions.rs b/esp-bootloader-esp-idf/src/partitions.rs index 70702ea4244..f755a71d487 100644 --- a/esp-bootloader-esp-idf/src/partitions.rs +++ b/esp-bootloader-esp-idf/src/partitions.rs @@ -7,6 +7,10 @@ //! //! For more information see +use core::ffi::CStr; + +use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout, Unaligned}; + /// Maximum length of a partition table. pub const PARTITION_TABLE_MAX_LEN: usize = 0xC00; @@ -19,40 +23,48 @@ const MD5_MAGIC: u16 = 0xebeb; const OTA_SUBTYPE_OFFSET: u8 = 0x10; +use zerocopy::little_endian::{U16 as u16_le, U32 as u32_le}; /// Represents a single partition entry. -#[derive(Clone, Copy)] -pub struct PartitionEntry<'a> { - pub(crate) binary: &'a [u8; RAW_ENTRY_LEN], +#[derive(Clone, Copy, Immutable, FromBytes, IntoBytes, KnownLayout, Unaligned)] +#[repr(packed)] +pub struct PartitionEntry { + magic: u16_le, + raw_type: u8, + raw_subtype: u8, + offset: u32_le, + len: u32_le, + label: [u8; 16], + flags: u32_le, } -impl<'a> PartitionEntry<'a> { - fn new(binary: &'a [u8; RAW_ENTRY_LEN]) -> Self { - Self { binary } +impl PartitionEntry { + fn new<'a>(binary: &'a [u8; RAW_ENTRY_LEN]) -> &'a Self { + PartitionEntry::ref_from_bytes(binary).unwrap() } /// The magic value of the entry. pub fn magic(&self) -> u16 { - u16::from_le_bytes(unwrap!(self.binary[..2].try_into())) + self.magic.into() } /// The partition type in raw representation. pub fn raw_type(&self) -> u8 { - self.binary[2] + self.raw_type.into() } /// The partition sub-type in raw representation. pub fn raw_subtype(&self) -> u8 { - self.binary[3] + self.raw_subtype.into() } /// Offset of the partition on flash. pub fn offset(&self) -> u32 { - u32::from_le_bytes(unwrap!(self.binary[4..][..4].try_into())) + self.offset.into() } /// Length of the partition in bytes. pub fn len(&self) -> u32 { - u32::from_le_bytes(unwrap!(self.binary[8..][..4].try_into())) + self.len.into() } /// Checks for a zero-length partition. @@ -61,26 +73,20 @@ impl<'a> PartitionEntry<'a> { } /// The label of the partition. - pub fn label(&self) -> &'a [u8] { - &self.binary[12..][..16] + pub fn label(&self) -> &[u8] { + &self.label } /// The label of the partition as `&str`. - pub fn label_as_str(&self) -> &'a str { - let array = self.label(); - let len = array - .iter() - .position(|b| *b == 0 || *b == 0xff) - .unwrap_or(array.len()); - unsafe { - core::str::from_utf8_unchecked(core::slice::from_raw_parts(array.as_ptr().cast(), len)) - } + pub fn label_as_str(&self) -> &str { + let cstr = CStr::from_bytes_until_nul(self.label()).unwrap(); + cstr.to_str().unwrap() } /// Raw flags of this partition. You probably want to use /// [Self::is_read_only] and [Self::is_encrypted] instead. pub fn flags(&self) -> u32 { - u32::from_le_bytes(unwrap!(self.binary[28..][..4].try_into())) + self.flags.into() } /// If the partition is read only. @@ -93,6 +99,49 @@ impl<'a> PartitionEntry<'a> { self.flags() & 0b10 != 0 } + #[cfg(not(feature = "std"))] + /// Get the currently booted partition. + pub fn is_booted(&self) -> bool { + cfg_if::cfg_if! { + if #[cfg(feature = "esp32")] { + let paddr = unsafe { + ((0x3FF10000 as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(feature = "esp32s2")] { + let paddr = unsafe { + (((0x61801000 + 128 * 4) as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(feature = "esp32s3")] { + // Revisit this once we support XiP from PSRAM for ESP32-S3 + let paddr = unsafe { + ((0x600C5000 as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(any(feature = "esp32c2", feature = "esp32c3"))] { + let paddr = unsafe { + ((0x600c5000 as *const u32).read_volatile() & 0xff) << 16 + }; + } else if #[cfg(any(feature = "esp32c6", feature = "esp32h2"))] { + let paddr = unsafe { + ((0x60002000 + 0x380) as *mut u32).write_volatile(0); + (((0x60002000 + 0x37c) as *const u32).read_volatile() & 0xff) << 16 + }; + } else { + compile_error!("Paddr not defined for the current esp32 device, check if you have enabled it, or raise an issue about it"); + } + } + self.offset == paddr + } + + #[cfg(feature = "validation")] + /// If entry is magic + fn hash(&self) -> Option<&[u8]> { + if self.magic() == MD5_MAGIC { + Some(&self.as_bytes()[16..][..16]) + } else { + None + } + } + /// The partition type (type and sub-type). pub fn partition_type(&self) -> PartitionType { match self.raw_type() { @@ -107,7 +156,7 @@ impl<'a> PartitionEntry<'a> { /// Provides a "view" into the partition allowing to read/write the /// partition contents by using the given [embedded_storage::Storage] and/or /// [embedded_storage::ReadStorage] implementation. - pub fn as_embedded_storage(self, flash: &'a mut F) -> FlashRegion<'a, F> + pub fn as_embedded_storage<'a, F>(self, flash: &'a mut F) -> FlashRegion<'a, F> where F: embedded_storage::ReadStorage, { @@ -115,7 +164,7 @@ impl<'a> PartitionEntry<'a> { } } -impl core::fmt::Debug for PartitionEntry<'_> { +impl core::fmt::Debug for PartitionEntry { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { f.debug_struct("PartitionEntry") .field("magic", &self.magic()) @@ -132,7 +181,7 @@ impl core::fmt::Debug for PartitionEntry<'_> { } #[cfg(feature = "defmt")] -impl defmt::Format for PartitionEntry<'_> { +impl defmt::Format for PartitionEntry { fn format(&self, fmt: defmt::Formatter) { defmt::write!( fmt, @@ -219,27 +268,20 @@ impl<'a> PartitionTable<'a> { #[cfg(feature = "validation")] { - let (hash, index) = { - let mut i = 0; - loop { - if let Ok(entry) = raw_table.get_partition(i) { - if entry.magic() == MD5_MAGIC { - break (&entry.binary[16..][..16], i); - } - - i += 1; - if i >= raw_table.entries { - return Err(Error::Invalid); - } - } - } - }; + let (hash, index) = raw_table + .iter() + .enumerate() + .find_map(|(i, e)| e.hash().map(|h| (h, i))) + .ok_or(Error::Invalid)?; let mut hasher = crate::crypto::Md5::new(); - for i in 0..index { - hasher.update(&raw_table.binary[i]); - } + raw_table + .binary + .iter() + .take(index) + .for_each(|e| hasher.update(e)); + let calculated_hash = hasher.finalize(); if calculated_hash != hash { @@ -283,7 +325,7 @@ impl<'a> PartitionTable<'a> { } /// Get a partition entry. - pub fn get_partition(&self, index: usize) -> Result, Error> { + pub fn get_partition(&self, index: usize) -> Result<&'a PartitionEntry, Error> { if index >= self.entries { return Err(Error::OutOfBounds); } @@ -291,7 +333,7 @@ impl<'a> PartitionTable<'a> { } /// Get the first partition matching the given partition type. - pub fn find_partition(&self, pt: PartitionType) -> Result>, Error> { + pub fn find_partition(&self, pt: PartitionType) -> Result, Error> { for i in 0..self.entries { let entry = self.get_partition(i)?; if entry.partition_type() == pt { @@ -302,19 +344,19 @@ impl<'a> PartitionTable<'a> { } /// Returns an iterator over the partitions. - pub fn iter(&self) -> impl Iterator> { + pub fn iter(&self) -> impl Iterator { (0..self.entries).filter_map(|i| self.get_partition(i).ok()) } #[cfg(feature = "std")] /// Get the currently booted partition. - pub fn booted_partition(&self) -> Result>, Error> { + pub fn booted_partition(&self) -> Result, Error> { Err(Error::Invalid) } #[cfg(not(feature = "std"))] /// Get the currently booted partition. - pub fn booted_partition(&self) -> Result>, Error> { + pub fn booted_partition(&self) -> Result, Error> { // Read entry 0 from MMU to know which partition is mapped // // See @@ -549,7 +591,7 @@ pub fn read_partition_table<'a>( #[derive(Debug)] #[cfg_attr(feature = "defmt", derive(defmt::Format))] pub struct FlashRegion<'a, F> { - pub(crate) raw: PartitionEntry<'a>, + pub(crate) raw: PartitionEntry, pub(crate) flash: &'a mut F, }