diff options
| author | Dario Nieuwenhuis <[email protected]> | 2024-01-11 18:55:59 +0100 |
|---|---|---|
| committer | Dario Nieuwenhuis <[email protected]> | 2024-01-11 18:55:59 +0100 |
| commit | e0775fbc8ab1ecc83bce42fe6e11accf481bc9e1 (patch) | |
| tree | 3d5119500fbb8627829e54e6bc999c3689ab4a0f /embassy-boot/src | |
| parent | b452a6bcf6858893a85882614e2dcde5a3405748 (diff) | |
Flatten embassy-boot dir tree
Diffstat (limited to 'embassy-boot/src')
| -rw-r--r-- | embassy-boot/src/boot_loader.rs | 411 | ||||
| -rw-r--r-- | embassy-boot/src/digest_adapters/ed25519_dalek.rs | 30 | ||||
| -rw-r--r-- | embassy-boot/src/digest_adapters/mod.rs | 5 | ||||
| -rw-r--r-- | embassy-boot/src/digest_adapters/salty.rs | 29 | ||||
| -rw-r--r-- | embassy-boot/src/firmware_updater/asynch.rs | 329 | ||||
| -rw-r--r-- | embassy-boot/src/firmware_updater/blocking.rs | 340 | ||||
| -rw-r--r-- | embassy-boot/src/firmware_updater/mod.rs | 49 | ||||
| -rw-r--r-- | embassy-boot/src/fmt.rs | 258 | ||||
| -rw-r--r-- | embassy-boot/src/lib.rs | 323 | ||||
| -rw-r--r-- | embassy-boot/src/mem_flash.rs | 170 | ||||
| -rw-r--r-- | embassy-boot/src/test_flash/asynch.rs | 64 | ||||
| -rw-r--r-- | embassy-boot/src/test_flash/blocking.rs | 68 | ||||
| -rw-r--r-- | embassy-boot/src/test_flash/mod.rs | 5 |
13 files changed, 2081 insertions, 0 deletions
diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs new file mode 100644 index 000000000..e568001bc --- /dev/null +++ b/embassy-boot/src/boot_loader.rs | |||
| @@ -0,0 +1,411 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | |||
| 3 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 4 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 5 | use embassy_sync::blocking_mutex::Mutex; | ||
| 6 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | ||
| 7 | |||
| 8 | use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 9 | |||
| 10 | /// Errors returned by bootloader | ||
| 11 | #[derive(PartialEq, Eq, Debug)] | ||
| 12 | pub enum BootError { | ||
| 13 | /// Error from flash. | ||
| 14 | Flash(NorFlashErrorKind), | ||
| 15 | /// Invalid bootloader magic | ||
| 16 | BadMagic, | ||
| 17 | } | ||
| 18 | |||
| 19 | #[cfg(feature = "defmt")] | ||
| 20 | impl defmt::Format for BootError { | ||
| 21 | fn format(&self, fmt: defmt::Formatter) { | ||
| 22 | match self { | ||
| 23 | BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), | ||
| 24 | BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | impl<E> From<E> for BootError | ||
| 30 | where | ||
| 31 | E: NorFlashError, | ||
| 32 | { | ||
| 33 | fn from(error: E) -> Self { | ||
| 34 | BootError::Flash(error.kind()) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Bootloader flash configuration holding the three flashes used by the bootloader | ||
| 39 | /// | ||
| 40 | /// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. | ||
| 41 | /// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition | ||
| 42 | /// the provided flash according to symbols defined in the linkerfile. | ||
| 43 | pub struct BootLoaderConfig<ACTIVE, DFU, STATE> { | ||
| 44 | /// Flash type used for the active partition - the partition which will be booted from. | ||
| 45 | pub active: ACTIVE, | ||
| 46 | /// Flash type used for the dfu partition - the partition which will be swapped in when requested. | ||
| 47 | pub dfu: DFU, | ||
| 48 | /// Flash type used for the state partition. | ||
| 49 | pub state: STATE, | ||
| 50 | } | ||
| 51 | |||
| 52 | impl<'a, FLASH: NorFlash> | ||
| 53 | BootLoaderConfig< | ||
| 54 | BlockingPartition<'a, NoopRawMutex, FLASH>, | ||
| 55 | BlockingPartition<'a, NoopRawMutex, FLASH>, | ||
| 56 | BlockingPartition<'a, NoopRawMutex, FLASH>, | ||
| 57 | > | ||
| 58 | { | ||
| 59 | /// Create a bootloader config from the flash and address symbols defined in the linkerfile | ||
| 60 | // #[cfg(target_os = "none")] | ||
| 61 | pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { | ||
| 62 | extern "C" { | ||
| 63 | static __bootloader_state_start: u32; | ||
| 64 | static __bootloader_state_end: u32; | ||
| 65 | static __bootloader_active_start: u32; | ||
| 66 | static __bootloader_active_end: u32; | ||
| 67 | static __bootloader_dfu_start: u32; | ||
| 68 | static __bootloader_dfu_end: u32; | ||
| 69 | } | ||
| 70 | |||
| 71 | let active = unsafe { | ||
| 72 | let start = &__bootloader_active_start as *const u32 as u32; | ||
| 73 | let end = &__bootloader_active_end as *const u32 as u32; | ||
| 74 | trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); | ||
| 75 | |||
| 76 | BlockingPartition::new(flash, start, end - start) | ||
| 77 | }; | ||
| 78 | let dfu = unsafe { | ||
| 79 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 80 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 81 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 82 | |||
| 83 | BlockingPartition::new(flash, start, end - start) | ||
| 84 | }; | ||
| 85 | let state = unsafe { | ||
| 86 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 87 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 88 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 89 | |||
| 90 | BlockingPartition::new(flash, start, end - start) | ||
| 91 | }; | ||
| 92 | |||
| 93 | Self { active, dfu, state } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | /// BootLoader works with any flash implementing embedded_storage. | ||
| 98 | pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> { | ||
| 99 | active: ACTIVE, | ||
| 100 | dfu: DFU, | ||
| 101 | /// The state partition has the following format: | ||
| 102 | /// All ranges are in multiples of WRITE_SIZE bytes. | ||
| 103 | /// | Range | Description | | ||
| 104 | /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | | ||
| 105 | /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | | ||
| 106 | /// | 2..2 + N | Progress index used while swapping or reverting | ||
| 107 | state: STATE, | ||
| 108 | } | ||
| 109 | |||
| 110 | impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> { | ||
| 111 | /// Get the page size which is the "unit of operation" within the bootloader. | ||
| 112 | const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { | ||
| 113 | ACTIVE::ERASE_SIZE as u32 | ||
| 114 | } else { | ||
| 115 | DFU::ERASE_SIZE as u32 | ||
| 116 | }; | ||
| 117 | |||
| 118 | /// Create a new instance of a bootloader with the flash partitions. | ||
| 119 | /// | ||
| 120 | /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. | ||
| 121 | /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. | ||
| 122 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 123 | Self { | ||
| 124 | active: config.active, | ||
| 125 | dfu: config.dfu, | ||
| 126 | state: config.state, | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Perform necessary boot preparations like swapping images. | ||
| 131 | /// | ||
| 132 | /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap | ||
| 133 | /// algorithm to work correctly. | ||
| 134 | /// | ||
| 135 | /// The provided aligned_buf argument must satisfy any alignment requirements | ||
| 136 | /// given by the partition flashes. All flash operations will use this buffer. | ||
| 137 | /// | ||
| 138 | /// ## SWAPPING | ||
| 139 | /// | ||
| 140 | /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. | ||
| 141 | /// The swap index contains the copy progress, as to allow continuation of the copy process on | ||
| 142 | /// power failure. The index counter is represented within 1 or more pages (depending on total | ||
| 143 | /// flash size), where a page X is considered swapped if index at location (`X + WRITE_SIZE`) | ||
| 144 | /// contains a zero value. This ensures that index updates can be performed atomically and | ||
| 145 | /// avoid a situation where the wrong index value is set (page write size is "atomic"). | ||
| 146 | /// | ||
| 147 | /// | ||
| 148 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 149 | /// |-----------|------------|--------|--------|--------|--------| | ||
| 150 | /// | Active | 0 | 1 | 2 | 3 | - | | ||
| 151 | /// | DFU | 0 | 3 | 2 | 1 | X | | ||
| 152 | /// | ||
| 153 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is | ||
| 154 | /// as follows: | ||
| 155 | /// | ||
| 156 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 157 | /// |-----------|------------|--------|--------|--------|--------| | ||
| 158 | /// | Active | 1 | 1 | 2 | 1 | - | | ||
| 159 | /// | DFU | 1 | 3 | 2 | 1 | 3 | | ||
| 160 | /// | ||
| 161 | /// The next iteration performs the same steps | ||
| 162 | /// | ||
| 163 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 164 | /// |-----------|------------|--------|--------|--------|--------| | ||
| 165 | /// | Active | 2 | 1 | 2 | 1 | - | | ||
| 166 | /// | DFU | 2 | 3 | 2 | 2 | 3 | | ||
| 167 | /// | ||
| 168 | /// And again until we're done | ||
| 169 | /// | ||
| 170 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 171 | /// |-----------|------------|--------|--------|--------|--------| | ||
| 172 | /// | Active | 3 | 3 | 2 | 1 | - | | ||
| 173 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 174 | /// | ||
| 175 | /// ## REVERTING | ||
| 176 | /// | ||
| 177 | /// The reverting algorithm uses the swap index to discover that images were swapped, but that | ||
| 178 | /// the application failed to mark the boot successful. In this case, the revert algorithm will | ||
| 179 | /// run. | ||
| 180 | /// | ||
| 181 | /// The revert index is located separately from the swap index, to ensure that revert can continue | ||
| 182 | /// on power failure. | ||
| 183 | /// | ||
| 184 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. | ||
| 185 | /// | ||
| 186 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 187 | /// |-----------|--------------|--------|--------|--------|--------| | ||
| 188 | /// | Active | 3 | 1 | 2 | 1 | - | | ||
| 189 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 190 | /// | ||
| 191 | /// | ||
| 192 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 193 | /// |-----------|--------------|--------|--------|--------|--------| | ||
| 194 | /// | Active | 3 | 1 | 2 | 1 | - | | ||
| 195 | /// | DFU | 3 | 3 | 2 | 2 | 3 | | ||
| 196 | /// | ||
| 197 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 198 | /// |-----------|--------------|--------|--------|--------|--------| | ||
| 199 | /// | Active | 3 | 1 | 2 | 3 | - | | ||
| 200 | /// | DFU | 3 | 3 | 2 | 1 | 3 | | ||
| 201 | /// | ||
| 202 | pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||
| 203 | // Ensure we have enough progress pages to store copy progress | ||
| 204 | assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); | ||
| 205 | assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); | ||
| 206 | assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); | ||
| 207 | assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); | ||
| 208 | assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); | ||
| 209 | assert!(aligned_buf.len() >= STATE::WRITE_SIZE); | ||
| 210 | assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); | ||
| 211 | assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); | ||
| 212 | |||
| 213 | // Ensure our partitions are able to handle boot operations | ||
| 214 | assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); | ||
| 215 | |||
| 216 | // Copy contents from partition N to active | ||
| 217 | let state = self.read_state(aligned_buf)?; | ||
| 218 | if state == State::Swap { | ||
| 219 | // | ||
| 220 | // Check if we already swapped. If we're in the swap state, this means we should revert | ||
| 221 | // since the app has failed to mark boot as successful | ||
| 222 | // | ||
| 223 | if !self.is_swapped(aligned_buf)? { | ||
| 224 | trace!("Swapping"); | ||
| 225 | self.swap(aligned_buf)?; | ||
| 226 | trace!("Swapping done"); | ||
| 227 | } else { | ||
| 228 | trace!("Reverting"); | ||
| 229 | self.revert(aligned_buf)?; | ||
| 230 | |||
| 231 | let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||
| 232 | |||
| 233 | // Invalidate progress | ||
| 234 | state_word.fill(!STATE_ERASE_VALUE); | ||
| 235 | self.state.write(STATE::WRITE_SIZE as u32, state_word)?; | ||
| 236 | |||
| 237 | // Clear magic and progress | ||
| 238 | self.state.erase(0, self.state.capacity() as u32)?; | ||
| 239 | |||
| 240 | // Set magic | ||
| 241 | state_word.fill(BOOT_MAGIC); | ||
| 242 | self.state.write(0, state_word)?; | ||
| 243 | } | ||
| 244 | } | ||
| 245 | Ok(state) | ||
| 246 | } | ||
| 247 | |||
| 248 | fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> { | ||
| 249 | let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; | ||
| 250 | let progress = self.current_progress(aligned_buf)?; | ||
| 251 | |||
| 252 | Ok(progress >= page_count * 2) | ||
| 253 | } | ||
| 254 | |||
| 255 | fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> { | ||
| 256 | let write_size = STATE::WRITE_SIZE as u32; | ||
| 257 | let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; | ||
| 258 | let state_word = &mut aligned_buf[..write_size as usize]; | ||
| 259 | |||
| 260 | self.state.read(write_size, state_word)?; | ||
| 261 | if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 262 | // Progress is invalid | ||
| 263 | return Ok(max_index); | ||
| 264 | } | ||
| 265 | |||
| 266 | for index in 0..max_index { | ||
| 267 | self.state.read((2 + index) as u32 * write_size, state_word)?; | ||
| 268 | |||
| 269 | if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { | ||
| 270 | return Ok(index); | ||
| 271 | } | ||
| 272 | } | ||
| 273 | Ok(max_index) | ||
| 274 | } | ||
| 275 | |||
| 276 | fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||
| 277 | let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||
| 278 | state_word.fill(!STATE_ERASE_VALUE); | ||
| 279 | self.state | ||
| 280 | .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; | ||
| 281 | Ok(()) | ||
| 282 | } | ||
| 283 | |||
| 284 | fn copy_page_once_to_active( | ||
| 285 | &mut self, | ||
| 286 | progress_index: usize, | ||
| 287 | from_offset: u32, | ||
| 288 | to_offset: u32, | ||
| 289 | aligned_buf: &mut [u8], | ||
| 290 | ) -> Result<(), BootError> { | ||
| 291 | if self.current_progress(aligned_buf)? <= progress_index { | ||
| 292 | let page_size = Self::PAGE_SIZE as u32; | ||
| 293 | |||
| 294 | self.active.erase(to_offset, to_offset + page_size)?; | ||
| 295 | |||
| 296 | for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { | ||
| 297 | self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; | ||
| 298 | self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; | ||
| 299 | } | ||
| 300 | |||
| 301 | self.update_progress(progress_index, aligned_buf)?; | ||
| 302 | } | ||
| 303 | Ok(()) | ||
| 304 | } | ||
| 305 | |||
| 306 | fn copy_page_once_to_dfu( | ||
| 307 | &mut self, | ||
| 308 | progress_index: usize, | ||
| 309 | from_offset: u32, | ||
| 310 | to_offset: u32, | ||
| 311 | aligned_buf: &mut [u8], | ||
| 312 | ) -> Result<(), BootError> { | ||
| 313 | if self.current_progress(aligned_buf)? <= progress_index { | ||
| 314 | let page_size = Self::PAGE_SIZE as u32; | ||
| 315 | |||
| 316 | self.dfu.erase(to_offset as u32, to_offset + page_size)?; | ||
| 317 | |||
| 318 | for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { | ||
| 319 | self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; | ||
| 320 | self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; | ||
| 321 | } | ||
| 322 | |||
| 323 | self.update_progress(progress_index, aligned_buf)?; | ||
| 324 | } | ||
| 325 | Ok(()) | ||
| 326 | } | ||
| 327 | |||
| 328 | fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||
| 329 | let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; | ||
| 330 | for page_num in 0..page_count { | ||
| 331 | let progress_index = (page_num * 2) as usize; | ||
| 332 | |||
| 333 | // Copy active page to the 'next' DFU page. | ||
| 334 | let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||
| 335 | let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; | ||
| 336 | //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); | ||
| 337 | self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; | ||
| 338 | |||
| 339 | // Copy DFU page to the active page | ||
| 340 | let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||
| 341 | let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||
| 342 | //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); | ||
| 343 | self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; | ||
| 344 | } | ||
| 345 | |||
| 346 | Ok(()) | ||
| 347 | } | ||
| 348 | |||
| 349 | fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||
| 350 | let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; | ||
| 351 | for page_num in 0..page_count { | ||
| 352 | let progress_index = (page_count * 2 + page_num * 2) as usize; | ||
| 353 | |||
| 354 | // Copy the bad active page to the DFU page | ||
| 355 | let active_from_offset = page_num * Self::PAGE_SIZE; | ||
| 356 | let dfu_to_offset = page_num * Self::PAGE_SIZE; | ||
| 357 | self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; | ||
| 358 | |||
| 359 | // Copy the DFU page back to the active page | ||
| 360 | let active_to_offset = page_num * Self::PAGE_SIZE; | ||
| 361 | let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; | ||
| 362 | self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; | ||
| 363 | } | ||
| 364 | |||
| 365 | Ok(()) | ||
| 366 | } | ||
| 367 | |||
| 368 | fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||
| 369 | let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||
| 370 | self.state.read(0, state_word)?; | ||
| 371 | |||
| 372 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 373 | Ok(State::Swap) | ||
| 374 | } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 375 | Ok(State::DfuDetach) | ||
| 376 | } else { | ||
| 377 | Ok(State::Boot) | ||
| 378 | } | ||
| 379 | } | ||
| 380 | } | ||
| 381 | |||
| 382 | fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||
| 383 | active: &ACTIVE, | ||
| 384 | dfu: &DFU, | ||
| 385 | state: &STATE, | ||
| 386 | page_size: u32, | ||
| 387 | ) { | ||
| 388 | assert_eq!(active.capacity() as u32 % page_size, 0); | ||
| 389 | assert_eq!(dfu.capacity() as u32 % page_size, 0); | ||
| 390 | // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm | ||
| 391 | assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); | ||
| 392 | assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); | ||
| 393 | } | ||
| 394 | |||
| 395 | #[cfg(test)] | ||
| 396 | mod tests { | ||
| 397 | use super::*; | ||
| 398 | use crate::mem_flash::MemFlash; | ||
| 399 | |||
| 400 | #[test] | ||
| 401 | #[should_panic] | ||
| 402 | fn test_range_asserts() { | ||
| 403 | const ACTIVE_SIZE: usize = 4194304 - 4096; | ||
| 404 | const DFU_SIZE: usize = 4194304; | ||
| 405 | const STATE_SIZE: usize = 4096; | ||
| 406 | static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF); | ||
| 407 | static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF); | ||
| 408 | static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF); | ||
| 409 | assert_partitions(&ACTIVE, &DFU, &STATE, 4096); | ||
| 410 | } | ||
| 411 | } | ||
diff --git a/embassy-boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..2e4e03da3 --- /dev/null +++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | use digest::typenum::U64; | ||
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | ||
| 3 | use ed25519_dalek::Digest; | ||
| 4 | |||
| 5 | pub struct Sha512(ed25519_dalek::Sha512); | ||
| 6 | |||
| 7 | impl Default for Sha512 { | ||
| 8 | fn default() -> Self { | ||
| 9 | Self(ed25519_dalek::Sha512::new()) | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | impl Update for Sha512 { | ||
| 14 | fn update(&mut self, data: &[u8]) { | ||
| 15 | Digest::update(&mut self.0, data) | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | impl FixedOutput for Sha512 { | ||
| 20 | fn finalize_into(self, out: &mut digest::Output<Self>) { | ||
| 21 | let result = self.0.finalize(); | ||
| 22 | out.as_mut_slice().copy_from_slice(result.as_slice()) | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | impl OutputSizeUser for Sha512 { | ||
| 27 | type OutputSize = U64; | ||
| 28 | } | ||
| 29 | |||
| 30 | impl HashMarker for Sha512 {} | ||
diff --git a/embassy-boot/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/src/digest_adapters/mod.rs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | #[cfg(feature = "ed25519-dalek")] | ||
| 2 | pub(crate) mod ed25519_dalek; | ||
| 3 | |||
| 4 | #[cfg(feature = "ed25519-salty")] | ||
| 5 | pub(crate) mod salty; | ||
diff --git a/embassy-boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/src/digest_adapters/salty.rs | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | use digest::typenum::U64; | ||
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | ||
| 3 | |||
| 4 | pub struct Sha512(salty::Sha512); | ||
| 5 | |||
| 6 | impl Default for Sha512 { | ||
| 7 | fn default() -> Self { | ||
| 8 | Self(salty::Sha512::new()) | ||
| 9 | } | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Update for Sha512 { | ||
| 13 | fn update(&mut self, data: &[u8]) { | ||
| 14 | self.0.update(data) | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | impl FixedOutput for Sha512 { | ||
| 19 | fn finalize_into(self, out: &mut digest::Output<Self>) { | ||
| 20 | let result = self.0.finalize(); | ||
| 21 | out.as_mut_slice().copy_from_slice(result.as_slice()) | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | impl OutputSizeUser for Sha512 { | ||
| 26 | type OutputSize = U64; | ||
| 27 | } | ||
| 28 | |||
| 29 | impl HashMarker for Sha512 {} | ||
diff --git a/embassy-boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..2e43e1cc1 --- /dev/null +++ b/embassy-boot/src/firmware_updater/asynch.rs | |||
| @@ -0,0 +1,329 @@ | |||
| 1 | use digest::Digest; | ||
| 2 | #[cfg(target_os = "none")] | ||
| 3 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 6 | use embedded_storage_async::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use super::FirmwareUpdaterConfig; | ||
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 10 | |||
| 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 12 | /// 'mess up' the internal bootloader state | ||
| 13 | pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||
| 14 | dfu: DFU, | ||
| 15 | state: FirmwareState<'d, STATE>, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[cfg(target_os = "none")] | ||
| 19 | impl<'a, FLASH: NorFlash> | ||
| 20 | FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> | ||
| 21 | { | ||
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 23 | pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self { | ||
| 24 | extern "C" { | ||
| 25 | static __bootloader_state_start: u32; | ||
| 26 | static __bootloader_state_end: u32; | ||
| 27 | static __bootloader_dfu_start: u32; | ||
| 28 | static __bootloader_dfu_end: u32; | ||
| 29 | } | ||
| 30 | |||
| 31 | let dfu = unsafe { | ||
| 32 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 33 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 34 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 35 | |||
| 36 | Partition::new(flash, start, end - start) | ||
| 37 | }; | ||
| 38 | let state = unsafe { | ||
| 39 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 40 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 41 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 42 | |||
| 43 | Partition::new(flash, start, end - start) | ||
| 44 | }; | ||
| 45 | |||
| 46 | Self { dfu, state } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | ||
| 51 | /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||
| 52 | pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 53 | Self { | ||
| 54 | dfu: config.dfu, | ||
| 55 | state: FirmwareState::new(config.state, aligned), | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// Obtain the current state. | ||
| 60 | /// | ||
| 61 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 62 | /// to do verifications and self-tests of the new image before calling | ||
| 63 | /// `mark_booted`. | ||
| 64 | pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||
| 65 | self.state.get_state().await | ||
| 66 | } | ||
| 67 | |||
| 68 | /// Verify the DFU given a public key. If there is an error then DO NOT | ||
| 69 | /// proceed with updating the firmware as it must be signed with a | ||
| 70 | /// corresponding private key (otherwise it could be malicious firmware). | ||
| 71 | /// | ||
| 72 | /// Mark to trigger firmware swap on next boot if verify suceeds. | ||
| 73 | /// | ||
| 74 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||
| 75 | /// been generated from a SHA-512 digest of the firmware bytes. | ||
| 76 | /// | ||
| 77 | /// If no signature feature is set then this method will always return a | ||
| 78 | /// signature error. | ||
| 79 | #[cfg(feature = "_verify")] | ||
| 80 | pub async fn verify_and_mark_updated( | ||
| 81 | &mut self, | ||
| 82 | _public_key: &[u8; 32], | ||
| 83 | _signature: &[u8; 64], | ||
| 84 | _update_len: u32, | ||
| 85 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 86 | assert!(_update_len <= self.dfu.capacity() as u32); | ||
| 87 | |||
| 88 | self.state.verify_booted().await?; | ||
| 89 | |||
| 90 | #[cfg(feature = "ed25519-dalek")] | ||
| 91 | { | ||
| 92 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; | ||
| 93 | |||
| 94 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 95 | |||
| 96 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||
| 97 | |||
| 98 | let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||
| 99 | let signature = Signature::from_bytes(_signature); | ||
| 100 | |||
| 101 | let mut chunk_buf = [0; 2]; | ||
| 102 | let mut message = [0; 64]; | ||
| 103 | self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||
| 104 | |||
| 105 | public_key.verify(&message, &signature).map_err(into_signature_error)? | ||
| 106 | } | ||
| 107 | #[cfg(feature = "ed25519-salty")] | ||
| 108 | { | ||
| 109 | use salty::{PublicKey, Signature}; | ||
| 110 | |||
| 111 | use crate::digest_adapters::salty::Sha512; | ||
| 112 | |||
| 113 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||
| 114 | FirmwareUpdaterError::Signature(signature::Error::default()) | ||
| 115 | } | ||
| 116 | |||
| 117 | let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; | ||
| 118 | let signature = Signature::try_from(_signature).map_err(into_signature_error)?; | ||
| 119 | |||
| 120 | let mut message = [0; 64]; | ||
| 121 | let mut chunk_buf = [0; 2]; | ||
| 122 | self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message).await?; | ||
| 123 | |||
| 124 | let r = public_key.verify(&message, &signature); | ||
| 125 | trace!( | ||
| 126 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||
| 127 | public_key.to_bytes(), | ||
| 128 | signature.to_bytes(), | ||
| 129 | message, | ||
| 130 | r.is_ok() | ||
| 131 | ); | ||
| 132 | r.map_err(into_signature_error)? | ||
| 133 | } | ||
| 134 | |||
| 135 | self.state.mark_updated().await | ||
| 136 | } | ||
| 137 | |||
| 138 | /// Verify the update in DFU with any digest. | ||
| 139 | pub async fn hash<D: Digest>( | ||
| 140 | &mut self, | ||
| 141 | update_len: u32, | ||
| 142 | chunk_buf: &mut [u8], | ||
| 143 | output: &mut [u8], | ||
| 144 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 145 | let mut digest = D::new(); | ||
| 146 | for offset in (0..update_len).step_by(chunk_buf.len()) { | ||
| 147 | self.dfu.read(offset, chunk_buf).await?; | ||
| 148 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||
| 149 | digest.update(&chunk_buf[..len]); | ||
| 150 | } | ||
| 151 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 152 | Ok(()) | ||
| 153 | } | ||
| 154 | |||
| 155 | /// Mark to trigger firmware swap on next boot. | ||
| 156 | #[cfg(not(feature = "_verify"))] | ||
| 157 | pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 158 | self.state.mark_updated().await | ||
| 159 | } | ||
| 160 | |||
| 161 | /// Mark to trigger USB DFU on next boot. | ||
| 162 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 163 | self.state.verify_booted().await?; | ||
| 164 | self.state.mark_dfu().await | ||
| 165 | } | ||
| 166 | |||
| 167 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 168 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 169 | self.state.mark_booted().await | ||
| 170 | } | ||
| 171 | |||
| 172 | /// Write data to a flash page. | ||
| 173 | /// | ||
| 174 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||
| 175 | /// | ||
| 176 | /// # Safety | ||
| 177 | /// | ||
| 178 | /// Failing to meet alignment and size requirements may result in a panic. | ||
| 179 | pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 180 | assert!(data.len() >= DFU::ERASE_SIZE); | ||
| 181 | |||
| 182 | self.state.verify_booted().await?; | ||
| 183 | |||
| 184 | self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; | ||
| 185 | |||
| 186 | self.dfu.write(offset as u32, data).await?; | ||
| 187 | |||
| 188 | Ok(()) | ||
| 189 | } | ||
| 190 | |||
| 191 | /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||
| 192 | /// returning its `Partition`. | ||
| 193 | /// | ||
| 194 | /// Using this instead of `write_firmware` allows for an optimized API in | ||
| 195 | /// exchange for added complexity. | ||
| 196 | pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||
| 197 | self.state.verify_booted().await?; | ||
| 198 | self.dfu.erase(0, self.dfu.capacity() as u32).await?; | ||
| 199 | |||
| 200 | Ok(&mut self.dfu) | ||
| 201 | } | ||
| 202 | } | ||
| 203 | |||
| 204 | /// Manages the state partition of the firmware update. | ||
| 205 | /// | ||
| 206 | /// Can be used standalone for more fine grained control, or as part of the updater. | ||
| 207 | pub struct FirmwareState<'d, STATE> { | ||
| 208 | state: STATE, | ||
| 209 | aligned: &'d mut [u8], | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | ||
| 213 | /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. | ||
| 214 | /// | ||
| 215 | /// # Safety | ||
| 216 | /// | ||
| 217 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 218 | /// and written to. | ||
| 219 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 220 | Self::new(config.state, aligned) | ||
| 221 | } | ||
| 222 | |||
| 223 | /// Create a firmware state instance with a buffer for magic content and state partition. | ||
| 224 | /// | ||
| 225 | /// # Safety | ||
| 226 | /// | ||
| 227 | /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE, | ||
| 228 | /// and follow the alignment rules for the flash being read from and written to. | ||
| 229 | pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { | ||
| 230 | assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE)); | ||
| 231 | Self { state, aligned } | ||
| 232 | } | ||
| 233 | |||
| 234 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||
| 235 | async fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 236 | if self.get_state().await? == State::Boot { | ||
| 237 | Ok(()) | ||
| 238 | } else { | ||
| 239 | Err(FirmwareUpdaterError::BadState) | ||
| 240 | } | ||
| 241 | } | ||
| 242 | |||
| 243 | /// Obtain the current state. | ||
| 244 | /// | ||
| 245 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 246 | /// to do verifications and self-tests of the new image before calling | ||
| 247 | /// `mark_booted`. | ||
| 248 | pub async fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||
| 249 | self.state.read(0, &mut self.aligned).await?; | ||
| 250 | |||
| 251 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 252 | Ok(State::Swap) | ||
| 253 | } else { | ||
| 254 | Ok(State::Boot) | ||
| 255 | } | ||
| 256 | } | ||
| 257 | |||
| 258 | /// Mark to trigger firmware swap on next boot. | ||
| 259 | pub async fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 260 | self.set_magic(SWAP_MAGIC).await | ||
| 261 | } | ||
| 262 | |||
| 263 | /// Mark to trigger USB DFU on next boot. | ||
| 264 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 265 | self.set_magic(DFU_DETACH_MAGIC).await | ||
| 266 | } | ||
| 267 | |||
| 268 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 269 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 270 | self.set_magic(BOOT_MAGIC).await | ||
| 271 | } | ||
| 272 | |||
| 273 | async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { | ||
| 274 | self.state.read(0, &mut self.aligned).await?; | ||
| 275 | |||
| 276 | if self.aligned.iter().any(|&b| b != magic) { | ||
| 277 | // Read progress validity | ||
| 278 | self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; | ||
| 279 | |||
| 280 | if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 281 | // The current progress validity marker is invalid | ||
| 282 | } else { | ||
| 283 | // Invalidate progress | ||
| 284 | self.aligned.fill(!STATE_ERASE_VALUE); | ||
| 285 | self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; | ||
| 286 | } | ||
| 287 | |||
| 288 | // Clear magic and progress | ||
| 289 | self.state.erase(0, self.state.capacity() as u32).await?; | ||
| 290 | |||
| 291 | // Set magic | ||
| 292 | self.aligned.fill(magic); | ||
| 293 | self.state.write(0, &self.aligned).await?; | ||
| 294 | } | ||
| 295 | Ok(()) | ||
| 296 | } | ||
| 297 | } | ||
| 298 | |||
| 299 | #[cfg(test)] | ||
| 300 | mod tests { | ||
| 301 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 302 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 303 | use embassy_sync::mutex::Mutex; | ||
| 304 | use futures::executor::block_on; | ||
| 305 | use sha1::{Digest, Sha1}; | ||
| 306 | |||
| 307 | use super::*; | ||
| 308 | use crate::mem_flash::MemFlash; | ||
| 309 | |||
| 310 | #[test] | ||
| 311 | fn can_verify_sha1() { | ||
| 312 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default()); | ||
| 313 | let state = Partition::new(&flash, 0, 4096); | ||
| 314 | let dfu = Partition::new(&flash, 65536, 65536); | ||
| 315 | let mut aligned = [0; 8]; | ||
| 316 | |||
| 317 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 318 | let mut to_write = [0; 4096]; | ||
| 319 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 320 | |||
| 321 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 322 | block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); | ||
| 323 | let mut chunk_buf = [0; 2]; | ||
| 324 | let mut hash = [0; 20]; | ||
| 325 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 326 | |||
| 327 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 328 | } | ||
| 329 | } | ||
diff --git a/embassy-boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs new file mode 100644 index 000000000..f1368540d --- /dev/null +++ b/embassy-boot/src/firmware_updater/blocking.rs | |||
| @@ -0,0 +1,340 @@ | |||
| 1 | use digest::Digest; | ||
| 2 | #[cfg(target_os = "none")] | ||
| 3 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 6 | use embedded_storage::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use super::FirmwareUpdaterConfig; | ||
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 10 | |||
| 11 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 12 | /// 'mess up' the internal bootloader state | ||
| 13 | pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { | ||
| 14 | dfu: DFU, | ||
| 15 | state: BlockingFirmwareState<'d, STATE>, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[cfg(target_os = "none")] | ||
| 19 | impl<'a, FLASH: NorFlash> | ||
| 20 | FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> | ||
| 21 | { | ||
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 23 | pub fn from_linkerfile_blocking( | ||
| 24 | flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>, | ||
| 25 | ) -> Self { | ||
| 26 | extern "C" { | ||
| 27 | static __bootloader_state_start: u32; | ||
| 28 | static __bootloader_state_end: u32; | ||
| 29 | static __bootloader_dfu_start: u32; | ||
| 30 | static __bootloader_dfu_end: u32; | ||
| 31 | } | ||
| 32 | |||
| 33 | let dfu = unsafe { | ||
| 34 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 35 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 36 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 37 | |||
| 38 | BlockingPartition::new(flash, start, end - start) | ||
| 39 | }; | ||
| 40 | let state = unsafe { | ||
| 41 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 42 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 43 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 44 | |||
| 45 | BlockingPartition::new(flash, start, end - start) | ||
| 46 | }; | ||
| 47 | |||
| 48 | Self { dfu, state } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> { | ||
| 53 | /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||
| 54 | /// | ||
| 55 | /// # Safety | ||
| 56 | /// | ||
| 57 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 58 | /// and written to. | ||
| 59 | pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 60 | Self { | ||
| 61 | dfu: config.dfu, | ||
| 62 | state: BlockingFirmwareState::new(config.state, aligned), | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | /// Obtain the current state. | ||
| 67 | /// | ||
| 68 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 69 | /// to do verifications and self-tests of the new image before calling | ||
| 70 | /// `mark_booted`. | ||
| 71 | pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||
| 72 | self.state.get_state() | ||
| 73 | } | ||
| 74 | |||
| 75 | /// Verify the DFU given a public key. If there is an error then DO NOT | ||
| 76 | /// proceed with updating the firmware as it must be signed with a | ||
| 77 | /// corresponding private key (otherwise it could be malicious firmware). | ||
| 78 | /// | ||
| 79 | /// Mark to trigger firmware swap on next boot if verify suceeds. | ||
| 80 | /// | ||
| 81 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||
| 82 | /// been generated from a SHA-512 digest of the firmware bytes. | ||
| 83 | /// | ||
| 84 | /// If no signature feature is set then this method will always return a | ||
| 85 | /// signature error. | ||
| 86 | #[cfg(feature = "_verify")] | ||
| 87 | pub fn verify_and_mark_updated( | ||
| 88 | &mut self, | ||
| 89 | _public_key: &[u8; 32], | ||
| 90 | _signature: &[u8; 64], | ||
| 91 | _update_len: u32, | ||
| 92 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 93 | assert!(_update_len <= self.dfu.capacity() as u32); | ||
| 94 | |||
| 95 | self.state.verify_booted()?; | ||
| 96 | |||
| 97 | #[cfg(feature = "ed25519-dalek")] | ||
| 98 | { | ||
| 99 | use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey}; | ||
| 100 | |||
| 101 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 102 | |||
| 103 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||
| 104 | |||
| 105 | let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||
| 106 | let signature = Signature::from_bytes(_signature); | ||
| 107 | |||
| 108 | let mut message = [0; 64]; | ||
| 109 | let mut chunk_buf = [0; 2]; | ||
| 110 | self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?; | ||
| 111 | |||
| 112 | public_key.verify(&message, &signature).map_err(into_signature_error)? | ||
| 113 | } | ||
| 114 | #[cfg(feature = "ed25519-salty")] | ||
| 115 | { | ||
| 116 | use salty::{PublicKey, Signature}; | ||
| 117 | |||
| 118 | use crate::digest_adapters::salty::Sha512; | ||
| 119 | |||
| 120 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||
| 121 | FirmwareUpdaterError::Signature(signature::Error::default()) | ||
| 122 | } | ||
| 123 | |||
| 124 | let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?; | ||
| 125 | let signature = Signature::try_from(_signature).map_err(into_signature_error)?; | ||
| 126 | |||
| 127 | let mut message = [0; 64]; | ||
| 128 | let mut chunk_buf = [0; 2]; | ||
| 129 | self.hash::<Sha512>(_update_len, &mut chunk_buf, &mut message)?; | ||
| 130 | |||
| 131 | let r = public_key.verify(&message, &signature); | ||
| 132 | trace!( | ||
| 133 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||
| 134 | public_key.to_bytes(), | ||
| 135 | signature.to_bytes(), | ||
| 136 | message, | ||
| 137 | r.is_ok() | ||
| 138 | ); | ||
| 139 | r.map_err(into_signature_error)? | ||
| 140 | } | ||
| 141 | |||
| 142 | self.state.mark_updated() | ||
| 143 | } | ||
| 144 | |||
| 145 | /// Verify the update in DFU with any digest. | ||
| 146 | pub fn hash<D: Digest>( | ||
| 147 | &mut self, | ||
| 148 | update_len: u32, | ||
| 149 | chunk_buf: &mut [u8], | ||
| 150 | output: &mut [u8], | ||
| 151 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 152 | let mut digest = D::new(); | ||
| 153 | for offset in (0..update_len).step_by(chunk_buf.len()) { | ||
| 154 | self.dfu.read(offset, chunk_buf)?; | ||
| 155 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||
| 156 | digest.update(&chunk_buf[..len]); | ||
| 157 | } | ||
| 158 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 159 | Ok(()) | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Mark to trigger firmware swap on next boot. | ||
| 163 | #[cfg(not(feature = "_verify"))] | ||
| 164 | pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 165 | self.state.mark_updated() | ||
| 166 | } | ||
| 167 | |||
| 168 | /// Mark to trigger USB DFU device on next boot. | ||
| 169 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 170 | self.state.verify_booted()?; | ||
| 171 | self.state.mark_dfu() | ||
| 172 | } | ||
| 173 | |||
| 174 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 175 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 176 | self.state.mark_booted() | ||
| 177 | } | ||
| 178 | |||
| 179 | /// Write data to a flash page. | ||
| 180 | /// | ||
| 181 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||
| 182 | /// | ||
| 183 | /// # Safety | ||
| 184 | /// | ||
| 185 | /// Failing to meet alignment and size requirements may result in a panic. | ||
| 186 | pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 187 | assert!(data.len() >= DFU::ERASE_SIZE); | ||
| 188 | self.state.verify_booted()?; | ||
| 189 | |||
| 190 | self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; | ||
| 191 | |||
| 192 | self.dfu.write(offset as u32, data)?; | ||
| 193 | |||
| 194 | Ok(()) | ||
| 195 | } | ||
| 196 | |||
| 197 | /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||
| 198 | /// returning its `Partition`. | ||
| 199 | /// | ||
| 200 | /// Using this instead of `write_firmware` allows for an optimized API in | ||
| 201 | /// exchange for added complexity. | ||
| 202 | pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { | ||
| 203 | self.state.verify_booted()?; | ||
| 204 | self.dfu.erase(0, self.dfu.capacity() as u32)?; | ||
| 205 | |||
| 206 | Ok(&mut self.dfu) | ||
| 207 | } | ||
| 208 | } | ||
| 209 | |||
| 210 | /// Manages the state partition of the firmware update. | ||
| 211 | /// | ||
| 212 | /// Can be used standalone for more fine grained control, or as part of the updater. | ||
| 213 | pub struct BlockingFirmwareState<'d, STATE> { | ||
| 214 | state: STATE, | ||
| 215 | aligned: &'d mut [u8], | ||
| 216 | } | ||
| 217 | |||
| 218 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | ||
| 219 | /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. | ||
| 220 | /// | ||
| 221 | /// # Safety | ||
| 222 | /// | ||
| 223 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 224 | /// and written to. | ||
| 225 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 226 | Self::new(config.state, aligned) | ||
| 227 | } | ||
| 228 | |||
| 229 | /// Create a firmware state instance with a buffer for magic content and state partition. | ||
| 230 | /// | ||
| 231 | /// # Safety | ||
| 232 | /// | ||
| 233 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 234 | /// and written to. | ||
| 235 | pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { | ||
| 236 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 237 | Self { state, aligned } | ||
| 238 | } | ||
| 239 | |||
| 240 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||
| 241 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 242 | if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { | ||
| 243 | Ok(()) | ||
| 244 | } else { | ||
| 245 | Err(FirmwareUpdaterError::BadState) | ||
| 246 | } | ||
| 247 | } | ||
| 248 | |||
| 249 | /// Obtain the current state. | ||
| 250 | /// | ||
| 251 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 252 | /// to do verifications and self-tests of the new image before calling | ||
| 253 | /// `mark_booted`. | ||
| 254 | pub fn get_state(&mut self) -> Result<State, FirmwareUpdaterError> { | ||
| 255 | self.state.read(0, &mut self.aligned)?; | ||
| 256 | |||
| 257 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 258 | Ok(State::Swap) | ||
| 259 | } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 260 | Ok(State::DfuDetach) | ||
| 261 | } else { | ||
| 262 | Ok(State::Boot) | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | /// Mark to trigger firmware swap on next boot. | ||
| 267 | pub fn mark_updated(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 268 | self.set_magic(SWAP_MAGIC) | ||
| 269 | } | ||
| 270 | |||
| 271 | /// Mark to trigger USB DFU on next boot. | ||
| 272 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 273 | self.set_magic(DFU_DETACH_MAGIC) | ||
| 274 | } | ||
| 275 | |||
| 276 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 277 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 278 | self.set_magic(BOOT_MAGIC) | ||
| 279 | } | ||
| 280 | |||
| 281 | fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { | ||
| 282 | self.state.read(0, &mut self.aligned)?; | ||
| 283 | |||
| 284 | if self.aligned.iter().any(|&b| b != magic) { | ||
| 285 | // Read progress validity | ||
| 286 | self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned)?; | ||
| 287 | |||
| 288 | if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 289 | // The current progress validity marker is invalid | ||
| 290 | } else { | ||
| 291 | // Invalidate progress | ||
| 292 | self.aligned.fill(!STATE_ERASE_VALUE); | ||
| 293 | self.state.write(STATE::WRITE_SIZE as u32, &self.aligned)?; | ||
| 294 | } | ||
| 295 | |||
| 296 | // Clear magic and progress | ||
| 297 | self.state.erase(0, self.state.capacity() as u32)?; | ||
| 298 | |||
| 299 | // Set magic | ||
| 300 | self.aligned.fill(magic); | ||
| 301 | self.state.write(0, &self.aligned)?; | ||
| 302 | } | ||
| 303 | Ok(()) | ||
| 304 | } | ||
| 305 | } | ||
| 306 | |||
| 307 | #[cfg(test)] | ||
| 308 | mod tests { | ||
| 309 | use core::cell::RefCell; | ||
| 310 | |||
| 311 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 312 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 313 | use embassy_sync::blocking_mutex::Mutex; | ||
| 314 | use sha1::{Digest, Sha1}; | ||
| 315 | |||
| 316 | use super::*; | ||
| 317 | use crate::mem_flash::MemFlash; | ||
| 318 | |||
| 319 | #[test] | ||
| 320 | fn can_verify_sha1() { | ||
| 321 | let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); | ||
| 322 | let state = BlockingPartition::new(&flash, 0, 4096); | ||
| 323 | let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||
| 324 | let mut aligned = [0; 8]; | ||
| 325 | |||
| 326 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 327 | let mut to_write = [0; 4096]; | ||
| 328 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 329 | |||
| 330 | let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned); | ||
| 331 | updater.write_firmware(0, to_write.as_slice()).unwrap(); | ||
| 332 | let mut chunk_buf = [0; 2]; | ||
| 333 | let mut hash = [0; 20]; | ||
| 334 | updater | ||
| 335 | .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||
| 336 | .unwrap(); | ||
| 337 | |||
| 338 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 339 | } | ||
| 340 | } | ||
diff --git a/embassy-boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs new file mode 100644 index 000000000..4814786bf --- /dev/null +++ b/embassy-boot/src/firmware_updater/mod.rs | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | mod asynch; | ||
| 2 | mod blocking; | ||
| 3 | |||
| 4 | pub use asynch::{FirmwareState, FirmwareUpdater}; | ||
| 5 | pub use blocking::{BlockingFirmwareState, BlockingFirmwareUpdater}; | ||
| 6 | use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | ||
| 7 | |||
| 8 | /// Firmware updater flash configuration holding the two flashes used by the updater | ||
| 9 | /// | ||
| 10 | /// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. | ||
| 11 | /// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition | ||
| 12 | /// the provided flash according to symbols defined in the linkerfile. | ||
| 13 | pub struct FirmwareUpdaterConfig<DFU, STATE> { | ||
| 14 | /// The dfu flash partition | ||
| 15 | pub dfu: DFU, | ||
| 16 | /// The state flash partition | ||
| 17 | pub state: STATE, | ||
| 18 | } | ||
| 19 | |||
| 20 | /// Errors returned by FirmwareUpdater | ||
| 21 | #[derive(Debug)] | ||
| 22 | pub enum FirmwareUpdaterError { | ||
| 23 | /// Error from flash. | ||
| 24 | Flash(NorFlashErrorKind), | ||
| 25 | /// Signature errors. | ||
| 26 | Signature(signature::Error), | ||
| 27 | /// Bad state. | ||
| 28 | BadState, | ||
| 29 | } | ||
| 30 | |||
| 31 | #[cfg(feature = "defmt")] | ||
| 32 | impl defmt::Format for FirmwareUpdaterError { | ||
| 33 | fn format(&self, fmt: defmt::Formatter) { | ||
| 34 | match self { | ||
| 35 | FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), | ||
| 36 | FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), | ||
| 37 | FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), | ||
| 38 | } | ||
| 39 | } | ||
| 40 | } | ||
| 41 | |||
| 42 | impl<E> From<E> for FirmwareUpdaterError | ||
| 43 | where | ||
| 44 | E: NorFlashError, | ||
| 45 | { | ||
| 46 | fn from(error: E) -> Self { | ||
| 47 | FirmwareUpdaterError::Flash(error.kind()) | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/embassy-boot/src/fmt.rs b/embassy-boot/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-boot/src/fmt.rs | |||
| @@ -0,0 +1,258 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | ||
| 5 | |||
| 6 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 7 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 8 | |||
| 9 | macro_rules! assert { | ||
| 10 | ($($x:tt)*) => { | ||
| 11 | { | ||
| 12 | #[cfg(not(feature = "defmt"))] | ||
| 13 | ::core::assert!($($x)*); | ||
| 14 | #[cfg(feature = "defmt")] | ||
| 15 | ::defmt::assert!($($x)*); | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 20 | macro_rules! assert_eq { | ||
| 21 | ($($x:tt)*) => { | ||
| 22 | { | ||
| 23 | #[cfg(not(feature = "defmt"))] | ||
| 24 | ::core::assert_eq!($($x)*); | ||
| 25 | #[cfg(feature = "defmt")] | ||
| 26 | ::defmt::assert_eq!($($x)*); | ||
| 27 | } | ||
| 28 | }; | ||
| 29 | } | ||
| 30 | |||
| 31 | macro_rules! assert_ne { | ||
| 32 | ($($x:tt)*) => { | ||
| 33 | { | ||
| 34 | #[cfg(not(feature = "defmt"))] | ||
| 35 | ::core::assert_ne!($($x)*); | ||
| 36 | #[cfg(feature = "defmt")] | ||
| 37 | ::defmt::assert_ne!($($x)*); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | } | ||
| 41 | |||
| 42 | macro_rules! debug_assert { | ||
| 43 | ($($x:tt)*) => { | ||
| 44 | { | ||
| 45 | #[cfg(not(feature = "defmt"))] | ||
| 46 | ::core::debug_assert!($($x)*); | ||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | ::defmt::debug_assert!($($x)*); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | } | ||
| 52 | |||
| 53 | macro_rules! debug_assert_eq { | ||
| 54 | ($($x:tt)*) => { | ||
| 55 | { | ||
| 56 | #[cfg(not(feature = "defmt"))] | ||
| 57 | ::core::debug_assert_eq!($($x)*); | ||
| 58 | #[cfg(feature = "defmt")] | ||
| 59 | ::defmt::debug_assert_eq!($($x)*); | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | } | ||
| 63 | |||
| 64 | macro_rules! debug_assert_ne { | ||
| 65 | ($($x:tt)*) => { | ||
| 66 | { | ||
| 67 | #[cfg(not(feature = "defmt"))] | ||
| 68 | ::core::debug_assert_ne!($($x)*); | ||
| 69 | #[cfg(feature = "defmt")] | ||
| 70 | ::defmt::debug_assert_ne!($($x)*); | ||
| 71 | } | ||
| 72 | }; | ||
| 73 | } | ||
| 74 | |||
| 75 | macro_rules! todo { | ||
| 76 | ($($x:tt)*) => { | ||
| 77 | { | ||
| 78 | #[cfg(not(feature = "defmt"))] | ||
| 79 | ::core::todo!($($x)*); | ||
| 80 | #[cfg(feature = "defmt")] | ||
| 81 | ::defmt::todo!($($x)*); | ||
| 82 | } | ||
| 83 | }; | ||
| 84 | } | ||
| 85 | |||
| 86 | #[cfg(not(feature = "defmt"))] | ||
| 87 | macro_rules! unreachable { | ||
| 88 | ($($x:tt)*) => { | ||
| 89 | ::core::unreachable!($($x)*) | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | #[cfg(feature = "defmt")] | ||
| 94 | macro_rules! unreachable { | ||
| 95 | ($($x:tt)*) => { | ||
| 96 | ::defmt::unreachable!($($x)*) | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | macro_rules! panic { | ||
| 101 | ($($x:tt)*) => { | ||
| 102 | { | ||
| 103 | #[cfg(not(feature = "defmt"))] | ||
| 104 | ::core::panic!($($x)*); | ||
| 105 | #[cfg(feature = "defmt")] | ||
| 106 | ::defmt::panic!($($x)*); | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | macro_rules! trace { | ||
| 112 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 113 | { | ||
| 114 | #[cfg(feature = "log")] | ||
| 115 | ::log::trace!($s $(, $x)*); | ||
| 116 | #[cfg(feature = "defmt")] | ||
| 117 | ::defmt::trace!($s $(, $x)*); | ||
| 118 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 119 | let _ = ($( & $x ),*); | ||
| 120 | } | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 124 | macro_rules! debug { | ||
| 125 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 126 | { | ||
| 127 | #[cfg(feature = "log")] | ||
| 128 | ::log::debug!($s $(, $x)*); | ||
| 129 | #[cfg(feature = "defmt")] | ||
| 130 | ::defmt::debug!($s $(, $x)*); | ||
| 131 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 132 | let _ = ($( & $x ),*); | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | } | ||
| 136 | |||
| 137 | macro_rules! info { | ||
| 138 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 139 | { | ||
| 140 | #[cfg(feature = "log")] | ||
| 141 | ::log::info!($s $(, $x)*); | ||
| 142 | #[cfg(feature = "defmt")] | ||
| 143 | ::defmt::info!($s $(, $x)*); | ||
| 144 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 145 | let _ = ($( & $x ),*); | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | } | ||
| 149 | |||
| 150 | macro_rules! warn { | ||
| 151 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 152 | { | ||
| 153 | #[cfg(feature = "log")] | ||
| 154 | ::log::warn!($s $(, $x)*); | ||
| 155 | #[cfg(feature = "defmt")] | ||
| 156 | ::defmt::warn!($s $(, $x)*); | ||
| 157 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 158 | let _ = ($( & $x ),*); | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | |||
| 163 | macro_rules! error { | ||
| 164 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 165 | { | ||
| 166 | #[cfg(feature = "log")] | ||
| 167 | ::log::error!($s $(, $x)*); | ||
| 168 | #[cfg(feature = "defmt")] | ||
| 169 | ::defmt::error!($s $(, $x)*); | ||
| 170 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 171 | let _ = ($( & $x ),*); | ||
| 172 | } | ||
| 173 | }; | ||
| 174 | } | ||
| 175 | |||
| 176 | #[cfg(feature = "defmt")] | ||
| 177 | macro_rules! unwrap { | ||
| 178 | ($($x:tt)*) => { | ||
| 179 | ::defmt::unwrap!($($x)*) | ||
| 180 | }; | ||
| 181 | } | ||
| 182 | |||
| 183 | #[cfg(not(feature = "defmt"))] | ||
| 184 | macro_rules! unwrap { | ||
| 185 | ($arg:expr) => { | ||
| 186 | match $crate::fmt::Try::into_result($arg) { | ||
| 187 | ::core::result::Result::Ok(t) => t, | ||
| 188 | ::core::result::Result::Err(e) => { | ||
| 189 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | }; | ||
| 193 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 194 | match $crate::fmt::Try::into_result($arg) { | ||
| 195 | ::core::result::Result::Ok(t) => t, | ||
| 196 | ::core::result::Result::Err(e) => { | ||
| 197 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 204 | pub struct NoneError; | ||
| 205 | |||
| 206 | pub trait Try { | ||
| 207 | type Ok; | ||
| 208 | type Error; | ||
| 209 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<T> Try for Option<T> { | ||
| 213 | type Ok = T; | ||
| 214 | type Error = NoneError; | ||
| 215 | |||
| 216 | #[inline] | ||
| 217 | fn into_result(self) -> Result<T, NoneError> { | ||
| 218 | self.ok_or(NoneError) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | impl<T, E> Try for Result<T, E> { | ||
| 223 | type Ok = T; | ||
| 224 | type Error = E; | ||
| 225 | |||
| 226 | #[inline] | ||
| 227 | fn into_result(self) -> Self { | ||
| 228 | self | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | #[allow(unused)] | ||
| 233 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||
| 234 | |||
| 235 | impl<'a> Debug for Bytes<'a> { | ||
| 236 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 237 | write!(f, "{:#02x?}", self.0) | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'a> Display for Bytes<'a> { | ||
| 242 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 243 | write!(f, "{:#02x?}", self.0) | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | impl<'a> LowerHex for Bytes<'a> { | ||
| 248 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 249 | write!(f, "{:#02x?}", self.0) | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | #[cfg(feature = "defmt")] | ||
| 254 | impl<'a> defmt::Format for Bytes<'a> { | ||
| 255 | fn format(&self, fmt: defmt::Formatter) { | ||
| 256 | defmt::write!(fmt, "{:02x}", self.0) | ||
| 257 | } | ||
| 258 | } | ||
diff --git a/embassy-boot/src/lib.rs b/embassy-boot/src/lib.rs new file mode 100644 index 000000000..b4f03e01e --- /dev/null +++ b/embassy-boot/src/lib.rs | |||
| @@ -0,0 +1,323 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![allow(async_fn_in_trait)] | ||
| 3 | #![warn(missing_docs)] | ||
| 4 | #![doc = include_str!("../README.md")] | ||
| 5 | mod fmt; | ||
| 6 | |||
| 7 | mod boot_loader; | ||
| 8 | mod digest_adapters; | ||
| 9 | mod firmware_updater; | ||
| 10 | #[cfg(test)] | ||
| 11 | mod mem_flash; | ||
| 12 | #[cfg(test)] | ||
| 13 | mod test_flash; | ||
| 14 | |||
| 15 | // The expected value of the flash after an erase | ||
| 16 | // TODO: Use the value provided by NorFlash when available | ||
| 17 | pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; | ||
| 18 | pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; | ||
| 19 | pub use firmware_updater::{ | ||
| 20 | BlockingFirmwareState, BlockingFirmwareUpdater, FirmwareState, FirmwareUpdater, FirmwareUpdaterConfig, | ||
| 21 | FirmwareUpdaterError, | ||
| 22 | }; | ||
| 23 | |||
| 24 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; | ||
| 25 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; | ||
| 26 | pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; | ||
| 27 | |||
| 28 | /// The state of the bootloader after running prepare. | ||
| 29 | #[derive(PartialEq, Eq, Debug)] | ||
| 30 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | ||
| 31 | pub enum State { | ||
| 32 | /// Bootloader is ready to boot the active partition. | ||
| 33 | Boot, | ||
| 34 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. | ||
| 35 | Swap, | ||
| 36 | /// Application has received a request to reboot into DFU mode to apply an update. | ||
| 37 | DfuDetach, | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. | ||
| 41 | #[repr(align(32))] | ||
| 42 | pub struct AlignedBuffer<const N: usize>(pub [u8; N]); | ||
| 43 | |||
| 44 | impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> { | ||
| 45 | fn as_ref(&self) -> &[u8] { | ||
| 46 | &self.0 | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> { | ||
| 51 | fn as_mut(&mut self) -> &mut [u8] { | ||
| 52 | &mut self.0 | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | #[cfg(test)] | ||
| 57 | mod tests { | ||
| 58 | #![allow(unused_imports)] | ||
| 59 | |||
| 60 | use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; | ||
| 61 | use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | ||
| 62 | use futures::executor::block_on; | ||
| 63 | |||
| 64 | use super::*; | ||
| 65 | use crate::boot_loader::BootLoaderConfig; | ||
| 66 | use crate::firmware_updater::FirmwareUpdaterConfig; | ||
| 67 | use crate::mem_flash::MemFlash; | ||
| 68 | use crate::test_flash::{AsyncTestFlash, BlockingTestFlash}; | ||
| 69 | |||
| 70 | /* | ||
| 71 | #[test] | ||
| 72 | fn test_bad_magic() { | ||
| 73 | let mut flash = MemFlash([0xff; 131072]); | ||
| 74 | let mut flash = SingleFlashConfig::new(&mut flash); | ||
| 75 | |||
| 76 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); | ||
| 77 | |||
| 78 | assert_eq!( | ||
| 79 | bootloader.prepare_boot(&mut flash), | ||
| 80 | Err(BootError::BadMagic) | ||
| 81 | ); | ||
| 82 | } | ||
| 83 | */ | ||
| 84 | |||
| 85 | #[test] | ||
| 86 | fn test_boot_state() { | ||
| 87 | let flash = BlockingTestFlash::new(BootLoaderConfig { | ||
| 88 | active: MemFlash::<57344, 4096, 4>::default(), | ||
| 89 | dfu: MemFlash::<61440, 4096, 4>::default(), | ||
| 90 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 91 | }); | ||
| 92 | |||
| 93 | flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); | ||
| 94 | |||
| 95 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 96 | active: flash.active(), | ||
| 97 | dfu: flash.dfu(), | ||
| 98 | state: flash.state(), | ||
| 99 | }); | ||
| 100 | |||
| 101 | let mut page = [0; 4096]; | ||
| 102 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 103 | } | ||
| 104 | |||
| 105 | #[test] | ||
| 106 | #[cfg(not(feature = "_verify"))] | ||
| 107 | fn test_swap_state() { | ||
| 108 | const FIRMWARE_SIZE: usize = 57344; | ||
| 109 | let flash = AsyncTestFlash::new(BootLoaderConfig { | ||
| 110 | active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(), | ||
| 111 | dfu: MemFlash::<61440, 4096, 4>::default(), | ||
| 112 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 113 | }); | ||
| 114 | |||
| 115 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||
| 116 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||
| 117 | let mut aligned = [0; 4]; | ||
| 118 | |||
| 119 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||
| 120 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||
| 121 | |||
| 122 | let mut updater = FirmwareUpdater::new( | ||
| 123 | FirmwareUpdaterConfig { | ||
| 124 | dfu: flash.dfu(), | ||
| 125 | state: flash.state(), | ||
| 126 | }, | ||
| 127 | &mut aligned, | ||
| 128 | ); | ||
| 129 | block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||
| 130 | block_on(updater.mark_updated()).unwrap(); | ||
| 131 | |||
| 132 | // Writing after marking updated is not allowed until marked as booted. | ||
| 133 | let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(0, &UPDATE)); | ||
| 134 | assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); | ||
| 135 | |||
| 136 | let flash = flash.into_blocking(); | ||
| 137 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 138 | active: flash.active(), | ||
| 139 | dfu: flash.dfu(), | ||
| 140 | state: flash.state(), | ||
| 141 | }); | ||
| 142 | |||
| 143 | let mut page = [0; 1024]; | ||
| 144 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 145 | |||
| 146 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 147 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 148 | assert_eq!(UPDATE, read_buf); | ||
| 149 | // First DFU page is untouched | ||
| 150 | flash.dfu().read(4096, &mut read_buf).unwrap(); | ||
| 151 | assert_eq!(ORIGINAL, read_buf); | ||
| 152 | |||
| 153 | // Running again should cause a revert | ||
| 154 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 155 | |||
| 156 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 157 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 158 | assert_eq!(ORIGINAL, read_buf); | ||
| 159 | // Last DFU page is untouched | ||
| 160 | flash.dfu().read(0, &mut read_buf).unwrap(); | ||
| 161 | assert_eq!(UPDATE, read_buf); | ||
| 162 | |||
| 163 | // Mark as booted | ||
| 164 | let flash = flash.into_async(); | ||
| 165 | let mut updater = FirmwareUpdater::new( | ||
| 166 | FirmwareUpdaterConfig { | ||
| 167 | dfu: flash.dfu(), | ||
| 168 | state: flash.state(), | ||
| 169 | }, | ||
| 170 | &mut aligned, | ||
| 171 | ); | ||
| 172 | block_on(updater.mark_booted()).unwrap(); | ||
| 173 | |||
| 174 | let flash = flash.into_blocking(); | ||
| 175 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 176 | active: flash.active(), | ||
| 177 | dfu: flash.dfu(), | ||
| 178 | state: flash.state(), | ||
| 179 | }); | ||
| 180 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 181 | } | ||
| 182 | |||
| 183 | #[test] | ||
| 184 | #[cfg(not(feature = "_verify"))] | ||
| 185 | fn test_swap_state_active_page_biggest() { | ||
| 186 | const FIRMWARE_SIZE: usize = 12288; | ||
| 187 | let flash = AsyncTestFlash::new(BootLoaderConfig { | ||
| 188 | active: MemFlash::<12288, 4096, 8>::random(), | ||
| 189 | dfu: MemFlash::<16384, 2048, 8>::random(), | ||
| 190 | state: MemFlash::<2048, 128, 4>::random(), | ||
| 191 | }); | ||
| 192 | |||
| 193 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||
| 194 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||
| 195 | let mut aligned = [0; 4]; | ||
| 196 | |||
| 197 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||
| 198 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||
| 199 | |||
| 200 | let mut updater = FirmwareUpdater::new( | ||
| 201 | FirmwareUpdaterConfig { | ||
| 202 | dfu: flash.dfu(), | ||
| 203 | state: flash.state(), | ||
| 204 | }, | ||
| 205 | &mut aligned, | ||
| 206 | ); | ||
| 207 | block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||
| 208 | block_on(updater.mark_updated()).unwrap(); | ||
| 209 | |||
| 210 | let flash = flash.into_blocking(); | ||
| 211 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 212 | active: flash.active(), | ||
| 213 | dfu: flash.dfu(), | ||
| 214 | state: flash.state(), | ||
| 215 | }); | ||
| 216 | |||
| 217 | let mut page = [0; 4096]; | ||
| 218 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 219 | |||
| 220 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 221 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 222 | assert_eq!(UPDATE, read_buf); | ||
| 223 | // First DFU page is untouched | ||
| 224 | flash.dfu().read(4096, &mut read_buf).unwrap(); | ||
| 225 | assert_eq!(ORIGINAL, read_buf); | ||
| 226 | } | ||
| 227 | |||
| 228 | #[test] | ||
| 229 | #[cfg(not(feature = "_verify"))] | ||
| 230 | fn test_swap_state_dfu_page_biggest() { | ||
| 231 | const FIRMWARE_SIZE: usize = 12288; | ||
| 232 | let flash = AsyncTestFlash::new(BootLoaderConfig { | ||
| 233 | active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(), | ||
| 234 | dfu: MemFlash::<16384, 4096, 8>::random(), | ||
| 235 | state: MemFlash::<2048, 128, 4>::random(), | ||
| 236 | }); | ||
| 237 | |||
| 238 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | ||
| 239 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; | ||
| 240 | let mut aligned = [0; 4]; | ||
| 241 | |||
| 242 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | ||
| 243 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); | ||
| 244 | |||
| 245 | let mut updater = FirmwareUpdater::new( | ||
| 246 | FirmwareUpdaterConfig { | ||
| 247 | dfu: flash.dfu(), | ||
| 248 | state: flash.state(), | ||
| 249 | }, | ||
| 250 | &mut aligned, | ||
| 251 | ); | ||
| 252 | block_on(updater.write_firmware(0, &UPDATE)).unwrap(); | ||
| 253 | block_on(updater.mark_updated()).unwrap(); | ||
| 254 | |||
| 255 | let flash = flash.into_blocking(); | ||
| 256 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 257 | active: flash.active(), | ||
| 258 | dfu: flash.dfu(), | ||
| 259 | state: flash.state(), | ||
| 260 | }); | ||
| 261 | let mut page = [0; 4096]; | ||
| 262 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 263 | |||
| 264 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 265 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 266 | assert_eq!(UPDATE, read_buf); | ||
| 267 | // First DFU page is untouched | ||
| 268 | flash.dfu().read(4096, &mut read_buf).unwrap(); | ||
| 269 | assert_eq!(ORIGINAL, read_buf); | ||
| 270 | } | ||
| 271 | |||
| 272 | #[test] | ||
| 273 | #[cfg(feature = "_verify")] | ||
| 274 | fn test_verify() { | ||
| 275 | // The following key setup is based on: | ||
| 276 | // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example | ||
| 277 | |||
| 278 | use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey}; | ||
| 279 | use rand::rngs::OsRng; | ||
| 280 | |||
| 281 | let mut csprng = OsRng {}; | ||
| 282 | let keypair = SigningKey::generate(&mut csprng); | ||
| 283 | |||
| 284 | let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; | ||
| 285 | let mut digest = Sha512::new(); | ||
| 286 | digest.update(&firmware); | ||
| 287 | let message = digest.finalize(); | ||
| 288 | let signature: Signature = keypair.sign(&message); | ||
| 289 | |||
| 290 | let public_key = keypair.verifying_key(); | ||
| 291 | |||
| 292 | // Setup flash | ||
| 293 | let flash = BlockingTestFlash::new(BootLoaderConfig { | ||
| 294 | active: MemFlash::<0, 0, 0>::default(), | ||
| 295 | dfu: MemFlash::<4096, 4096, 4>::default(), | ||
| 296 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 297 | }); | ||
| 298 | |||
| 299 | let firmware_len = firmware.len(); | ||
| 300 | |||
| 301 | let mut write_buf = [0; 4096]; | ||
| 302 | write_buf[0..firmware_len].copy_from_slice(firmware); | ||
| 303 | flash.dfu().write(0, &write_buf).unwrap(); | ||
| 304 | |||
| 305 | // On with the test | ||
| 306 | let flash = flash.into_async(); | ||
| 307 | let mut aligned = [0; 4]; | ||
| 308 | let mut updater = FirmwareUpdater::new( | ||
| 309 | FirmwareUpdaterConfig { | ||
| 310 | dfu: flash.dfu(), | ||
| 311 | state: flash.state(), | ||
| 312 | }, | ||
| 313 | &mut aligned, | ||
| 314 | ); | ||
| 315 | |||
| 316 | assert!(block_on(updater.verify_and_mark_updated( | ||
| 317 | &public_key.to_bytes(), | ||
| 318 | &signature.to_bytes(), | ||
| 319 | firmware_len as u32, | ||
| 320 | )) | ||
| 321 | .is_ok()); | ||
| 322 | } | ||
| 323 | } | ||
diff --git a/embassy-boot/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs new file mode 100644 index 000000000..40f352c8d --- /dev/null +++ b/embassy-boot/src/mem_flash.rs | |||
| @@ -0,0 +1,170 @@ | |||
| 1 | #![allow(unused)] | ||
| 2 | |||
| 3 | use core::ops::{Bound, Range, RangeBounds}; | ||
| 4 | |||
| 5 | use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; | ||
| 6 | use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||
| 7 | |||
| 8 | pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> { | ||
| 9 | pub mem: [u8; SIZE], | ||
| 10 | pub pending_write_successes: Option<usize>, | ||
| 11 | } | ||
| 12 | |||
| 13 | #[derive(Debug)] | ||
| 14 | pub struct MemFlashError; | ||
| 15 | |||
| 16 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> { | ||
| 17 | pub const fn new(fill: u8) -> Self { | ||
| 18 | Self { | ||
| 19 | mem: [fill; SIZE], | ||
| 20 | pending_write_successes: None, | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | #[cfg(test)] | ||
| 25 | pub fn random() -> Self { | ||
| 26 | let mut mem = [0; SIZE]; | ||
| 27 | for byte in mem.iter_mut() { | ||
| 28 | *byte = rand::random::<u8>(); | ||
| 29 | } | ||
| 30 | Self { | ||
| 31 | mem, | ||
| 32 | pending_write_successes: None, | ||
| 33 | } | ||
| 34 | } | ||
| 35 | |||
| 36 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { | ||
| 37 | let len = bytes.len(); | ||
| 38 | bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); | ||
| 39 | Ok(()) | ||
| 40 | } | ||
| 41 | |||
| 42 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { | ||
| 43 | let offset = offset as usize; | ||
| 44 | assert!(bytes.len() % WRITE_SIZE == 0); | ||
| 45 | assert!(offset % WRITE_SIZE == 0); | ||
| 46 | assert!(offset + bytes.len() <= SIZE); | ||
| 47 | |||
| 48 | if let Some(pending_successes) = self.pending_write_successes { | ||
| 49 | if pending_successes > 0 { | ||
| 50 | self.pending_write_successes = Some(pending_successes - 1); | ||
| 51 | } else { | ||
| 52 | return Err(MemFlashError); | ||
| 53 | } | ||
| 54 | } | ||
| 55 | |||
| 56 | for ((offset, mem_byte), new_byte) in self | ||
| 57 | .mem | ||
| 58 | .iter_mut() | ||
| 59 | .enumerate() | ||
| 60 | .skip(offset) | ||
| 61 | .take(bytes.len()) | ||
| 62 | .zip(bytes) | ||
| 63 | { | ||
| 64 | assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); | ||
| 65 | *mem_byte = *new_byte; | ||
| 66 | } | ||
| 67 | |||
| 68 | Ok(()) | ||
| 69 | } | ||
| 70 | |||
| 71 | fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { | ||
| 72 | let from = from as usize; | ||
| 73 | let to = to as usize; | ||
| 74 | assert!(from % ERASE_SIZE == 0); | ||
| 75 | assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); | ||
| 76 | for i in from..to { | ||
| 77 | self.mem[i] = 0xFF; | ||
| 78 | } | ||
| 79 | Ok(()) | ||
| 80 | } | ||
| 81 | |||
| 82 | pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { | ||
| 83 | let offset = offset as usize; | ||
| 84 | assert!(bytes.len() % WRITE_SIZE == 0); | ||
| 85 | assert!(offset % WRITE_SIZE == 0); | ||
| 86 | assert!(offset + bytes.len() <= SIZE); | ||
| 87 | |||
| 88 | self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); | ||
| 89 | |||
| 90 | Ok(()) | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default | ||
| 95 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 96 | { | ||
| 97 | fn default() -> Self { | ||
| 98 | Self::new(0xFF) | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType | ||
| 103 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 104 | { | ||
| 105 | type Error = MemFlashError; | ||
| 106 | } | ||
| 107 | |||
| 108 | impl NorFlashError for MemFlashError { | ||
| 109 | fn kind(&self) -> NorFlashErrorKind { | ||
| 110 | NorFlashErrorKind::Other | ||
| 111 | } | ||
| 112 | } | ||
| 113 | |||
| 114 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash | ||
| 115 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 116 | { | ||
| 117 | const READ_SIZE: usize = 1; | ||
| 118 | |||
| 119 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||
| 120 | self.read(offset, bytes) | ||
| 121 | } | ||
| 122 | |||
| 123 | fn capacity(&self) -> usize { | ||
| 124 | SIZE | ||
| 125 | } | ||
| 126 | } | ||
| 127 | |||
| 128 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash | ||
| 129 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 130 | { | ||
| 131 | const WRITE_SIZE: usize = WRITE_SIZE; | ||
| 132 | const ERASE_SIZE: usize = ERASE_SIZE; | ||
| 133 | |||
| 134 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||
| 135 | self.write(offset, bytes) | ||
| 136 | } | ||
| 137 | |||
| 138 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 139 | self.erase(from, to) | ||
| 140 | } | ||
| 141 | } | ||
| 142 | |||
| 143 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash | ||
| 144 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 145 | { | ||
| 146 | const READ_SIZE: usize = 1; | ||
| 147 | |||
| 148 | async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||
| 149 | self.read(offset, bytes) | ||
| 150 | } | ||
| 151 | |||
| 152 | fn capacity(&self) -> usize { | ||
| 153 | SIZE | ||
| 154 | } | ||
| 155 | } | ||
| 156 | |||
| 157 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash | ||
| 158 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 159 | { | ||
| 160 | const WRITE_SIZE: usize = WRITE_SIZE; | ||
| 161 | const ERASE_SIZE: usize = ERASE_SIZE; | ||
| 162 | |||
| 163 | async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||
| 164 | self.write(offset, bytes) | ||
| 165 | } | ||
| 166 | |||
| 167 | async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 168 | self.erase(from, to) | ||
| 169 | } | ||
| 170 | } | ||
diff --git a/embassy-boot/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs new file mode 100644 index 000000000..3ac9e71ab --- /dev/null +++ b/embassy-boot/src/test_flash/asynch.rs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 3 | use embassy_sync::mutex::Mutex; | ||
| 4 | use embedded_storage_async::nor_flash::NorFlash; | ||
| 5 | |||
| 6 | use crate::BootLoaderConfig; | ||
| 7 | |||
| 8 | pub struct AsyncTestFlash<ACTIVE, DFU, STATE> | ||
| 9 | where | ||
| 10 | ACTIVE: NorFlash, | ||
| 11 | DFU: NorFlash, | ||
| 12 | STATE: NorFlash, | ||
| 13 | { | ||
| 14 | active: Mutex<NoopRawMutex, ACTIVE>, | ||
| 15 | dfu: Mutex<NoopRawMutex, DFU>, | ||
| 16 | state: Mutex<NoopRawMutex, STATE>, | ||
| 17 | } | ||
| 18 | |||
| 19 | impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE> | ||
| 20 | where | ||
| 21 | ACTIVE: NorFlash, | ||
| 22 | DFU: NorFlash, | ||
| 23 | STATE: NorFlash, | ||
| 24 | { | ||
| 25 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 26 | Self { | ||
| 27 | active: Mutex::new(config.active), | ||
| 28 | dfu: Mutex::new(config.dfu), | ||
| 29 | state: Mutex::new(config.state), | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> { | ||
| 34 | Self::create_partition(&self.active) | ||
| 35 | } | ||
| 36 | |||
| 37 | pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> { | ||
| 38 | Self::create_partition(&self.dfu) | ||
| 39 | } | ||
| 40 | |||
| 41 | pub fn state(&self) -> Partition<NoopRawMutex, STATE> { | ||
| 42 | Self::create_partition(&self.state) | ||
| 43 | } | ||
| 44 | |||
| 45 | fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> { | ||
| 46 | Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE> | ||
| 51 | where | ||
| 52 | ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, | ||
| 53 | DFU: NorFlash + embedded_storage::nor_flash::NorFlash, | ||
| 54 | STATE: NorFlash + embedded_storage::nor_flash::NorFlash, | ||
| 55 | { | ||
| 56 | pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> { | ||
| 57 | let config = BootLoaderConfig { | ||
| 58 | active: self.active.into_inner(), | ||
| 59 | dfu: self.dfu.into_inner(), | ||
| 60 | state: self.state.into_inner(), | ||
| 61 | }; | ||
| 62 | super::BlockingTestFlash::new(config) | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/embassy-boot/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs new file mode 100644 index 000000000..5ec476c65 --- /dev/null +++ b/embassy-boot/src/test_flash/blocking.rs | |||
| @@ -0,0 +1,68 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | |||
| 3 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 4 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 5 | use embassy_sync::blocking_mutex::Mutex; | ||
| 6 | use embedded_storage::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use crate::BootLoaderConfig; | ||
| 9 | |||
| 10 | pub struct BlockingTestFlash<ACTIVE, DFU, STATE> | ||
| 11 | where | ||
| 12 | ACTIVE: NorFlash, | ||
| 13 | DFU: NorFlash, | ||
| 14 | STATE: NorFlash, | ||
| 15 | { | ||
| 16 | active: Mutex<NoopRawMutex, RefCell<ACTIVE>>, | ||
| 17 | dfu: Mutex<NoopRawMutex, RefCell<DFU>>, | ||
| 18 | state: Mutex<NoopRawMutex, RefCell<STATE>>, | ||
| 19 | } | ||
| 20 | |||
| 21 | impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE> | ||
| 22 | where | ||
| 23 | ACTIVE: NorFlash, | ||
| 24 | DFU: NorFlash, | ||
| 25 | STATE: NorFlash, | ||
| 26 | { | ||
| 27 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 28 | Self { | ||
| 29 | active: Mutex::new(RefCell::new(config.active)), | ||
| 30 | dfu: Mutex::new(RefCell::new(config.dfu)), | ||
| 31 | state: Mutex::new(RefCell::new(config.state)), | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> { | ||
| 36 | Self::create_partition(&self.active) | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> { | ||
| 40 | Self::create_partition(&self.dfu) | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> { | ||
| 44 | Self::create_partition(&self.state) | ||
| 45 | } | ||
| 46 | |||
| 47 | pub fn create_partition<T: NorFlash>( | ||
| 48 | mutex: &Mutex<NoopRawMutex, RefCell<T>>, | ||
| 49 | ) -> BlockingPartition<NoopRawMutex, T> { | ||
| 50 | BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE> | ||
| 55 | where | ||
| 56 | ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||
| 57 | DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||
| 58 | STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||
| 59 | { | ||
| 60 | pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> { | ||
| 61 | let config = BootLoaderConfig { | ||
| 62 | active: self.active.into_inner().into_inner(), | ||
| 63 | dfu: self.dfu.into_inner().into_inner(), | ||
| 64 | state: self.state.into_inner().into_inner(), | ||
| 65 | }; | ||
| 66 | super::AsyncTestFlash::new(config) | ||
| 67 | } | ||
| 68 | } | ||
diff --git a/embassy-boot/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs new file mode 100644 index 000000000..79b15a081 --- /dev/null +++ b/embassy-boot/src/test_flash/mod.rs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | mod asynch; | ||
| 2 | mod blocking; | ||
| 3 | |||
| 4 | pub(crate) use asynch::AsyncTestFlash; | ||
| 5 | pub(crate) use blocking::BlockingTestFlash; | ||
