aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/boot/src/boot_loader.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-boot/boot/src/boot_loader.rs')
-rw-r--r--embassy-boot/boot/src/boot_loader.rs421
1 files changed, 421 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..a8c19197b
--- /dev/null
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -0,0 +1,421 @@
1use core::cell::RefCell;
2
3use embassy_embedded_hal::flash::partition::BlockingPartition;
4use embassy_sync::blocking_mutex::raw::NoopRawMutex;
5use embassy_sync::blocking_mutex::Mutex;
6use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
7
8use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
9
10/// Errors returned by bootloader
11#[derive(PartialEq, Eq, Debug)]
12pub enum BootError {
13 /// Error from flash.
14 Flash(NorFlashErrorKind),
15 /// Invalid bootloader magic
16 BadMagic,
17}
18
19#[cfg(feature = "defmt")]
20impl 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
29impl<E> From<E> for BootError
30where
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.
43pub 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
52impl<'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.
98pub 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
110impl<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 ///
154 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
155 /// as follows:
156 ///
157 /// +-----------+------------+--------+--------+--------+--------+
158 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
159 /// +-----------+------------+--------+--------+--------+--------+
160 /// | Active | 1 | 1 | 2 | 1 | - |
161 /// | DFU | 1 | 3 | 2 | 1 | 3 |
162 /// +-----------+------------+--------+--------+--------+--------+
163 ///
164 /// The next iteration performs the same steps
165 ///
166 /// +-----------+------------+--------+--------+--------+--------+
167 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
168 /// +-----------+------------+--------+--------+--------+--------+
169 /// | Active | 2 | 1 | 2 | 1 | - |
170 /// | DFU | 2 | 3 | 2 | 2 | 3 |
171 /// +-----------+------------+--------+--------+--------+--------+
172 ///
173 /// And again until we're done
174 ///
175 /// +-----------+------------+--------+--------+--------+--------+
176 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
177 /// +-----------+------------+--------+--------+--------+--------+
178 /// | Active | 3 | 3 | 2 | 1 | - |
179 /// | DFU | 3 | 3 | 1 | 2 | 3 |
180 /// +-----------+------------+--------+--------+--------+--------+
181 ///
182 /// REVERTING
183 ///
184 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
185 /// the application failed to mark the boot successful. In this case, the revert algorithm will
186 /// run.
187 ///
188 /// The revert index is located separately from the swap index, to ensure that revert can continue
189 /// on power failure.
190 ///
191 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
192 ///
193 /// +-----------+--------------+--------+--------+--------+--------+
194 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
195 //*/
196 /// +-----------+--------------+--------+--------+--------+--------+
197 /// | Active | 3 | 1 | 2 | 1 | - |
198 /// | DFU | 3 | 3 | 1 | 2 | 3 |
199 /// +-----------+--------------+--------+--------+--------+--------+
200 ///
201 ///
202 /// +-----------+--------------+--------+--------+--------+--------+
203 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
204 /// +-----------+--------------+--------+--------+--------+--------+
205 /// | Active | 3 | 1 | 2 | 1 | - |
206 /// | DFU | 3 | 3 | 2 | 2 | 3 |
207 /// +-----------+--------------+--------+--------+--------+--------+
208 ///
209 /// +-----------+--------------+--------+--------+--------+--------+
210 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
211 /// +-----------+--------------+--------+--------+--------+--------+
212 /// | Active | 3 | 1 | 2 | 3 | - |
213 /// | DFU | 3 | 3 | 2 | 1 | 3 |
214 /// +-----------+--------------+--------+--------+--------+--------+
215 ///
216 pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
217 // Ensure we have enough progress pages to store copy progress
218 assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32);
219 assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32);
220 assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32);
221 assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32);
222 assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32);
223 assert!(aligned_buf.len() >= STATE::WRITE_SIZE);
224 assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
225 assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
226
227 assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
228
229 // Copy contents from partition N to active
230 let state = self.read_state(aligned_buf)?;
231 if state == State::Swap {
232 //
233 // Check if we already swapped. If we're in the swap state, this means we should revert
234 // since the app has failed to mark boot as successful
235 //
236 if !self.is_swapped(aligned_buf)? {
237 trace!("Swapping");
238 self.swap(aligned_buf)?;
239 trace!("Swapping done");
240 } else {
241 trace!("Reverting");
242 self.revert(aligned_buf)?;
243
244 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
245
246 // Invalidate progress
247 state_word.fill(!STATE_ERASE_VALUE);
248 self.state.write(STATE::WRITE_SIZE as u32, state_word)?;
249
250 // Clear magic and progress
251 self.state.erase(0, self.state.capacity() as u32)?;
252
253 // Set magic
254 state_word.fill(BOOT_MAGIC);
255 self.state.write(0, state_word)?;
256 }
257 }
258 Ok(state)
259 }
260
261 fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
262 let page_count = self.active.capacity() / Self::PAGE_SIZE as usize;
263 let progress = self.current_progress(aligned_buf)?;
264
265 Ok(progress >= page_count * 2)
266 }
267
268 fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
269 let write_size = STATE::WRITE_SIZE as u32;
270 let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2;
271 let state_word = &mut aligned_buf[..write_size as usize];
272
273 self.state.read(write_size, state_word)?;
274 if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) {
275 // Progress is invalid
276 return Ok(max_index);
277 }
278
279 for index in 0..max_index {
280 self.state.read((2 + index) as u32 * write_size, state_word)?;
281
282 if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) {
283 return Ok(index);
284 }
285 }
286 Ok(max_index)
287 }
288
289 fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> {
290 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
291 state_word.fill(!STATE_ERASE_VALUE);
292 self.state
293 .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?;
294 Ok(())
295 }
296
297 fn copy_page_once_to_active(
298 &mut self,
299 progress_index: usize,
300 from_offset: u32,
301 to_offset: u32,
302 aligned_buf: &mut [u8],
303 ) -> Result<(), BootError> {
304 if self.current_progress(aligned_buf)? <= progress_index {
305 let page_size = Self::PAGE_SIZE as u32;
306
307 self.active.erase(to_offset, to_offset + page_size)?;
308
309 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
310 self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?;
311 self.active.write(to_offset + offset_in_page as u32, aligned_buf)?;
312 }
313
314 self.update_progress(progress_index, aligned_buf)?;
315 }
316 Ok(())
317 }
318
319 fn copy_page_once_to_dfu(
320 &mut self,
321 progress_index: usize,
322 from_offset: u32,
323 to_offset: u32,
324 aligned_buf: &mut [u8],
325 ) -> Result<(), BootError> {
326 if self.current_progress(aligned_buf)? <= progress_index {
327 let page_size = Self::PAGE_SIZE as u32;
328
329 self.dfu.erase(to_offset as u32, to_offset + page_size)?;
330
331 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
332 self.active.read(from_offset + offset_in_page as u32, aligned_buf)?;
333 self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?;
334 }
335
336 self.update_progress(progress_index, aligned_buf)?;
337 }
338 Ok(())
339 }
340
341 fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
342 let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
343 for page_num in 0..page_count {
344 let progress_index = (page_num * 2) as usize;
345
346 // Copy active page to the 'next' DFU page.
347 let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
348 let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE;
349 //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
350 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
351
352 // Copy DFU page to the active page
353 let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
354 let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE;
355 //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
356 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
357 }
358
359 Ok(())
360 }
361
362 fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> {
363 let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE;
364 for page_num in 0..page_count {
365 let progress_index = (page_count * 2 + page_num * 2) as usize;
366
367 // Copy the bad active page to the DFU page
368 let active_from_offset = page_num * Self::PAGE_SIZE;
369 let dfu_to_offset = page_num * Self::PAGE_SIZE;
370 self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?;
371
372 // Copy the DFU page back to the active page
373 let active_to_offset = page_num * Self::PAGE_SIZE;
374 let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE;
375 self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?;
376 }
377
378 Ok(())
379 }
380
381 fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
382 let state_word = &mut aligned_buf[..STATE::WRITE_SIZE];
383 self.state.read(0, state_word)?;
384
385 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
386 Ok(State::Swap)
387 } else {
388 Ok(State::Boot)
389 }
390 }
391}
392
393fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
394 active: &ACTIVE,
395 dfu: &DFU,
396 state: &STATE,
397 page_size: u32,
398) {
399 assert_eq!(active.capacity() as u32 % page_size, 0);
400 assert_eq!(dfu.capacity() as u32 % page_size, 0);
401 assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size);
402 assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32);
403}
404
405#[cfg(test)]
406mod tests {
407 use super::*;
408 use crate::mem_flash::MemFlash;
409
410 #[test]
411 #[should_panic]
412 fn test_range_asserts() {
413 const ACTIVE_SIZE: usize = 4194304 - 4096;
414 const DFU_SIZE: usize = 4194304;
415 const STATE_SIZE: usize = 4096;
416 static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF);
417 static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF);
418 static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF);
419 assert_partitions(&ACTIVE, &DFU, &STATE, 4096);
420 }
421}