-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathota.rs
More file actions
246 lines (206 loc) · 7.76 KB
/
Copy pathota.rs
File metadata and controls
246 lines (206 loc) · 7.76 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
use super::OsResult;
use crate::{
null_check, re_esp,
sysc::{rtcvar::RtcValue, OsError},
};
use esp_idf_svc::ota::{EspOta, EspOtaUpdate, FirmwareInfo, SlotState};
use pwmp_client::pwmp_msg::version::Version;
use std::ops::{Deref, DerefMut};
/// Maximum number of times the firmware can fail.
const MAX_FAILIURES: u8 = 3;
/// Number of times the current firmware has failed.
#[link_section = ".rtc_noinit"]
static FAILIURES: RtcValue<u8> = RtcValue::new();
/// Whether the last update has been reported back to the PWMP server.
#[link_section = ".rtc_noinit"]
static REPORTED: RtcValue<bool> = RtcValue::new();
/// A high-level Over-the-Air updates driver/wrapper.
///
/// Provides a simpler API for dealing with firmware updates.
pub struct Ota(EspOta);
/// A handle for a pending update.
pub struct OtaHandle<'h>(Option<EspOtaUpdate<'h>>);
#[allow(static_mut_refs)]
impl Ota {
/// Initialize a new driver.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails ([`EspOta::new`]).
pub fn new() -> OsResult<Self> {
Ok(Self(re_esp!(EspOta::new(), OtaInit)?))
}
/// Returns whether the currently running firmware is marked as [`Valid`](SlotState::Valid).
///
/// # Errors
/// Returns an error if the underlying OTA driver fails ([`EspOta::get_running_slot`]).
pub fn current_verified(&self) -> OsResult<bool> {
Ok(re_esp!(self.0.get_running_slot(), OtaSlot)?.state == SlotState::Valid)
}
/// Mark the current firmware as "reported", signaling that the PWMP server
/// was told to mark the firmware update as successfull or not.
#[allow(clippy::unused_self)]
pub fn mark_reported(&self) {
REPORTED.set(true);
}
/// Returns whether the last firmware update needs reporting to the PWMP server.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails.
pub fn report_needed(&self) -> OsResult<bool> {
// The current firmware might be verified, but it could be a previous version.
if self.current_verified()? && !self.rollback_detected()? {
log::debug!("Skipping report check on verified firmware");
return Ok(false);
}
Ok(!REPORTED.read())
}
/// Returns whether a firmware rollback has been detected.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails ([`EspOta::get_last_invalid_slot`]).
pub fn rollback_detected(&self) -> OsResult<bool> {
Ok(re_esp!(self.0.get_last_invalid_slot(), OtaSlot)?.is_some())
}
/// Initiates a new firmware update and returns a handle for it.
///
/// The returned handle can then be used to write the new firmware
/// to the flash memory, or to abort the update.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails ([`EspOta::initiate_update`]).
pub fn begin_update(&mut self) -> OsResult<OtaHandle<'_>> {
log::debug!("Initializing update");
Ok(OtaHandle(Some(re_esp!(self.0.initiate_update(), OtaInit)?)))
}
/// Returns whether a firmware rollback is needed.
///
/// If the currently running firmware failed more than [`MAX_FAILIURES`]
/// times, this will return `true`
///
/// # Errors
/// Returns an error if the underlying OTA driver fails.
pub fn rollback_if_needed(&mut self) -> OsResult<()> {
if self.current_verified()? {
return Ok(());
}
if FAILIURES.read() >= MAX_FAILIURES {
log::info!("Rolling back to previous version");
self.0.mark_running_slot_invalid_and_reboot();
}
Ok(())
}
/// Increment the number of failiures for this firmware.
///
/// This should be called before the system goes to sleep. It's safe to call
/// even if the current firmware is marked as [`Valid`](SlotState::Valid), in which case
/// nothing will be done.
///
/// # Errors
/// Fails if [`current_verified`](Self::current_verified) returns an error.
pub fn inc_failiures(&self) -> OsResult<()> {
// if the current firmware is verified, we don't need to increment anything
if self.current_verified()? {
return Ok(());
}
let counter = FAILIURES.read() + 1;
log::warn!("Firmware has failed {counter}/{MAX_FAILIURES} times");
FAILIURES.set(counter);
Ok(())
}
/// Returns the version of the currently running firmware.
///
/// *This method should only be used in debug builds.*
///
/// If the version number is not available, [`Option::None`] is returned.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails.
pub fn current_version(&self) -> OsResult<Version> {
let slot = crate::re_esp!(self.0.get_running_slot(), OtaSlot)?;
let Some(info) = slot.firmware else {
return Err(OsError::MissingPartitionMetadata);
};
let Some(version) = Self::parse_info_version(&info) else {
log::error!("Current firmware has an invalid version string");
return Err(OsError::IllegalFirmwareVersion);
};
Ok(version)
}
/// Returns the version of the previous firmware from the flash.
///
/// If the version number is not available, [`Option::None`] is returned.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails.
pub fn previous_version(&self) -> OsResult<Version> {
let slot = crate::re_esp!(self.0.get_running_slot(), OtaSlot)?;
let Some(info) = slot.firmware else {
return Err(OsError::MissingPartitionMetadata);
};
let Some(version) = Self::parse_info_version(&info) else {
log::error!("Previous firmware has an invalid version string");
return Err(OsError::IllegalFirmwareVersion);
};
Ok(version)
}
/// Parses the raw version string of a firmware on flash.
///
/// If the parsing fails, [`Option::None`] is returned.
fn parse_info_version(info: &FirmwareInfo) -> Option<Version> {
/*
* If ESP-IDF uses `git describe` to get a version string, it will
* look like this: `v2.0.0-rc3-8-g1a1ba69`.
*
* This method assumes the above format.
*/
// Index of the first `-`
let dash_index = info.version.find('-')?;
// Cut the version string
let slice = &info.version[1..dash_index];
Version::parse(slice)
}
}
impl OtaHandle<'_> {
/// Aborts the firmware update.
///
/// # Errors
/// Returns an error if the underlying OTA driver fails.
pub fn cancel(mut self) -> OsResult<()> {
let inner = null_check!(self.0.take());
re_esp!(inner.abort(), OtaAbort)?;
Ok(())
}
}
impl<'h> Deref for OtaHandle<'h> {
type Target = EspOtaUpdate<'h>;
fn deref(&self) -> &Self::Target {
self.0
.as_ref()
.ok_or(OsError::UnexpectedNull)
.expect("Unexpected NULL")
}
}
impl DerefMut for OtaHandle<'_> {
fn deref_mut(&mut self) -> &mut Self::Target {
self.0
.as_mut()
.ok_or(OsError::UnexpectedNull)
.expect("Unexpected NULL")
}
}
#[allow(static_mut_refs)]
impl Drop for OtaHandle<'_> {
fn drop(&mut self) {
let Some(mut handle) = self.0.take() else {
return;
};
log::error!("Finalizing update");
handle.flush().expect("Failed to flush OTA write");
handle.complete().expect("Failed to complete update");
FAILIURES.set(0);
REPORTED.set(false);
// Null-safety of `self.0`:
// The handle can never be used after this drop.
// Therefore, no calls to `unwrap_unchecked()` can be made in the Deref implementations, and no UB can occur.
}
}