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