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 pub const fn new() -> Self {
47 Self {
48 partition_table_offset: PART_OFFSET,
49 }
50 }
51
52 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 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 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 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 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 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 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 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 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 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 pub fn get_currently_booted_partition(&self) -> Option<usize> {
340 mmu_hal::esp_get_current_running_partition(self.get_partitions())
341 }
342
343 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 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 tmp_pinfo.otadata_offset = p_offset;
447 tmp_pinfo.otadata_size = p_size;
448 }
449 }
450
451 Ok(tmp_pinfo)
452 }
453}