esp_ota_nostd/
lib.rs

1#![no_std]
2
3mod crc;
4mod error;
5mod ota_data;
6mod ota_data_structs;
7pub mod partitions;
8
9use crate::error::{OtaInternalError, OtaUpdateError};
10use crate::ota_data::{read_ota_data, write_ota_data};
11use crate::ota_data_structs::{EspOTAData, EspOTAState};
12use core::sync::atomic::Ordering;
13use embedded_io_async::Read;
14use embedded_storage::nor_flash::NorFlash;
15use esp_partition_table::{AppPartitionType, NorFlashOpError, PartitionEntry, PartitionType};
16use portable_atomic::AtomicBool;
17use crate::partitions::find_partition_by_type;
18
19/// Size of a flash sector
20const SECTOR_SIZE: usize = 0x1000;
21
22static IS_UPDATING: AtomicBool = AtomicBool::new(false);
23
24/// Starts a new OTA update.
25/// - The `binary` is the data that should be written to the ota partition.
26/// - This function returns an error if multiple ota updates are attempted concurrently.
27/// - If the update was successful, the caller should reboot to activate the new firmware.
28/// - The `progress_fn` is called periodically with the total amount of bytes written so far.
29pub async fn ota_begin<S: NorFlash, R: Read>(
30    storage: &mut S,
31    mut binary: R,
32    mut progress_fn: impl FnMut(usize),
33) -> Result<(), OtaUpdateError<S, R::Error>> {
34    // Check if there is already an update happening
35    if IS_UPDATING.swap(true, Ordering::SeqCst) {
36        return Err(OtaUpdateError::AlreadyUpdating);
37    }
38
39    // Check if we're in a valid state
40    let ota_data = read_ota_data(storage)?;
41    if !ota_data.is_valid() {
42        return Err(OtaUpdateError::PendingVerify);
43    }
44
45    // Find partition to write to
46    let booted_seq = ota_data.seq;
47    let new_seq = ota_data.seq + 1;
48    let new_part = ((new_seq - 1) % 2) as u8;
49    let ota_app =
50        find_partition_by_type(storage, PartitionType::App(AppPartitionType::Ota(new_part)))?;
51    log::info!("Starting OTA update. Current sequence is {booted_seq}, updating to sequence {new_seq} (partition {}).", ota_app.name());
52
53    // Erase partition
54    storage
55        .erase(ota_app.offset, ota_app.offset + ota_app.size as u32)
56        .map_err(|e| OtaInternalError::NorFlashOpError(NorFlashOpError::StorageError(e)))?;
57
58    // Write ota data to flash
59    let mut data_written = 0;
60    loop {
61        let mut data_buffer = [0; SECTOR_SIZE];
62        let mut read_len = 0;
63
64        let mut is_done = false;
65        while read_len < SECTOR_SIZE {
66            let read = binary
67                .read(&mut data_buffer[read_len..])
68                .await
69                .map_err(|e| OtaUpdateError::ReadError(e))?;
70            if read == 0 {
71                is_done = true;
72                break;
73            }
74            read_len += read;
75        }
76
77        if data_written + read_len > ota_app.size {
78            return Err(OtaUpdateError::OutOfSpace);
79        }
80
81        storage
82            .write(
83                ota_app.offset + data_written as u32,
84                &data_buffer[0..read_len],
85            )
86            .map_err(|e| OtaInternalError::NorFlashOpError(NorFlashOpError::StorageError(e)))?;
87
88        data_written += read_len;
89        progress_fn(data_written);
90
91        if is_done {
92            break;
93        }
94    }
95
96    // Write new OTA data boot entry
97    let data = EspOTAData::new(new_seq, [0xFF; 20]);
98    write_ota_data(storage, data)?;
99
100    Ok(())
101}
102
103/// Mark OTA update as valid.
104/// Must be called after an OTA update and reboot to confirm the new firmware works.
105/// May also be called after a reboot without OTA update.
106/// If the system reboots before an OTA update is accepted
107/// the update will be marked as aborted and will not be booted again.
108pub fn ota_accept<S: NorFlash>(storage: &mut S) -> Result<(), OtaInternalError<S>> {
109    let mut ota_data = read_ota_data(storage)?;
110    match ota_data.state {
111        EspOTAState::PendingVerify => {
112            log::info!("Accepted pending OTA update");
113            ota_data.state = EspOTAState::Valid;
114            write_ota_data(storage, ota_data)?;
115        },
116        EspOTAState::New | EspOTAState::Undefined => {
117            log::warn!("Accepted OTA update from {:?} state", ota_data.state);
118            ota_data.state = EspOTAState::Valid;
119            write_ota_data(storage, ota_data)?;
120        },
121        EspOTAState::Invalid | EspOTAState::Aborted => {
122            log::warn!("Detected rollback that was not processed by bootloader, rolling back manually.");
123            ota_data.state = EspOTAState::Valid;
124            ota_data.seq -= 1;
125            write_ota_data(storage, ota_data)?;
126        }
127        EspOTAState::Valid => {},
128    }
129    Ok(())
130}
131
132/// Explicitly mark an OTA update as invalid.
133/// May be called after an OTA update failed, but is not required.
134/// If the system reboots before an OTA update is confirmed as valid
135/// the update will be marked as aborted and will not be booted again.
136pub fn ota_reject<S: NorFlash>(storage: &mut S) -> Result<(), OtaInternalError<S>> {
137    let mut ota_data = read_ota_data(storage)?;
138    match ota_data.state {
139        EspOTAState::PendingVerify => {
140            log::info!("Rejected pending OTA update");
141            ota_data.state = EspOTAState::Invalid;
142            write_ota_data(storage, ota_data)?;
143        }
144        EspOTAState::New | EspOTAState::Undefined => {
145            log::warn!("Rejected OTA update from {:?} state", ota_data.state);
146            ota_data.state = EspOTAState::Invalid;
147            write_ota_data(storage, ota_data)?;
148        }
149        EspOTAState::Valid => {
150            log::error!("Tried to reject OTA update that has already been accepted, ignoring request.");
151        }
152        EspOTAState::Invalid => {
153            log::warn!("Tried to reject OTA update that has already been rejected, ignoring request.");
154        }
155        EspOTAState::Aborted => {
156            log::warn!("Tried to reject OTA update from aborted state, ignoring request.");
157        }
158    }
159    Ok(())
160}
161
162// /// This function rolls back the app if the previous boot did not 
163// pub fn ota_rollback<S: NorFlash>(storage: &mut S) -> Result<(), OtaInternalError<S>> {
164//     
165// }
166
167/// Returns true if this OTA update has been accepted, i.e. with `ota_accept`
168pub fn ota_is_valid<S: NorFlash>(storage: &mut S) -> Result<bool, OtaInternalError<S>> {
169    Ok(read_ota_data(storage)?.is_valid())
170}
171
172/// Find the ota partition we're currently running on
173pub fn get_booted_partition<S: NorFlash>(storage: &mut S) -> Result<PartitionEntry, OtaInternalError<S>> {
174    let ota_data = read_ota_data(storage)?;
175    let booted_seq = ota_data.seq;
176    let new_part = ((booted_seq - 1) % 2) as u8;
177    find_partition_by_type(storage, PartitionType::App(AppPartitionType::Ota(new_part)))
178}