-
Notifications
You must be signed in to change notification settings - Fork 389
Description
Motivations
So, initially PartitionEntry was defined as follows:
/// Represents a single partition entry.
#[derive(Clone, Copy)]
pub struct PartitionEntry<'a> {
pub(crate) binary: &'a [u8; RAW_ENTRY_LEN],
}This means that the lifetime of PartitionEntry must be as long as that of the partition table.
However, in cases where one wishes to do away with the partition table (because it is no longer needed), and preserve the partition partition for the rest of the application (let's say one intends to perform Ota, the only necessary partitions are ota_data, and the list of ota partitions to choose from; One might only preserve the information needed for ota_data and a chosen next_ota_partition beforehand). Storing the entire partition table might be a bit wasteful here. So, the better way would be to extract the binary field out. However, this might be too much of hassle for the user.
Solution
A better approach to making it "clonable" and detaching it from PartitionTable would be to use the zerocopy crate to define the struct as is ( similar to that in C ). And make the function new() returns &Self instead of Self. This would allow for more flexible control of the lifetime of PartitionTable.
use zerocopy::little_endian::{U16 as u16_le, U32 as u32_le};
/// Represents a single partition entry.
#[derive(Clone, Immutable, FromBytes, IntoBytes, KnownLayout)]
#[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,
}For the implemenation, I have kept the functions for backwards compatibility reasons (I would happily change them if allowed)
impl PartitionEntry {
fn new<'a>(binary: &'a [u8; RAW_ENTRY_LEN]) -> &'a Self {
//TODO: should handle error properly instead of unwrapping
PartitionEntry::ref_from_bytes(binary).unwrap()
}
/// The magic value of the entry.
pub fn magic(&self) -> u16 {
self.magic.into()
}
/// The partition type in raw representation.
pub fn raw_type(&self) -> u8 {
self.raw_type.into()
}
/// The partition sub-type in raw representation.
pub fn raw_subtype(&self) -> u8 {
self.raw_subtype.into()
}
/// Offset of the partition on flash.
pub fn offset(&self) -> u32 {
self.offset.into()
}
/// Length of the partition in bytes.
pub fn len(&self) -> u32 {
self.len.into()
}
/// Checks for a zero-length partition.
pub fn is_empty(&self) -> bool {
self.len() == 0
}
/// The label of the partition.
pub fn label(&self) -> &[u8] {
&self.label
}
/// The label of the partition as `&str`.
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 {
self.flags.into()
}
/// If the partition is read only.
pub fn is_read_only(&self) -> bool {
self.flags() & 0b01 != 0
}
/// If the partition is encrypted.
pub fn is_encrypted(&self) -> bool {
self.flags() & 0b10 != 0
}
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, then return the hash
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() {
0 => PartitionType::App(unwrap!(self.raw_subtype().try_into())),
1 => PartitionType::Data(unwrap!(self.raw_subtype().try_into())),
2 => PartitionType::Bootloader(unwrap!(self.raw_subtype().try_into())),
3 => PartitionType::PartitionTable(unwrap!(self.raw_subtype().try_into())),
_ => unreachable!(),
}
}
/// 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<'a, F>(self, flash: &'a mut F) -> FlashRegion<'a, F>
where
F: embedded_storage::ReadStorage,
{
FlashRegion { raw: self, flash }
}
}In addition to the solution described, I have provided an MD5 hash function (because in terms of correctness, a MD5 partition should manifest its properties for more flexbility)
Alternatives
One could copy the content of binary from the partition table buffer directly. But this would require RAW_ENTRY_LEN to be declared public.
// src/partition.rs
const RAW_ENTRY_LEN: usize = 32;which is truly inconvenient for the user.
Metadata
Metadata
Assignees
Labels
Type
Projects
Status