esp_hal_ota/
lib.rs

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]
7mod logging;
8
9use embedded_storage::{ReadStorage, Storage};
10pub use structs::*;
11
12pub mod crc32;
13pub mod helpers;
14pub mod mmu_hal;
15pub mod mmu_ll;
16pub mod structs;
17
18const PART_OFFSET: u32 = 0x8000;
19const PART_SIZE: u32 = 0xc00;
20const FIRST_OTA_PART_SUBTYPE: u8 = 0x10;
21const OTA_VERIFY_READ_SIZE: usize = 256;
22
23pub struct Ota<S>
24where
25    S: ReadStorage + Storage,
26{
27    flash: S,
28
29    progress: Option<FlashProgress>,
30    pinfo: PartitionInfo,
31}
32
33pub struct ProgressDetails {
34    pub remaining: u32,
35    pub last_crc: u32,
36}
37
38#[derive(Debug)]
39pub struct OtaConfiguratuion {
40    partition_table_offset: u32,
41}
42impl 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}
60impl Default for OtaConfiguratuion {
61    fn default() -> Self {
62        Self::new()
63    }
64}
65
66impl<S> Ota<S>
67where
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        self.flash
197            .write(progress.flash_offset, &chunk[..write_size])
198            .map_err(|_| OtaError::FlashRWError)?;
199
200        debug!(
201            "[OTA] Wrote {} bytes to ota partition at 0x{:x}",
202            write_size, progress.flash_offset
203        );
204
205        progress.last_crc = crc32::calc_crc32(&chunk[..write_size], progress.last_crc);
206
207        progress.flash_offset += write_size as u32;
208        progress.remaining -= write_size as u32;
209        Ok(progress.remaining == 0)
210    }
211
212    /// verify - should it read flash and check crc
213    /// rollback - if rollbacks enable (will set ota_state to ESP_OTA_IMG_NEW)
214    pub fn ota_flush(&mut self, verify: bool, rollback: bool) -> Result<()> {
215        if verify && !self.ota_verify()? {
216            error!("[OTA] Verify failed! Not flushing...");
217
218            return Err(OtaError::OtaVerifyError);
219        }
220
221        let progress = self.progress.clone().ok_or(OtaError::OtaNotStarted)?;
222
223        if progress.target_crc != progress.last_crc {
224            warn!("[OTA] Calculated crc: {}", progress.last_crc);
225            warn!("[OTA] Target crc: {}", progress.target_crc);
226            error!("[OTA] Crc check failed! Cant finish ota update...");
227
228            return Err(OtaError::WrongCRC);
229        }
230
231        let img_state = match rollback {
232            true => OtaImgState::EspOtaImgNew,
233            false => OtaImgState::EspOtaImgUndefined,
234        };
235
236        self.set_target_ota_boot_partition(progress.target_partition, img_state);
237        Ok(())
238    }
239
240    /// It reads written flash and checks crc
241    pub fn ota_verify(&mut self) -> Result<bool> {
242        let progress = self.progress.clone().ok_or(OtaError::OtaNotStarted)?;
243
244        let mut calc_crc = 0;
245        let mut bytes = [0; OTA_VERIFY_READ_SIZE];
246
247        let mut partition_offset = self.pinfo.ota_partitions[progress.target_partition].0;
248        let mut remaining = progress.flash_size;
249
250        loop {
251            let n = remaining.min(OTA_VERIFY_READ_SIZE as u32);
252            if n == 0 {
253                break;
254            }
255
256            _ = self.flash.read(partition_offset, &mut bytes[..n as usize]);
257            partition_offset += n;
258            remaining -= n;
259
260            calc_crc = crc32::calc_crc32(&bytes[..n as usize], calc_crc);
261        }
262
263        Ok(calc_crc == progress.target_crc)
264    }
265
266    /// Sets ota boot target partition
267    pub fn set_target_ota_boot_partition(&mut self, target: usize, state: OtaImgState) {
268        let (slot1, slot2) = self.get_ota_boot_entries();
269        let (seq1, seq2) = (slot1.seq, slot2.seq);
270
271        let mut target_seq = seq1.max(seq2);
272        while helpers::seq_to_part(target_seq, self.pinfo.ota_partitions_count) != target
273            || target_seq == 0
274        {
275            target_seq += 1;
276        }
277
278        let flash = &mut self.flash;
279        let target_crc = crc32::calc_crc32(&target_seq.to_le_bytes(), 0xFFFFFFFF);
280        if seq1 > seq2 {
281            let offset = self.pinfo.otadata_offset + (self.pinfo.otadata_size >> 1);
282
283            _ = flash.write(offset, &target_seq.to_le_bytes());
284            _ = flash.write(offset + 32 - 4 - 4, &(state as u32).to_le_bytes());
285            _ = flash.write(offset + 32 - 4, &target_crc.to_le_bytes());
286        } else {
287            _ = flash.write(self.pinfo.otadata_offset, &target_seq.to_le_bytes());
288            _ = flash.write(
289                self.pinfo.otadata_offset + 32 - 4 - 4,
290                &(state as u32).to_le_bytes(),
291            );
292            _ = flash.write(
293                self.pinfo.otadata_offset + 32 - 4,
294                &target_crc.to_le_bytes(),
295            );
296        }
297    }
298
299    pub fn set_ota_state(&mut self, slot: u8, state: OtaImgState) -> Result<()> {
300        let offset = match slot {
301            1 => self.pinfo.otadata_offset,
302            2 => self.pinfo.otadata_offset + (self.pinfo.otadata_size >> 1),
303            _ => {
304                error!("Use slot1 or slot2!");
305                return Err(OtaError::CannotFindCurrentBootPartition);
306            }
307        };
308
309        _ = self
310            .flash
311            .write(offset + 32 - 4 - 4, &(state as u32).to_le_bytes());
312
313        Ok(())
314    }
315
316    /// Returns current OTA boot sequences
317    ///
318    /// NOTE: if crc doesn't match, it returns 0 for that seq
319    /// NOTE: [Entry struct (link to .h file)](https://github.com/espressif/esp-idf/blob/master/components/bootloader_support/include/esp_flash_partitions.h#L66)
320    pub fn get_ota_boot_entries(&mut self) -> (EspOtaSelectEntry, EspOtaSelectEntry) {
321        let mut bytes = [0; 32];
322        _ = self.flash.read(self.pinfo.otadata_offset, &mut bytes);
323        let mut slot1: EspOtaSelectEntry =
324            unsafe { core::ptr::read(bytes.as_ptr() as *const EspOtaSelectEntry) };
325        slot1.check_crc();
326
327        _ = self.flash.read(
328            self.pinfo.otadata_offset + (self.pinfo.otadata_size >> 1),
329            &mut bytes,
330        );
331        let mut slot2: EspOtaSelectEntry =
332            unsafe { core::ptr::read(bytes.as_ptr() as *const EspOtaSelectEntry) };
333        slot2.check_crc();
334
335        (slot1, slot2)
336    }
337
338    /// Returns currently booted partition index
339    pub fn get_currently_booted_partition(&self) -> Option<usize> {
340        mmu_hal::esp_get_current_running_partition(self.get_partitions())
341    }
342
343    /// BUG: this wont work if user has ota partitions not starting from ota0
344    /// or if user skips some ota partitions: ota0, ota2, ota3...
345    pub fn get_next_ota_partition(&self) -> Option<usize> {
346        let curr_part = mmu_hal::esp_get_current_running_partition(self.get_partitions());
347        curr_part.map(|next_part| (next_part + 1) % self.pinfo.ota_partitions_count)
348    }
349
350    fn get_current_slot(&mut self) -> Result<(u8, EspOtaSelectEntry)> {
351        let (slot1, slot2) = self.get_ota_boot_entries();
352        let current_partition = self
353            .get_currently_booted_partition()
354            .ok_or(OtaError::CannotFindCurrentBootPartition)?;
355
356        let slot1_part = helpers::seq_to_part(slot1.seq, self.pinfo.ota_partitions_count);
357        let slot2_part = helpers::seq_to_part(slot2.seq, self.pinfo.ota_partitions_count);
358        if current_partition == slot1_part {
359            return Ok((1, slot1));
360        } else if current_partition == slot2_part {
361            return Ok((2, slot2));
362        }
363
364        Err(OtaError::CannotFindCurrentBootPartition)
365    }
366
367    pub fn get_ota_image_state(&mut self) -> Result<OtaImgState> {
368        let (slot1, slot2) = self.get_ota_boot_entries();
369        let current_partition = self
370            .get_currently_booted_partition()
371            .ok_or(OtaError::CannotFindCurrentBootPartition)?;
372
373        let slot1_part = helpers::seq_to_part(slot1.seq, self.pinfo.ota_partitions_count);
374        let slot2_part = helpers::seq_to_part(slot2.seq, self.pinfo.ota_partitions_count);
375        if current_partition == slot1_part {
376            return Ok(slot1.ota_state);
377        } else if current_partition == slot2_part {
378            return Ok(slot2.ota_state);
379        }
380
381        Err(OtaError::CannotFindCurrentBootPartition)
382    }
383
384    pub fn ota_mark_app_valid(&mut self) -> Result<()> {
385        let (current_slot_nmb, current_slot) = self.get_current_slot()?;
386        if current_slot.ota_state != OtaImgState::EspOtaImgValid {
387            self.set_ota_state(current_slot_nmb, OtaImgState::EspOtaImgValid)?;
388
389            info!("Marked current slot as valid!");
390        }
391
392        Ok(())
393    }
394
395    pub fn ota_mark_app_invalid_rollback(&mut self) -> Result<()> {
396        let (current_slot_nmb, current_slot) = self.get_current_slot()?;
397        if current_slot.ota_state != OtaImgState::EspOtaImgValid {
398            self.set_ota_state(current_slot_nmb, OtaImgState::EspOtaImgInvalid)?;
399
400            info!("Marked current slot as invalid!");
401        }
402
403        Ok(())
404    }
405
406    fn read_partitions(flash: &mut S, partition_table_offset: u32) -> Result<PartitionInfo> {
407        let mut tmp_pinfo = PartitionInfo {
408            ota_partitions: [(0, 0); 16],
409            ota_partitions_count: 0,
410            otadata_size: 0,
411            otadata_offset: 0,
412        };
413
414        let mut bytes = [0xFF; 32];
415        let mut last_ota_part: i8 = -1;
416        for read_offset in (0..PART_SIZE).step_by(32) {
417            _ = flash.read(partition_table_offset + read_offset, &mut bytes);
418            if bytes == [0xFF; 32] {
419                break;
420            }
421
422            let magic = &bytes[0..2];
423            if magic != [0xAA, 0x50] {
424                continue;
425            }
426
427            let p_type = &bytes[2];
428            let p_subtype = &bytes[3];
429            let p_offset = u32::from_le_bytes(bytes[4..8].try_into().unwrap());
430            let p_size = u32::from_le_bytes(bytes[8..12].try_into().unwrap());
431            //let p_name = core::str::from_utf8(&bytes[12..28]).unwrap();
432            //let p_flags = u32::from_le_bytes(bytes[28..32].try_into().unwrap());
433            //log::info!("{magic:?} {p_type} {p_subtype} {p_offset} {p_size} {p_name} {p_flags}");
434
435            if *p_type == 0 && *p_subtype >= FIRST_OTA_PART_SUBTYPE {
436                let ota_part_idx = *p_subtype - FIRST_OTA_PART_SUBTYPE;
437                if ota_part_idx as i8 - last_ota_part != 1 {
438                    return Err(OtaError::WrongOTAPArtitionOrder);
439                }
440
441                last_ota_part = ota_part_idx as i8;
442                tmp_pinfo.ota_partitions[tmp_pinfo.ota_partitions_count] = (p_offset, p_size);
443                tmp_pinfo.ota_partitions_count += 1;
444            } else if *p_type == 1 && *p_subtype == 0 {
445                //otadata
446                tmp_pinfo.otadata_offset = p_offset;
447                tmp_pinfo.otadata_size = p_size;
448            }
449        }
450
451        Ok(tmp_pinfo)
452    }
453}