esp_bootloader_esp_idf/
ota.rs

1//! # Over The Air Updates (OTA)
2//!
3//! ## Overview
4//! The OTA update mechanism allows a device to update itself based on data
5//! received while the normal firmware is running (for example, over Wi-Fi,
6//! Bluetooth or Ethernet).
7//!
8//! OTA requires configuring the Partition Tables of the device with at least
9//! two OTA app slot partitions (i.e., ota_0 and ota_1) and an OTA Data
10//! Partition.
11//!
12//! The OTA operation functions write a new app firmware image to whichever OTA
13//! app slot that is currently not selected for booting. Once the image is
14//! verified, the OTA Data partition is updated to specify that this image
15//! should be used for the next boot.
16//!
17//! Note: The prebuilt bootloaders provided by `espflash` _might not_ include
18//! OTA support. In that case you need to build the bootloader yourself.
19//!
20//! The general procedure to change the active slot
21//! - read the partition table [crate::partitions::read_partition_table]
22//! - find the Data/Ota partition
23//! - initialize [Ota]
24//! - read the current slot, change the current slot
25//!
26//! For more details see <https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html>
27use embedded_storage::{ReadStorage, Storage};
28
29use crate::partitions::FlashRegion;
30
31// IN THEORY the partition table format allows up to 16 OTA-app partitions but
32// in reality the ESP-IDF bootloader only supports exactly two.
33//
34// See https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/ota.html
35// See https://github.com/espressif/esp-idf/blob/1c468f68259065ef51afd114605d9122f13d9d72/components/bootloader_support/src/bootloader_utility.c#L91-L116
36const SLOT0_DATA_OFFSET: u32 = 0x0000;
37const SLOT1_DATA_OFFSET: u32 = 0x1000;
38
39/// Representation of the current OTA slot.
40#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, strum::FromRepr)]
41#[cfg_attr(feature = "defmt", derive(defmt::Format))]
42pub enum Slot {
43    /// If there is a `firmware` app-partition it's used. Otherwise OTA-0
44    None,
45    /// OTA-0
46    Slot0,
47    /// OTA-1
48    Slot1,
49}
50
51impl Slot {
52    /// The slot represented as `usize`
53    pub fn number(&self) -> usize {
54        match self {
55            Slot::None => 0,
56            Slot::Slot0 => 0,
57            Slot::Slot1 => 1,
58        }
59    }
60
61    /// The next logical OTA slot
62    pub fn next(&self) -> Slot {
63        match self {
64            Slot::None => Slot::Slot0,
65            Slot::Slot0 => Slot::Slot1,
66            Slot::Slot1 => Slot::Slot0,
67        }
68    }
69
70    fn offset(&self) -> u32 {
71        match self {
72            Slot::None => SLOT0_DATA_OFFSET,
73            Slot::Slot0 => SLOT0_DATA_OFFSET,
74            Slot::Slot1 => SLOT1_DATA_OFFSET,
75        }
76    }
77}
78
79/// OTA image states for checking operability of the app.
80#[derive(Debug, PartialEq, Eq, Clone, Copy, Default, Hash, strum::FromRepr)]
81#[cfg_attr(feature = "defmt", derive(defmt::Format))]
82#[repr(u32)]
83pub enum OtaImageState {
84    /// Monitor the first boot. The bootloader will change this to
85    /// `PendingVerify` if auto-rollback is enabled.
86    ///
87    /// You want to set this state after activating a newly installed update.
88    New           = 0x0,
89
90    /// Bootloader changes [OtaImageState::New] to
91    /// [OtaImageState::PendingVerify] to indicate the app should confirm the
92    /// image as working.
93    PendingVerify = 0x1,
94
95    /// Set by the firmware once it's found to be working. The bootloader will
96    /// consider this Slot as working and continue to use it.
97    Valid         = 0x2,
98
99    /// Set by the firmware once it's found to be non-working.
100    ///
101    /// The bootloader will consider this Slot as non-working and not try to
102    /// boot it further.
103    Invalid       = 0x3,
104
105    /// The bootloader will change the state to [OtaImageState::Aborted] if the
106    /// application didn't change [OtaImageState::PendingVerify]
107    /// to either [OtaImageState::Valid] or [OtaImageState::Invalid].
108    Aborted       = 0x4,
109
110    /// Undefined. The bootloader won't make any assumptions about the working
111    /// state of this slot.
112    #[default]
113    Undefined     = 0xFFFFFFFF,
114}
115
116impl TryFrom<u32> for OtaImageState {
117    type Error = crate::partitions::Error;
118
119    fn try_from(value: u32) -> Result<Self, Self::Error> {
120        OtaImageState::from_repr(value).ok_or(crate::partitions::Error::Invalid)
121    }
122}
123
124/// OTA selection entry structure (two copies in the OTA data partition).
125/// Size of 32 bytes is friendly to flash encryption.
126#[derive(Debug, Clone, Copy, Default)]
127#[cfg_attr(feature = "defmt", derive(defmt::Format))]
128#[repr(C)]
129struct OtaSelectEntry {
130    /// OTA sequence number.
131    pub ota_seq: u32,
132    /// Sequence label (unused in the bootloader).
133    pub seq_label: [u8; 20],
134    /// OTA image state.
135    pub ota_state: OtaImageState,
136    /// CRC32 of the `ota_seq` field only.
137    pub crc: u32,
138}
139
140impl OtaSelectEntry {
141    fn as_bytes_mut(&mut self) -> &mut [u8; 0x20] {
142        debug_assert!(core::mem::size_of::<Self>() == 32);
143        unwrap!(
144            unsafe { core::slice::from_raw_parts_mut(self as *mut _ as *mut u8, 0x20) }.try_into()
145        )
146    }
147}
148
149/// This is used to manipulate the OTA-data partition.
150///
151/// This will ever only deal with OTA-0 and OTA-1 since these two slots are
152/// supported by the ESP-IDF bootloader.
153#[derive(Debug)]
154#[cfg_attr(feature = "defmt", derive(defmt::Format))]
155pub struct Ota<'a, F>
156where
157    F: embedded_storage::Storage,
158{
159    flash: &'a mut FlashRegion<'a, F>,
160}
161
162impl<'a, F> Ota<'a, F>
163where
164    F: embedded_storage::Storage,
165{
166    /// Create a [Ota] instance from the given [FlashRegion]
167    ///
168    /// # Errors
169    /// A [crate::partitions::Error::InvalidPartition] if the given flash region
170    /// doesn't represent a Data/Ota partition or the size is unexpected
171    pub fn new(flash: &'a mut FlashRegion<'a, F>) -> Result<Ota<'a, F>, crate::partitions::Error> {
172        if flash.capacity() != 0x2000
173            || flash.raw.partition_type()
174                != crate::partitions::PartitionType::Data(
175                    crate::partitions::DataPartitionSubType::Ota,
176                )
177        {
178            return Err(crate::partitions::Error::InvalidPartition {
179                expected_size: 0x2000,
180                expected_type: crate::partitions::PartitionType::Data(
181                    crate::partitions::DataPartitionSubType::Ota,
182                ),
183            });
184        }
185
186        Ok(Ota { flash })
187    }
188
189    /// Returns the currently active OTA-slot.
190    pub fn current_slot(&mut self) -> Result<Slot, crate::partitions::Error> {
191        let (seq0, seq1) = self.get_slot_seq()?;
192
193        let slot = if seq0 == 0xffffffff && seq1 == 0xffffffff {
194            Slot::None
195        } else if seq0 == 0xffffffff {
196            Slot::Slot1
197        } else if seq1 == 0xffffffff || seq0 > seq1 {
198            Slot::Slot0
199        } else {
200            Slot::Slot1
201        };
202
203        Ok(slot)
204    }
205
206    fn get_slot_seq(&mut self) -> Result<(u32, u32), crate::partitions::Error> {
207        let mut buffer1 = OtaSelectEntry::default();
208        let mut buffer2 = OtaSelectEntry::default();
209        self.flash.read(SLOT0_DATA_OFFSET, buffer1.as_bytes_mut())?;
210        self.flash.read(SLOT1_DATA_OFFSET, buffer2.as_bytes_mut())?;
211        let seq0 = buffer1.ota_seq;
212        let seq1 = buffer2.ota_seq;
213        Ok((seq0, seq1))
214    }
215
216    /// Sets the currently active OTA-slot.
217    ///
218    /// Passing [Slot::None] will reset the OTA-data
219    pub fn set_current_slot(&mut self, slot: Slot) -> Result<(), crate::partitions::Error> {
220        if slot == Slot::None {
221            self.flash.write(SLOT0_DATA_OFFSET, &[0xffu8; 0x20])?;
222            self.flash.write(SLOT1_DATA_OFFSET, &[0xffu8; 0x20])?;
223            return Ok(());
224        }
225
226        let (seq0, seq1) = self.get_slot_seq()?;
227
228        let new_seq = {
229            if seq0 == 0xffffffff && seq1 == 0xffffffff {
230                1
231            } else if seq0 == 0xffffffff {
232                seq1 + 1
233            } else if seq1 == 0xffffffff {
234                seq0 + 1
235            } else {
236                u32::max(seq0, seq1) + 1
237            }
238        };
239
240        let crc = crate::crypto::Crc32::new();
241        let checksum = crc.crc(&new_seq.to_le_bytes());
242
243        let mut buffer = OtaSelectEntry::default();
244        self.flash.read(slot.offset(), buffer.as_bytes_mut())?;
245        buffer.ota_seq = new_seq;
246        buffer.crc = checksum;
247        self.flash.write(slot.offset(), buffer.as_bytes_mut())?;
248
249        Ok(())
250    }
251
252    /// Set the [OtaImageState] of the currently selected slot.
253    ///
254    /// # Errors
255    /// A [crate::partitions::Error::InvalidState] if the currently selected
256    /// slot is [Slot::None]
257    pub fn set_current_ota_state(
258        &mut self,
259        state: OtaImageState,
260    ) -> Result<(), crate::partitions::Error> {
261        match self.current_slot()? {
262            Slot::None => Err(crate::partitions::Error::InvalidState),
263            _ => {
264                let offset = self.current_slot()?.offset();
265                let mut buffer = OtaSelectEntry::default();
266                self.flash.read(offset, buffer.as_bytes_mut())?;
267                buffer.ota_state = state;
268                self.flash.write(offset, buffer.as_bytes_mut())?;
269                Ok(())
270            }
271        }
272    }
273
274    /// Get the [OtaImageState] of the currently selected slot.
275    ///
276    /// # Errors
277    /// A [crate::partitions::Error::InvalidState] if the currently selected
278    /// slot is [Slot::None]
279    pub fn current_ota_state(&mut self) -> Result<OtaImageState, crate::partitions::Error> {
280        match self.current_slot()? {
281            Slot::None => Err(crate::partitions::Error::InvalidState),
282            _ => {
283                let offset = self.current_slot()?.offset();
284                let mut buffer = OtaSelectEntry::default();
285                self.flash.read(offset, buffer.as_bytes_mut())?;
286                Ok(buffer.ota_state)
287            }
288        }
289    }
290}
291
292#[cfg(test)]
293mod tests {
294    use super::*;
295    use crate::partitions::PartitionEntry;
296
297    struct MockFlash {
298        data: [u8; 0x2000],
299    }
300
301    impl embedded_storage::Storage for MockFlash {
302        fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
303            self.data[offset as usize..][..bytes.len()].copy_from_slice(bytes);
304            Ok(())
305        }
306    }
307
308    impl embedded_storage::ReadStorage for MockFlash {
309        type Error = crate::partitions::Error;
310        fn read(&mut self, offset: u32, buffer: &mut [u8]) -> Result<(), Self::Error> {
311            let l = buffer.len();
312            buffer[..l].copy_from_slice(&self.data[offset as usize..][..l]);
313            Ok(())
314        }
315
316        fn capacity(&self) -> usize {
317            unimplemented!()
318        }
319    }
320
321    const PARTITION_RAW: [u8; 32] = [
322        0xaa, 0x50, // MAGIC
323        1,    // TYPE = DATA
324        0,    // SUBTYPE = OTA
325        0, 0, 0, 0, // OFFSET
326        0, 0x20, 0, 0, // LEN (0x2000)
327        0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // LABEL
328        0, 0, 0, 0, // FLAGS
329    ];
330
331    const SLOT_INITIAL: &[u8] = &[
332        255u8, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
333        255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
334    ];
335
336    const SLOT_COUNT_1_UNDEFINED: &[u8] = &[
337        1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
338        255, 255, 255, 255, 255, 255, 255, 255, 255, 154, 152, 67, 71,
339    ];
340
341    const SLOT_COUNT_1_VALID: &[u8] = &[
342        1u8, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
343        255, 255, 255, 255, 255, 2, 0, 0, 0, 154, 152, 67, 71,
344    ];
345
346    const SLOT_COUNT_2_NEW: &[u8] = &[
347        2, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
348        255, 255, 255, 255, 0, 0, 0, 0, 116, 55, 246, 85,
349    ];
350
351    const SLOT_COUNT_3_PENDING: &[u8] = &[
352        3, 0, 0, 0, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
353        255, 255, 255, 255, 1, 0, 0, 0, 17, 80, 74, 237,
354    ];
355
356    #[test]
357    fn test_initial_state_and_next_slot() {
358        let mut binary = PARTITION_RAW;
359
360        let mock_entry = PartitionEntry {
361            binary: &mut binary,
362        };
363
364        let mut mock_flash = MockFlash {
365            data: [0xff; 0x2000],
366        };
367
368        let mut mock_region = FlashRegion {
369            raw: &mock_entry,
370            flash: &mut mock_flash,
371        };
372
373        let mut sut = Ota::new(&mut mock_region).unwrap();
374        assert_eq!(sut.current_slot().unwrap(), Slot::None);
375        assert_eq!(
376            sut.current_ota_state(),
377            Err(crate::partitions::Error::InvalidState)
378        );
379        assert_eq!(
380            sut.set_current_ota_state(OtaImageState::New),
381            Err(crate::partitions::Error::InvalidState)
382        );
383        assert_eq!(
384            sut.current_ota_state(),
385            Err(crate::partitions::Error::InvalidState)
386        );
387
388        sut.set_current_slot(Slot::Slot0).unwrap();
389        assert_eq!(sut.current_slot().unwrap(), Slot::Slot0);
390        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Undefined));
391
392        assert_eq!(SLOT_COUNT_1_UNDEFINED, &mock_flash.data[0x0000..][..0x20],);
393        assert_eq!(SLOT_INITIAL, &mock_flash.data[0x1000..][..0x20],);
394    }
395
396    #[test]
397    fn test_slot0_valid_next_slot() {
398        let mut binary = PARTITION_RAW;
399
400        let mock_entry = PartitionEntry {
401            binary: &mut binary,
402        };
403
404        let mut mock_flash = MockFlash {
405            data: [0xff; 0x2000],
406        };
407
408        mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID);
409        mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_INITIAL);
410
411        let mut mock_region = FlashRegion {
412            raw: &mock_entry,
413            flash: &mut mock_flash,
414        };
415
416        let mut sut = Ota::new(&mut mock_region).unwrap();
417        assert_eq!(sut.current_slot().unwrap(), Slot::Slot0);
418        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::Valid));
419
420        sut.set_current_slot(Slot::Slot1).unwrap();
421        sut.set_current_ota_state(OtaImageState::New).unwrap();
422        assert_eq!(sut.current_slot().unwrap(), Slot::Slot1);
423        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
424
425        assert_eq!(SLOT_COUNT_1_VALID, &mock_flash.data[0x0000..][..0x20],);
426        assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],);
427    }
428
429    #[test]
430    fn test_slot1_new_next_slot() {
431        let mut binary = PARTITION_RAW;
432
433        let mock_entry = PartitionEntry {
434            binary: &mut binary,
435        };
436
437        let mut mock_flash = MockFlash {
438            data: [0xff; 0x2000],
439        };
440
441        mock_flash.data[0x0000..][..0x20].copy_from_slice(SLOT_COUNT_1_VALID);
442        mock_flash.data[0x1000..][..0x20].copy_from_slice(SLOT_COUNT_2_NEW);
443
444        let mut mock_region = FlashRegion {
445            raw: &mock_entry,
446            flash: &mut mock_flash,
447        };
448
449        let mut sut = Ota::new(&mut mock_region).unwrap();
450        assert_eq!(sut.current_slot().unwrap(), Slot::Slot1);
451        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::New));
452
453        sut.set_current_slot(Slot::Slot0).unwrap();
454        sut.set_current_ota_state(OtaImageState::PendingVerify)
455            .unwrap();
456        assert_eq!(sut.current_slot().unwrap(), Slot::Slot0);
457        assert_eq!(sut.current_ota_state(), Ok(OtaImageState::PendingVerify));
458
459        assert_eq!(SLOT_COUNT_3_PENDING, &mock_flash.data[0x0000..][..0x20],);
460        assert_eq!(SLOT_COUNT_2_NEW, &mock_flash.data[0x1000..][..0x20],);
461    }
462
463    #[test]
464    fn test_ota_slot_next() {
465        assert_eq!(Slot::None.next(), Slot::Slot0);
466        assert_eq!(Slot::Slot0.next(), Slot::Slot1);
467        assert_eq!(Slot::Slot1.next(), Slot::Slot0);
468    }
469}