aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/boot/src/boot_loader.rs
diff options
context:
space:
mode:
authorsander <[email protected]>2023-04-11 13:48:34 +0200
committersander <[email protected]>2023-04-11 13:48:34 +0200
commitc309797488a4f33dfab659fdb2908eff76e16e40 (patch)
tree2ee72042d9f06bf694fea8dd4cd2ba3e2e2cb10b /embassy-boot/boot/src/boot_loader.rs
parent6b2aaacf830d69fcb05f9611d3780f56b4ae82bc (diff)
parentb150b9506b2f0502065dc1b22eccc6448f611bff (diff)
merge embassy/master
Diffstat (limited to 'embassy-boot/boot/src/boot_loader.rs')
-rw-r--r--embassy-boot/boot/src/boot_loader.rs533
1 files changed, 533 insertions, 0 deletions
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
new file mode 100644
index 000000000..b959de2c4
--- /dev/null
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -0,0 +1,533 @@
1use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
2
3use crate::{Partition, State, BOOT_MAGIC, SWAP_MAGIC};
4
5/// Errors returned by bootloader
6#[derive(PartialEq, Eq, Debug)]
7pub enum BootError {
8 /// Error from flash.
9 Flash(NorFlashErrorKind),
10 /// Invalid bootloader magic
11 BadMagic,
12}
13
14#[cfg(feature = "defmt")]
15impl defmt::Format for BootError {
16 fn format(&self, fmt: defmt::Formatter) {
17 match self {
18 BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"),
19 BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"),
20 }
21 }
22}
23
24impl<E> From<E> for BootError
25where
26 E: NorFlashError,
27{
28 fn from(error: E) -> Self {
29 BootError::Flash(error.kind())
30 }
31}
32
33/// Trait defining the flash handles used for active and DFU partition.
34pub trait FlashConfig {
35 /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
36 const STATE_ERASE_VALUE: u8 = 0xFF;
37 /// Flash type used for the state partition.
38 type STATE: NorFlash;
39 /// Flash type used for the active partition.
40 type ACTIVE: NorFlash;
41 /// Flash type used for the dfu partition.
42 type DFU: NorFlash;
43
44 /// Return flash instance used to write/read to/from active partition.
45 fn active(&mut self) -> &mut Self::ACTIVE;
46 /// Return flash instance used to write/read to/from dfu partition.
47 fn dfu(&mut self) -> &mut Self::DFU;
48 /// Return flash instance used to write/read to/from bootloader state.
49 fn state(&mut self) -> &mut Self::STATE;
50}
51
52trait FlashConfigEx {
53 fn page_size() -> u32;
54}
55
56impl<T: FlashConfig> FlashConfigEx for T {
57 /// Get the page size which is the "unit of operation" within the bootloader.
58 fn page_size() -> u32 {
59 core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE) as u32
60 }
61}
62
63/// BootLoader works with any flash implementing embedded_storage.
64pub struct BootLoader {
65 // Page with current state of bootloader. The state partition has the following format:
66 // All ranges are in multiples of WRITE_SIZE bytes.
67 // | Range | Description |
68 // | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
69 // | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
70 // | 2..2 + N | Progress index used while swapping or reverting |
71 state: Partition,
72 // Location of the partition which will be booted from
73 active: Partition,
74 // Location of the partition which will be swapped in when requested
75 dfu: Partition,
76}
77
78impl BootLoader {
79 /// Create a new instance of a bootloader with the given partitions.
80 ///
81 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
82 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
83 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
84 Self { active, dfu, state }
85 }
86
87 /// Return the offset of the active partition into the active flash.
88 pub fn boot_address(&self) -> usize {
89 self.active.from as usize
90 }
91
92 /// Perform necessary boot preparations like swapping images.
93 ///
94 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
95 /// algorithm to work correctly.
96 ///
97 /// The provided aligned_buf argument must satisfy any alignment requirements
98 /// given by the partition flashes. All flash operations will use this buffer.
99 ///
100 /// SWAPPING
101 ///
102 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
103 /// The swap index contains the copy progress, as to allow continuation of the copy process on
104 /// power failure. The index counter is represented within 1 or more pages (depending on total
105 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
106 /// contains a zero value. This ensures that index updates can be performed atomically and
107 /// avoid a situation where the wrong index value is set (page write size is "atomic").
108 ///
109 /// +-----------+------------+--------+--------+--------+--------+
110 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
111 /// +-----------+------------+--------+--------+--------+--------+
112 /// | Active | 0 | 1 | 2 | 3 | - |
113 /// | DFU | 0 | 3 | 2 | 1 | X |
114 /// +-----------+------------+--------+--------+--------+--------+
115 ///
116 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
117 /// as follows:
118 ///
119 /// +-----------+------------+--------+--------+--------+--------+
120 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
121 /// +-----------+------------+--------+--------+--------+--------+
122 /// | Active | 1 | 1 | 2 | 1 | - |
123 /// | DFU | 1 | 3 | 2 | 1 | 3 |
124 /// +-----------+------------+--------+--------+--------+--------+
125 ///
126 /// The next iteration performs the same steps
127 ///
128 /// +-----------+------------+--------+--------+--------+--------+
129 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
130 /// +-----------+------------+--------+--------+--------+--------+
131 /// | Active | 2 | 1 | 2 | 1 | - |
132 /// | DFU | 2 | 3 | 2 | 2 | 3 |
133 /// +-----------+------------+--------+--------+--------+--------+
134 ///
135 /// And again until we're done
136 ///
137 /// +-----------+------------+--------+--------+--------+--------+
138 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
139 /// +-----------+------------+--------+--------+--------+--------+
140 /// | Active | 3 | 3 | 2 | 1 | - |
141 /// | DFU | 3 | 3 | 1 | 2 | 3 |
142 /// +-----------+------------+--------+--------+--------+--------+
143 ///
144 /// REVERTING
145 ///
146 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
147 /// the application failed to mark the boot successful. In this case, the revert algorithm will
148 /// run.
149 ///
150 /// The revert index is located separately from the swap index, to ensure that revert can continue
151 /// on power failure.
152 ///
153 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
154 ///
155 /// +-----------+--------------+--------+--------+--------+--------+
156 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
157 //*/
158 /// +-----------+--------------+--------+--------+--------+--------+
159 /// | Active | 3 | 1 | 2 | 1 | - |
160 /// | DFU | 3 | 3 | 1 | 2 | 3 |
161 /// +-----------+--------------+--------+--------+--------+--------+
162 ///
163 ///
164 /// +-----------+--------------+--------+--------+--------+--------+
165 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
166 /// +-----------+--------------+--------+--------+--------+--------+
167 /// | Active | 3 | 1 | 2 | 1 | - |
168 /// | DFU | 3 | 3 | 2 | 2 | 3 |
169 /// +-----------+--------------+--------+--------+--------+--------+
170 ///
171 /// +-----------+--------------+--------+--------+--------+--------+
172 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
173 /// +-----------+--------------+--------+--------+--------+--------+
174 /// | Active | 3 | 1 | 2 | 3 | - |
175 /// | DFU | 3 | 3 | 2 | 1 | 3 |
176 /// +-----------+--------------+--------+--------+--------+--------+
177 ///
178 pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
179 // Ensure we have enough progress pages to store copy progress
180 assert_eq!(0, P::page_size() % aligned_buf.len() as u32);
181 assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE as u32);
182 assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE as u32);
183 assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE as u32);
184 assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE as u32);
185 assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
186 assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
187 assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
188 assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
189
190 // Copy contents from partition N to active
191 let state = self.read_state(p, aligned_buf)?;
192 if state == State::Swap {
193 //
194 // Check if we already swapped. If we're in the swap state, this means we should revert
195 // since the app has failed to mark boot as successful
196 //
197 if !self.is_swapped(p, aligned_buf)? {
198 trace!("Swapping");
199 self.swap(p, aligned_buf)?;
200 trace!("Swapping done");
201 } else {
202 trace!("Reverting");
203 self.revert(p, aligned_buf)?;
204
205 let state_flash = p.state();
206 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
207
208 // Invalidate progress
209 state_word.fill(!P::STATE_ERASE_VALUE);
210 self.state
211 .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
212
213 // Clear magic and progress
214 self.state.wipe_blocking(state_flash)?;
215
216 // Set magic
217 state_word.fill(BOOT_MAGIC);
218 self.state.write_blocking(state_flash, 0, state_word)?;
219 }
220 }
221 Ok(state)
222 }
223
224 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
225 let page_count = (self.active.size() / P::page_size()) as usize;
226 let progress = self.current_progress(p, aligned_buf)?;
227
228 Ok(progress >= page_count * 2)
229 }
230
231 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
232 let write_size = P::STATE::WRITE_SIZE as u32;
233 let max_index = (((self.state.size() - write_size) / write_size) - 2) as usize;
234 let state_flash = config.state();
235 let state_word = &mut aligned_buf[..write_size as usize];
236
237 self.state.read_blocking(state_flash, write_size, state_word)?;
238 if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
239 // Progress is invalid
240 return Ok(max_index);
241 }
242
243 for index in 0..max_index {
244 self.state
245 .read_blocking(state_flash, (2 + index) as u32 * write_size, state_word)?;
246
247 if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
248 return Ok(index);
249 }
250 }
251 Ok(max_index)
252 }
253
254 fn update_progress<P: FlashConfig>(
255 &mut self,
256 progress_index: usize,
257 p: &mut P,
258 aligned_buf: &mut [u8],
259 ) -> Result<(), BootError> {
260 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
261 state_word.fill(!P::STATE_ERASE_VALUE);
262 self.state.write_blocking(
263 p.state(),
264 (2 + progress_index) as u32 * P::STATE::WRITE_SIZE as u32,
265 state_word,
266 )?;
267 Ok(())
268 }
269
270 fn copy_page_once_to_active<P: FlashConfig>(
271 &mut self,
272 progress_index: usize,
273 from_offset: u32,
274 to_offset: u32,
275 p: &mut P,
276 aligned_buf: &mut [u8],
277 ) -> Result<(), BootError> {
278 if self.current_progress(p, aligned_buf)? <= progress_index {
279 let page_size = P::page_size() as u32;
280
281 self.active
282 .erase_blocking(p.active(), to_offset, to_offset + page_size)?;
283
284 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
285 self.dfu
286 .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
287 self.active
288 .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
289 }
290
291 self.update_progress(progress_index, p, aligned_buf)?;
292 }
293 Ok(())
294 }
295
296 fn copy_page_once_to_dfu<P: FlashConfig>(
297 &mut self,
298 progress_index: usize,
299 from_offset: u32,
300 to_offset: u32,
301 p: &mut P,
302 aligned_buf: &mut [u8],
303 ) -> Result<(), BootError> {
304 if self.current_progress(p, aligned_buf)? <= progress_index {
305 let page_size = P::page_size() as u32;
306
307 self.dfu
308 .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
309
310 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
311 self.active
312 .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
313 self.dfu
314 .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
315 }
316
317 self.update_progress(progress_index, p, aligned_buf)?;
318 }
319 Ok(())
320 }
321
322 fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
323 let page_size = P::page_size();
324 let page_count = self.active.size() / page_size;
325 for page_num in 0..page_count {
326 let progress_index = (page_num * 2) as usize;
327
328 // Copy active page to the 'next' DFU page.
329 let active_from_offset = (page_count - 1 - page_num) * page_size;
330 let dfu_to_offset = (page_count - page_num) * page_size;
331 //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
332 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
333
334 // Copy DFU page to the active page
335 let active_to_offset = (page_count - 1 - page_num) * page_size;
336 let dfu_from_offset = (page_count - 1 - page_num) * page_size;
337 //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
338 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
339 }
340
341 Ok(())
342 }
343
344 fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
345 let page_size = P::page_size();
346 let page_count = self.active.size() / page_size;
347 for page_num in 0..page_count {
348 let progress_index = (page_count * 2 + page_num * 2) as usize;
349
350 // Copy the bad active page to the DFU page
351 let active_from_offset = page_num * page_size;
352 let dfu_to_offset = page_num * page_size;
353 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, p, aligned_buf)?;
354
355 // Copy the DFU page back to the active page
356 let active_to_offset = page_num * page_size;
357 let dfu_from_offset = (page_num + 1) * page_size;
358 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
359 }
360
361 Ok(())
362 }
363
364 fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
365 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
366 self.state.read_blocking(config.state(), 0, state_word)?;
367
368 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
369 Ok(State::Swap)
370 } else {
371 Ok(State::Boot)
372 }
373 }
374}
375
376fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_size: u32, state_write_size: usize) {
377 assert_eq!(active.size() % page_size, 0);
378 assert_eq!(dfu.size() % page_size, 0);
379 assert!(dfu.size() - active.size() >= page_size);
380 assert!(2 + 2 * (active.size() / page_size) <= state.size() / state_write_size as u32);
381}
382
383/// A flash wrapper implementing the Flash and embedded_storage traits.
384pub struct BootFlash<F>
385where
386 F: NorFlash,
387{
388 flash: F,
389}
390
391impl<F> BootFlash<F>
392where
393 F: NorFlash,
394{
395 /// Create a new instance of a bootable flash
396 pub fn new(flash: F) -> Self {
397 Self { flash }
398 }
399}
400
401impl<F> ErrorType for BootFlash<F>
402where
403 F: NorFlash,
404{
405 type Error = F::Error;
406}
407
408impl<F> NorFlash for BootFlash<F>
409where
410 F: NorFlash,
411{
412 const WRITE_SIZE: usize = F::WRITE_SIZE;
413 const ERASE_SIZE: usize = F::ERASE_SIZE;
414
415 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
416 F::erase(&mut self.flash, from, to)
417 }
418
419 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
420 F::write(&mut self.flash, offset, bytes)
421 }
422}
423
424impl<F> ReadNorFlash for BootFlash<F>
425where
426 F: NorFlash,
427{
428 const READ_SIZE: usize = F::READ_SIZE;
429
430 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
431 F::read(&mut self.flash, offset, bytes)
432 }
433
434 fn capacity(&self) -> usize {
435 F::capacity(&self.flash)
436 }
437}
438
439/// Convenience provider that uses a single flash for all partitions.
440pub struct SingleFlashConfig<'a, F>
441where
442 F: NorFlash,
443{
444 flash: &'a mut F,
445}
446
447impl<'a, F> SingleFlashConfig<'a, F>
448where
449 F: NorFlash,
450{
451 /// Create a provider for a single flash.
452 pub fn new(flash: &'a mut F) -> Self {
453 Self { flash }
454 }
455}
456
457impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
458where
459 F: NorFlash,
460{
461 type STATE = F;
462 type ACTIVE = F;
463 type DFU = F;
464
465 fn active(&mut self) -> &mut Self::STATE {
466 self.flash
467 }
468 fn dfu(&mut self) -> &mut Self::ACTIVE {
469 self.flash
470 }
471 fn state(&mut self) -> &mut Self::DFU {
472 self.flash
473 }
474}
475
476/// Convenience flash provider that uses separate flash instances for each partition.
477pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
478where
479 ACTIVE: NorFlash,
480 STATE: NorFlash,
481 DFU: NorFlash,
482{
483 active: &'a mut ACTIVE,
484 state: &'a mut STATE,
485 dfu: &'a mut DFU,
486}
487
488impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
489where
490 ACTIVE: NorFlash,
491 STATE: NorFlash,
492 DFU: NorFlash,
493{
494 /// Create a new flash provider with separate configuration for all three partitions.
495 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
496 Self { active, state, dfu }
497 }
498}
499
500impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
501where
502 ACTIVE: NorFlash,
503 STATE: NorFlash,
504 DFU: NorFlash,
505{
506 type STATE = STATE;
507 type ACTIVE = ACTIVE;
508 type DFU = DFU;
509
510 fn active(&mut self) -> &mut Self::ACTIVE {
511 self.active
512 }
513 fn dfu(&mut self) -> &mut Self::DFU {
514 self.dfu
515 }
516 fn state(&mut self) -> &mut Self::STATE {
517 self.state
518 }
519}
520
521#[cfg(test)]
522mod tests {
523 use super::*;
524
525 #[test]
526 #[should_panic]
527 fn test_range_asserts() {
528 const ACTIVE: Partition = Partition::new(4096, 4194304);
529 const DFU: Partition = Partition::new(4194304, 2 * 4194304);
530 const STATE: Partition = Partition::new(0, 4096);
531 assert_partitions(ACTIVE, DFU, STATE, 4096, 4);
532 }
533}