use embedded_storage::{ReadStorage, Storage};
use crate::partitions::{
AppPartitionSubType,
DataPartitionSubType,
Error,
FlashRegion,
PartitionType,
};
const SLOT0_DATA_OFFSET: u32 = 0x0000;
const SLOT1_DATA_OFFSET: u32 = 0x1000;
const UNINITIALIZED_SEQUENCE: u32 = 0xffffffff;
#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
enum OtaDataSlot {
None,
Slot0,
Slot1,
}
impl OtaDataSlot {
fn next(&self) -> OtaDataSlot {
match self {
OtaDataSlot::None => OtaDataSlot::Slot0,
OtaDataSlot::Slot0 => OtaDataSlot::Slot1,
OtaDataSlot::Slot1 => OtaDataSlot::Slot0,
}
}
fn offset(&self) -> u32 {
match self {
OtaDataSlot::None => SLOT0_DATA_OFFSET,
OtaDataSlot::Slot0 => SLOT0_DATA_OFFSET,
OtaDataSlot::Slot1 => SLOT1_DATA_OFFSET,
}
}
}
#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash, strum::FromRepr)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(u32)]
pub enum OtaImageState {
New = 0x0,
PendingVerify = 0x1,
Valid = 0x2,
Invalid = 0x3,
Aborted = 0x4,
#[default]
Undefined = 0xFFFFFFFF,
}
impl TryFrom<u32> for OtaImageState {
type Error = Error;
fn try_from(value: u32) -> Result<Self, Self::Error> {
OtaImageState::from_repr(value).ok_or(Error::Invalid)
}
}
#[derive(Debug, Clone, Copy, Default)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[repr(C)]
struct OtaSelectEntry {
pub ota_seq: u32,
pub seq_label: [u8; 20],
pub ota_state: OtaImageState,
pub crc: u32,
}
impl OtaSelectEntry {
fn as_bytes_mut(&mut self) -> &mut [u8; 0x20] {
debug_assert!(core::mem::size_of::<Self>() == 32);
unwrap!(
unsafe { core::slice::from_raw_parts_mut(self as *mut _ as *mut u8, 0x20) }.try_into()
)
}
}
#[derive(Debug)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Ota<'a, F>
where
F: embedded_storage::Storage,
{
flash: FlashRegion<'a, F>,
ota_partition_count: usize,
}
impl<'a, F> Ota<'a, F>
where
F: embedded_storage::Storage,
{
pub fn new(flash: FlashRegion<'a, F>, ota_partition_count: usize) -> Result<Ota<'a, F>, Error> {
if ota_partition_count == 0 || ota_partition_count > 16 {
return Err(Error::InvalidArgument);
}
if flash.capacity() != 0x2000
|| flash.raw.partition_type() != PartitionType::Data(DataPartitionSubType::Ota)
{
return Err(Error::InvalidPartition {
expected_size: 0x2000,
expected_type: PartitionType::Data(DataPartitionSubType::Ota),
});
}
Ok(Ota {
flash,
ota_partition_count,
})
}
pub fn current_app_partition(&mut self) -> Result<AppPartitionSubType, Error> {
let (seq0, seq1) = self.get_slot_seq()?;
let slot = if seq0 == UNINITIALIZED_SEQUENCE && seq1 == UNINITIALIZED_SEQUENCE {
AppPartitionSubType::Factory
} else if seq0 == UNINITIALIZED_SEQUENCE {
AppPartitionSubType::from_ota_app_number(
((seq1 - 1) % self.ota_partition_count as u32) as u8,
)?
} else if seq1 == UNINITIALIZED_SEQUENCE || seq0 > seq1 {
AppPartitionSubType::from_ota_app_number(
((seq0 - 1) % self.ota_partition_count as u32) as u8,
)?
} else {
let counter = u32::max(seq0, seq1) - 1;
AppPartitionSubType::from_ota_app_number(
(counter % self.ota_partition_count as u32) as u8,
)?
};
Ok(slot)
}
fn get_slot_seq(&mut self) -> Result<(u32, u32), Error> {
let mut buffer1 = OtaSelectEntry::default();
let mut buffer2 = OtaSelectEntry::default();
self.flash.read(SLOT0_DATA_OFFSET, buffer1.as_bytes_mut())?;
self.flash.read(SLOT1_DATA_OFFSET, buffer2.as_bytes_mut())?;
let seq0 = buffer1.ota_seq;
let seq1 = buffer2.ota_seq;
Ok((seq0, seq1))
}
pub fn set_current_app_partition(&mut self, app: AppPartitionSubType) -> Result<(), Error> {
if app == AppPartitionSubType::Factory {
self.flash.write(SLOT0_DATA_OFFSET, &[0xffu8; 0x20])?;
self.flash.write(SLOT1_DATA_OFFSET, &[0xffu8; 0x20])?;
return Ok(());
}
if app == AppPartitionSubType::Test {
return Err(Error::InvalidArgument);
}
let ota_app_index = app.ota_app_number();
if ota_app_index >= self.ota_partition_count as u8 {
return Err(Error::InvalidArgument);
}
let current = self.current_app_partition()?;
if current != app {
let inc = if current == AppPartitionSubType::Factory {
(((app.ota_app_number()) as i32 + 1) + (self.ota_partition_count as i32)) as u32
% self.ota_partition_count as u32
} else {
((((app.ota_app_number()) as i32) - ((current.ota_app_number()) as i32))
+ (self.ota_partition_count as i32)) as u32
% self.ota_partition_count as u32
};
let slot = self.current_slot()?.next();
let (seq0, seq1) = self.get_slot_seq()?;
let new_seq = {
if seq0 == UNINITIALIZED_SEQUENCE && seq1 == UNINITIALIZED_SEQUENCE {
inc
} else if seq0 == UNINITIALIZED_SEQUENCE {
seq1 + inc
} else if seq1 == UNINITIALIZED_SEQUENCE {
seq0 + inc
} else {
u32::max(seq0, seq1) + inc
}
};
let crc = crate::crypto::Crc32::new();
let checksum = crc.crc(&new_seq.to_le_bytes());
let mut buffer = OtaSelectEntry::default();
self.flash.read(slot.offset(), buffer.as_bytes_mut())?;
buffer.ota_seq = new_seq;
buffer.crc = checksum;
self.flash.write(slot.offset(), buffer.as_bytes_mut())?;
}
Ok(())
}
fn current_slot(&mut self) -> Result<OtaDataSlot, Error> {
let (seq0, seq1) = self.get_slot_seq()?;
let slot = if seq0 == UNINITIALIZED_SEQUENCE && seq1 == UNINITIALIZED_SEQUENCE {
OtaDataSlot::None
} else if seq0 == UNINITIALIZED_SEQUENCE {
OtaDataSlot::Slot1
} else if seq1 == UNINITIALIZED_SEQUENCE || seq0 > seq1 {
OtaDataSlot::Slot0
} else {
OtaDataSlot::Slot1
};
Ok(slot)
}
pub fn set_current_ota_state(&mut self, state: OtaImageState) -> Result<(), Error> {
if let (UNINITIALIZED_SEQUENCE, UNINITIALIZED_SEQUENCE) = self.get_slot_seq()? {
Err(Error::InvalidState)
} else {
let offset = self.current_slot()?.offset();
let mut buffer = OtaSelectEntry::default();
self.flash.read(offset, buffer.as_bytes_mut())?;
buffer.ota_state = state;
self.flash.write(offset, buffer.as_bytes_mut())?;
Ok(())
}
}
pub fn current_ota_state(&mut self) -> Result<OtaImageState, Error> {
if let (UNINITIALIZED_SEQUENCE, UNINITIALIZED_SEQUENCE) = self.get_slot_seq()? {
Err(Error::InvalidState)
} else {
let offset = self.current_slot()?.offset();
let mut buffer = OtaSelectEntry::default();
self.flash.read(offset, buffer.as_bytes_mut())?;
Ok(buffer.ota_state)
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::partitions::PartitionEntry;
struct MockFlash {
data: [u8; 0x2000],
}
impl embedded_storage::Storage for MockFlash {
fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes);
Ok(())
}
}
impl embedded_storage::ReadStorage for MockFlash {
type Error = crate::partitions::Error;
fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> {
let l = buffer.len();
buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]);
Ok(())
}
fn capacity(&self) -> usize {
unimplemented!()
}
}
const PARTITION_RAW: [u8; 32] = [
0xaa, 0x50, 1, 0, 0, 0, 0, 0, 0, 0x20, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, ];
const SLOT_INITIAL: &[u8] = &[
255u8, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
];
const SLOT_COUNT_1_UNDEFINED: &[u8] = &[
1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 255, 255, 255, 255, 154, 152, 67, 71,
];
const SLOT_COUNT_1_VALID: &[u8] = &[
1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 255, 2, 0, 0, 0, 154, 152, 67, 71,
];
const SLOT_COUNT_2_NEW: &[u8] = &[
2, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 0, 0, 0, 0, 116, 55, 246, 85,
];
const SLOT_COUNT_3_PENDING: &[u8] = &[
3, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
255, 255, 255, 255, 1, 0, 0, 0, 17, 80, 74, 237,
];
#[test]
fn test_initial_state_and_next_slot() {
let mut binary = PARTITION_RAW;
let mock_entry = PartitionEntry {
binary: &mut binary,
};
let mut mock_flash = MockFlash {
data: [0xff; 0x2000],
};
let mock_region = FlashRegion {
raw: mock_entry,
flash: &mut mock_flash,
};
let mut sut = Ota::new(mock_region, 2).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Factory
);
assert_eq!(
sut.current_ota_state(),
Err(crate::partitions::Error::InvalidState)
);
assert_eq!(
sut.set_current_ota_state(OtaImageState::New),
Err(crate::partitions::Error::InvalidState)
);
assert_eq!(
sut.current_ota_state(),
Err(crate::partitions::Error::InvalidState)
);
sut.set_current_app_partition(AppPartitionSubType::Ota0)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
assert_eq!(SLOT_COUNT_1_UNDEFINED, &mock_flash.data[0x0000..][..0x20],);
assert_eq!(SLOT_INITIAL, &mock_flash.data[0x1000..][..0x20],);
}
#[test]
fn test_slot0_valid_next_slot() {
let mut binary = PARTITION_RAW;
let mock_entry = PartitionEntry {
binary: &mut binary,
};
let mut mock_flash = MockFlash {
data: [0xff; 0x2000],
};
mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID);
mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_INITIAL);
let mock_region = FlashRegion {
raw: mock_entry,
flash: &mut mock_flash,
};
let mut sut = Ota::new(mock_region, 2).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
sut.set_current_app_partition(AppPartitionSubType::Ota1)
.unwrap();
sut.set_current_ota_state(OtaImageState::New).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota1
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
assert_eq!(SLOT_COUNT_1_VALID, &mock_flash.data[0x0000..][..0x20],);
assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],);
}
#[test]
fn test_slot1_new_next_slot() {
let mut binary = PARTITION_RAW;
let mock_entry = PartitionEntry {
binary: &mut binary,
};
let mut mock_flash = MockFlash {
data: [0xff; 0x2000],
};
mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID);
mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_COUNT_2_NEW);
let mock_region = FlashRegion {
raw: mock_entry,
flash: &mut mock_flash,
};
let mut sut = Ota::new(mock_region, 2).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota1
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
sut.set_current_app_partition(AppPartitionSubType::Ota0)
.unwrap();
sut.set_current_ota_state(OtaImageState::PendingVerify)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
assert_eq!(SLOT_COUNT_3_PENDING, &mock_flash.data[0x0000..][..0x20],);
assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],);
}
#[test]
fn test_multi_updates() {
let mut binary = PARTITION_RAW;
let mock_entry = PartitionEntry {
binary: &mut binary,
};
let mut mock_flash = MockFlash {
data: [0xff; 0x2000],
};
let mock_region = FlashRegion {
raw: mock_entry,
flash: &mut mock_flash,
};
let mut sut = Ota::new(mock_region, 2).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Factory
);
assert_eq!(
sut.current_ota_state(),
Err(crate::partitions::Error::InvalidState)
);
sut.set_current_app_partition(AppPartitionSubType::Ota0)
.unwrap();
sut.set_current_ota_state(OtaImageState::PendingVerify)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
sut.set_current_app_partition(AppPartitionSubType::Ota1)
.unwrap();
sut.set_current_ota_state(OtaImageState::New).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota1
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
sut.set_current_app_partition(AppPartitionSubType::Ota0)
.unwrap();
sut.set_current_ota_state(OtaImageState::Aborted).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted));
sut.set_current_app_partition(AppPartitionSubType::Ota0)
.unwrap();
sut.set_current_ota_state(OtaImageState::Valid).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
}
#[test]
fn test_multi_updates_4_apps() {
let mut binary = PARTITION_RAW;
let mock_entry = PartitionEntry {
binary: &mut binary,
};
let mut mock_flash = MockFlash {
data: [0xff; 0x2000],
};
let mock_region = FlashRegion {
raw: mock_entry,
flash: &mut mock_flash,
};
let mut sut = Ota::new(mock_region, 4).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Factory
);
assert_eq!(
sut.current_ota_state(),
Err(crate::partitions::Error::InvalidState)
);
sut.set_current_app_partition(AppPartitionSubType::Ota0)
.unwrap();
sut.set_current_ota_state(OtaImageState::PendingVerify)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota0
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
sut.set_current_app_partition(AppPartitionSubType::Ota1)
.unwrap();
sut.set_current_ota_state(OtaImageState::New).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota1
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
sut.set_current_app_partition(AppPartitionSubType::Ota2)
.unwrap();
sut.set_current_ota_state(OtaImageState::Aborted).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota2
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Aborted));
sut.set_current_app_partition(AppPartitionSubType::Ota3)
.unwrap();
sut.set_current_ota_state(OtaImageState::Valid).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota3
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
sut.set_current_app_partition(AppPartitionSubType::Ota2)
.unwrap();
sut.set_current_ota_state(OtaImageState::Invalid).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota2
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Invalid));
assert_eq!(
sut.set_current_app_partition(AppPartitionSubType::Ota5),
Err(crate::partitions::Error::InvalidArgument)
);
assert_eq!(
sut.set_current_app_partition(AppPartitionSubType::Test),
Err(crate::partitions::Error::InvalidArgument)
);
}
#[test]
fn test_multi_updates_skip_parts() {
let mut binary = PARTITION_RAW;
let mock_entry = PartitionEntry {
binary: &mut binary,
};
let mut mock_flash = MockFlash {
data: [0xff; 0x2000],
};
let mock_region = FlashRegion {
raw: mock_entry,
flash: &mut mock_flash,
};
let mut sut = Ota::new(mock_region, 16).unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Factory
);
assert_eq!(
sut.current_ota_state(),
Err(crate::partitions::Error::InvalidState)
);
sut.set_current_app_partition(AppPartitionSubType::Ota10)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota10
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
sut.set_current_app_partition(AppPartitionSubType::Ota14)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota14
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
sut.set_current_app_partition(AppPartitionSubType::Ota5)
.unwrap();
assert_eq!(
sut.current_app_partition().unwrap(),
AppPartitionSubType::Ota5
);
assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
}
#[test]
fn test_ota_slot_next() {
assert_eq!(OtaDataSlot::None.next(), OtaDataSlot::Slot0);
assert_eq!(OtaDataSlot::Slot0.next(), OtaDataSlot::Slot1);
assert_eq!(OtaDataSlot::Slot1.next(), OtaDataSlot::Slot0);
}
}