eculocate

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README

lib.rs (15503B)


      1 #![no_std]
      2 #![cfg_attr(any(feature = "esp32", feature = "esp32s2"), feature(concat_idents))]
      3 #![cfg_attr(feature = "esp32", feature(asm_experimental_arch))]
      4 #![doc = include_str!("../README.md")]
      5 
      6 #[macro_use]
      7 mod logging;
      8 
      9 use embedded_storage::{ReadStorage, Storage};
     10 pub use structs::*;
     11 
     12 pub mod crc32;
     13 pub mod helpers;
     14 pub mod mmu_hal;
     15 pub mod mmu_ll;
     16 pub mod structs;
     17 
     18 const PART_OFFSET: u32 = 0x8000;
     19 const PART_SIZE: u32 = 0xc00;
     20 const FIRST_OTA_PART_SUBTYPE: u8 = 0x10;
     21 const OTA_VERIFY_READ_SIZE: usize = 256;
     22 
     23 pub struct Ota<S>
     24 where
     25     S: ReadStorage + Storage,
     26 {
     27     flash: S,
     28 
     29     progress: Option<FlashProgress>,
     30     pinfo: PartitionInfo,
     31 }
     32 
     33 pub struct ProgressDetails {
     34     pub remaining: u32,
     35     pub last_crc: u32,
     36 }
     37 
     38 #[derive(Debug)]
     39 pub struct OtaConfiguratuion {
     40     partition_table_offset: u32,
     41 }
     42 impl OtaConfiguratuion {
     43     /// Create default configuration of the [`Ota`] process.
     44     ///
     45     /// Notably this uses the default partition table offset of `0x8000`
     46     pub const fn new() -> Self {
     47         Self {
     48             partition_table_offset: PART_OFFSET,
     49         }
     50     }
     51 
     52     /// Set custom partition table offset
     53     pub const fn with_partition_table_offset(self, partition_table_offset: u32) -> Self {
     54         Self {
     55             partition_table_offset,
     56             ..self
     57         }
     58     }
     59 }
     60 impl Default for OtaConfiguratuion {
     61     fn default() -> Self {
     62         Self::new()
     63     }
     64 }
     65 
     66 impl<S> Ota<S>
     67 where
     68     S: ReadStorage + Storage,
     69 {
     70     pub fn new(flash: S) -> Result<Self> {
     71         Self::with_configuration(flash, OtaConfiguratuion::new())
     72     }
     73 
     74     pub fn with_configuration(mut flash: S, config: OtaConfiguratuion) -> Result<Self> {
     75         let pinfo = Self::read_partitions(&mut flash, config.partition_table_offset)?;
     76         if pinfo.ota_partitions_count < 2 {
     77             error!("Not enough OTA partitions! (>= 2)");
     78 
     79             return Err(OtaError::NotEnoughPartitions);
     80         }
     81 
     82         Ok(Ota {
     83             flash,
     84             progress: None,
     85             pinfo,
     86         })
     87     }
     88 
     89     fn get_partitions(&self) -> &[(u32, u32)] {
     90         &self.pinfo.ota_partitions[..self.pinfo.ota_partitions_count]
     91     }
     92 
     93     /// To begin ota update (need to provide flash size)
     94     pub fn ota_begin(&mut self, size: u32, target_crc: u32) -> Result<()> {
     95         let next_part = self.get_next_ota_partition().unwrap_or(0);
     96 
     97         let ota_offset = self.get_partitions()[next_part].0;
     98         self.progress = Some(FlashProgress {
     99             last_crc: 0,
    100             flash_size: size,
    101             remaining: size,
    102             flash_offset: ota_offset,
    103             target_partition: next_part,
    104             target_crc,
    105         });
    106 
    107         Ok(())
    108     }
    109 
    110     /// Resumes an OTA update after progress has been lost
    111     pub fn ota_resume(
    112         &mut self,
    113         flash_size: u32,
    114         remaining: u32,
    115         target_crc: u32,
    116         last_crc: u32,
    117         verify_crc: bool,
    118     ) -> Result<()> {
    119         let next_part = self.get_next_ota_partition().unwrap_or(0);
    120         let ota_offset = self.get_partitions()[next_part].0;
    121 
    122         if verify_crc {
    123             let mut calc_crc = 0;
    124             let mut bytes = [0; OTA_VERIFY_READ_SIZE];
    125             let mut written = flash_size - remaining;
    126             let mut partition_offset = ota_offset;
    127 
    128             loop {
    129                 let n = written.min(OTA_VERIFY_READ_SIZE as u32);
    130                 if n == 0 {
    131                     break;
    132                 }
    133 
    134                 _ = self.flash.read(partition_offset, &mut bytes[..n as usize]);
    135                 partition_offset += n;
    136                 written -= n;
    137 
    138                 calc_crc = crc32::calc_crc32(&bytes[..n as usize], calc_crc);
    139             }
    140 
    141             if calc_crc != last_crc {
    142                 return Err(OtaError::WrongCRC);
    143             }
    144         }
    145 
    146         self.progress = Some(FlashProgress {
    147             last_crc,
    148             flash_size,
    149             remaining,
    150             flash_offset: ota_offset + (flash_size - remaining),
    151             target_partition: next_part,
    152             target_crc,
    153         });
    154 
    155         Ok(())
    156     }
    157 
    158     /// Returns progress details to save for resumption later
    159     pub fn get_progress_details(&self) -> Option<ProgressDetails> {
    160         if self.progress.is_none() {
    161             warn!("[OTA] Cannot get progress details!");
    162 
    163             return None;
    164         }
    165 
    166         let progress = self.progress.as_ref().unwrap();
    167         Some(ProgressDetails {
    168             remaining: progress.remaining,
    169             last_crc: progress.last_crc,
    170         })
    171     }
    172 
    173     /// Returns ota progress in f32 (0..1)
    174     pub fn get_ota_progress(&self) -> f32 {
    175         if self.progress.is_none() {
    176             warn!("[OTA] Cannot get ota progress! Seems like update wasn't started yet.");
    177 
    178             return 0.0;
    179         }
    180 
    181         let progress = self.progress.as_ref().unwrap();
    182         (progress.flash_size - progress.remaining) as f32 / progress.flash_size as f32
    183     }
    184 
    185     /// Writes next firmware chunk
    186     pub fn ota_write_chunk(&mut self, chunk: &[u8]) -> Result<bool> {
    187         let progress = self.progress.as_mut().ok_or(OtaError::OtaNotStarted)?;
    188 
    189         if progress.remaining == 0 {
    190             return Ok(true);
    191         }
    192 
    193         let write_size = chunk.len() as u32;
    194         let write_size = write_size.min(progress.remaining) as usize;
    195 
    196         let unchanged =
    197             write_size <= 8192 && {
    198                 let mut old_data = [0u8; 8192];
    199                 let p = &mut old_data[..write_size];
    200                 _ = self.flash.read(progress.flash_offset, p);
    201                 p == &chunk[..write_size]
    202             };
    203 
    204         if unchanged {
    205             debug!("[OTA] skip writing {} unchanged bytes at 0z:{:x}",
    206 		   write_size, progress.flash_offset);
    207         } else {
    208             self.flash
    209                 .write(progress.flash_offset, &chunk[..write_size])
    210                 .map_err(|_| OtaError::FlashRWError)?;
    211 
    212             debug!(
    213 		"[OTA] Wrote {} bytes to ota partition at 0x{:x}",
    214 		write_size, progress.flash_offset
    215             );
    216 	}
    217         progress.last_crc = crc32::calc_crc32(&chunk[..write_size], progress.last_crc);
    218 
    219         progress.flash_offset += write_size as u32;
    220         progress.remaining -= write_size as u32;
    221         Ok(progress.remaining == 0)
    222     }
    223 
    224     /// verify - should it read flash and check crc
    225     /// rollback - if rollbacks enable (will set ota_state to ESP_OTA_IMG_NEW)
    226     pub fn ota_flush(&mut self, verify: bool, rollback: bool) -> Result<()> {
    227         if verify && !self.ota_verify()? {
    228             error!("[OTA] Verify failed! Not flushing...");
    229 
    230             return Err(OtaError::OtaVerifyError);
    231         }
    232 
    233         let progress = self.progress.clone().ok_or(OtaError::OtaNotStarted)?;
    234 
    235         if verify && progress.target_crc != progress.last_crc {
    236             warn!("[OTA] Calculated crc: {}", progress.last_crc);
    237             warn!("[OTA] Target crc: {}", progress.target_crc);
    238             error!("[OTA] Crc check failed! Cant finish ota update...");
    239 
    240             return Err(OtaError::WrongCRC);
    241         }
    242 
    243         let img_state = match rollback {
    244             true => OtaImgState::EspOtaImgNew,
    245             false => OtaImgState::EspOtaImgUndefined,
    246         };
    247 
    248         self.set_target_ota_boot_partition(progress.target_partition, img_state);
    249         Ok(())
    250     }
    251 
    252     /// It reads written flash and checks crc
    253     pub fn ota_verify(&mut self) -> Result<bool> {
    254         let progress = self.progress.clone().ok_or(OtaError::OtaNotStarted)?;
    255 
    256         let mut calc_crc = 0;
    257         let mut bytes = [0; OTA_VERIFY_READ_SIZE];
    258 
    259         let mut partition_offset = self.pinfo.ota_partitions[progress.target_partition].0;
    260         let mut remaining = progress.flash_size;
    261 
    262         loop {
    263             let n = remaining.min(OTA_VERIFY_READ_SIZE as u32);
    264             if n == 0 {
    265                 break;
    266             }
    267 
    268             _ = self.flash.read(partition_offset, &mut bytes[..n as usize]);
    269             partition_offset += n;
    270             remaining -= n;
    271 
    272             calc_crc = crc32::calc_crc32(&bytes[..n as usize], calc_crc);
    273         }
    274 
    275         Ok(calc_crc == progress.target_crc)
    276     }
    277 
    278     /// Sets ota boot target partition
    279     pub fn set_target_ota_boot_partition(&mut self, target: usize, state: OtaImgState) {
    280         let (slot1, slot2) = self.get_ota_boot_entries();
    281         let (seq1, seq2) = (slot1.seq, slot2.seq);
    282 
    283         let mut target_seq = seq1.max(seq2);
    284         while helpers::seq_to_part(target_seq, self.pinfo.ota_partitions_count) != target
    285             || target_seq == 0
    286         {
    287             target_seq += 1;
    288         }
    289 
    290         let flash = &mut self.flash;
    291         let target_crc = crc32::calc_crc32(&target_seq.to_le_bytes(), 0xFFFFFFFF);
    292         if seq1 > seq2 {
    293             let offset = self.pinfo.otadata_offset + (self.pinfo.otadata_size >> 1);
    294 
    295             _ = flash.write(offset, &target_seq.to_le_bytes());
    296             _ = flash.write(offset + 32 - 4 - 4, &(state as u32).to_le_bytes());
    297             _ = flash.write(offset + 32 - 4, &target_crc.to_le_bytes());
    298         } else {
    299             _ = flash.write(self.pinfo.otadata_offset, &target_seq.to_le_bytes());
    300             _ = flash.write(
    301                 self.pinfo.otadata_offset + 32 - 4 - 4,
    302                 &(state as u32).to_le_bytes(),
    303             );
    304             _ = flash.write(
    305                 self.pinfo.otadata_offset + 32 - 4,
    306                 &target_crc.to_le_bytes(),
    307             );
    308         }
    309     }
    310 
    311     pub fn set_ota_state(&mut self, slot: u8, state: OtaImgState) -> Result<()> {
    312         let offset = match slot {
    313             1 => self.pinfo.otadata_offset,
    314             2 => self.pinfo.otadata_offset + (self.pinfo.otadata_size >> 1),
    315             _ => {
    316                 error!("Use slot1 or slot2!");
    317                 return Err(OtaError::CannotFindCurrentBootPartition);
    318             }
    319         };
    320 
    321         _ = self
    322             .flash
    323             .write(offset + 32 - 4 - 4, &(state as u32).to_le_bytes());
    324 
    325         Ok(())
    326     }
    327 
    328     /// Returns current OTA boot sequences
    329     ///
    330     /// NOTE: if crc doesn't match, it returns 0 for that seq
    331     /// NOTE: [Entry struct (link to .h file)](https://github.com/espressif/esp-idf/blob/master/components/bootloader_support/include/esp_flash_partitions.h#L66)
    332     pub fn get_ota_boot_entries(&mut self) -> (EspOtaSelectEntry, EspOtaSelectEntry) {
    333         let mut bytes = [0; 32];
    334         _ = self.flash.read(self.pinfo.otadata_offset, &mut bytes);
    335         let mut slot1: EspOtaSelectEntry =
    336             unsafe { core::ptr::read(bytes.as_ptr() as *const EspOtaSelectEntry) };
    337         slot1.check_crc();
    338 
    339         _ = self.flash.read(
    340             self.pinfo.otadata_offset + (self.pinfo.otadata_size >> 1),
    341             &mut bytes,
    342         );
    343         let mut slot2: EspOtaSelectEntry =
    344             unsafe { core::ptr::read(bytes.as_ptr() as *const EspOtaSelectEntry) };
    345         slot2.check_crc();
    346 
    347         (slot1, slot2)
    348     }
    349 
    350     /// Returns currently booted partition index
    351     pub fn get_currently_booted_partition(&self) -> Option<usize> {
    352         mmu_hal::esp_get_current_running_partition(self.get_partitions())
    353     }
    354 
    355     /// BUG: this wont work if user has ota partitions not starting from ota0
    356     /// or if user skips some ota partitions: ota0, ota2, ota3...
    357     pub fn get_next_ota_partition(&self) -> Option<usize> {
    358         let curr_part = mmu_hal::esp_get_current_running_partition(self.get_partitions());
    359         curr_part.map(|next_part| (next_part + 1) % self.pinfo.ota_partitions_count)
    360     }
    361 
    362     fn get_current_slot(&mut self) -> Result<(u8, EspOtaSelectEntry)> {
    363         let (slot1, slot2) = self.get_ota_boot_entries();
    364         let current_partition = self
    365             .get_currently_booted_partition()
    366             .ok_or(OtaError::CannotFindCurrentBootPartition)?;
    367 
    368         let slot1_part = helpers::seq_to_part(slot1.seq, self.pinfo.ota_partitions_count);
    369         let slot2_part = helpers::seq_to_part(slot2.seq, self.pinfo.ota_partitions_count);
    370         if current_partition == slot1_part {
    371             return Ok((1, slot1));
    372         } else if current_partition == slot2_part {
    373             return Ok((2, slot2));
    374         }
    375 
    376         Err(OtaError::CannotFindCurrentBootPartition)
    377     }
    378 
    379     pub fn get_ota_image_state(&mut self) -> Result<OtaImgState> {
    380         let (slot1, slot2) = self.get_ota_boot_entries();
    381         let current_partition = self
    382             .get_currently_booted_partition()
    383             .ok_or(OtaError::CannotFindCurrentBootPartition)?;
    384 
    385         let slot1_part = helpers::seq_to_part(slot1.seq, self.pinfo.ota_partitions_count);
    386         let slot2_part = helpers::seq_to_part(slot2.seq, self.pinfo.ota_partitions_count);
    387         if current_partition == slot1_part {
    388             return Ok(slot1.ota_state);
    389         } else if current_partition == slot2_part {
    390             return Ok(slot2.ota_state);
    391         }
    392 
    393         Err(OtaError::CannotFindCurrentBootPartition)
    394     }
    395 
    396     pub fn ota_mark_app_valid(&mut self) -> Result<()> {
    397         let (current_slot_nmb, current_slot) = self.get_current_slot()?;
    398         if current_slot.ota_state != OtaImgState::EspOtaImgValid {
    399             self.set_ota_state(current_slot_nmb, OtaImgState::EspOtaImgValid)?;
    400 
    401             info!("Marked current slot as valid!");
    402         }
    403 
    404         Ok(())
    405     }
    406 
    407     pub fn ota_mark_app_invalid_rollback(&mut self) -> Result<()> {
    408         let (current_slot_nmb, current_slot) = self.get_current_slot()?;
    409         if current_slot.ota_state != OtaImgState::EspOtaImgValid {
    410             self.set_ota_state(current_slot_nmb, OtaImgState::EspOtaImgInvalid)?;
    411 
    412             info!("Marked current slot as invalid!");
    413         }
    414 
    415         Ok(())
    416     }
    417 
    418     fn read_partitions(flash: &mut S, partition_table_offset: u32) -> Result<PartitionInfo> {
    419         let mut tmp_pinfo = PartitionInfo {
    420             ota_partitions: [(0, 0); 16],
    421             ota_partitions_count: 0,
    422             otadata_size: 0,
    423             otadata_offset: 0,
    424         };
    425 
    426         let mut bytes = [0xFF; 32];
    427         let mut last_ota_part: i8 = -1;
    428         for read_offset in (0..PART_SIZE).step_by(32) {
    429             _ = flash.read(partition_table_offset + read_offset, &mut bytes);
    430             if bytes == [0xFF; 32] {
    431                 break;
    432             }
    433 
    434             let magic = &bytes[0..2];
    435             if magic != [0xAA, 0x50] {
    436                 continue;
    437             }
    438 
    439             let p_type = &bytes[2];
    440             let p_subtype = &bytes[3];
    441             let p_offset = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
    442             let p_size = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
    443             //let p_name = core::str::from_utf8(&bytes[12..28]).unwrap();
    444             //let p_flags = u32::from_le_bytes(bytes[28..32].try_into().unwrap());
    445             //log::info!("{magic:?} {p_type} {p_subtype} {p_offset} {p_size} {p_name} {p_flags}");
    446 
    447             if *p_type == 0 && *p_subtype >= FIRST_OTA_PART_SUBTYPE {
    448                 let ota_part_idx = *p_subtype - FIRST_OTA_PART_SUBTYPE;
    449                 if ota_part_idx as i8 - last_ota_part != 1 {
    450                     return Err(OtaError::WrongOTAPArtitionOrder);
    451                 }
    452 
    453                 last_ota_part = ota_part_idx as i8;
    454                 tmp_pinfo.ota_partitions[tmp_pinfo.ota_partitions_count] = (p_offset, p_size);
    455                 tmp_pinfo.ota_partitions_count += 1;
    456             } else if *p_type == 1 && *p_subtype == 0 {
    457                 //otadata
    458                 tmp_pinfo.otadata_offset = p_offset;
    459                 tmp_pinfo.otadata_size = p_size;
    460             }
    461         }
    462 
    463         Ok(tmp_pinfo)
    464     }
    465 }