aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/boot/src
diff options
context:
space:
mode:
authorQuentin Smith <[email protected]>2023-07-17 21:31:43 -0400
committerQuentin Smith <[email protected]>2023-07-17 21:31:43 -0400
commit6f02403184eb7fb7990fb88fc9df9c4328a690a3 (patch)
tree748f510e190bb2724750507a6e69ed1a8e08cb20 /embassy-boot/boot/src
parentd896f80405aa8963877049ed999e4aba25d6e2bb (diff)
parent6b5df4523aa1c4902f02e803450ae4b418e0e3ca (diff)
Merge remote-tracking branch 'origin/main' into nrf-pdm
Diffstat (limited to 'embassy-boot/boot/src')
-rw-r--r--embassy-boot/boot/src/boot_loader.rs421
-rw-r--r--embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs30
-rw-r--r--embassy-boot/boot/src/digest_adapters/mod.rs5
-rw-r--r--embassy-boot/boot/src/digest_adapters/salty.rs29
-rw-r--r--embassy-boot/boot/src/firmware_updater/asynch.rs299
-rw-r--r--embassy-boot/boot/src/firmware_updater/blocking.rs302
-rw-r--r--embassy-boot/boot/src/firmware_updater/mod.rs51
-rw-r--r--embassy-boot/boot/src/lib.rs1138
-rw-r--r--embassy-boot/boot/src/mem_flash.rs173
-rw-r--r--embassy-boot/boot/src/test_flash/asynch.rs64
-rw-r--r--embassy-boot/boot/src/test_flash/blocking.rs69
-rw-r--r--embassy-boot/boot/src/test_flash/mod.rs7
12 files changed, 1694 insertions, 894 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}
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs
new file mode 100644
index 000000000..a184d1c51
--- /dev/null
+++ b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs
@@ -0,0 +1,30 @@
1use digest::typenum::U64;
2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
3use ed25519_dalek::Digest as _;
4
5pub struct Sha512(ed25519_dalek::Sha512);
6
7impl Default for Sha512 {
8 fn default() -> Self {
9 Self(ed25519_dalek::Sha512::new())
10 }
11}
12
13impl Update for Sha512 {
14 fn update(&mut self, data: &[u8]) {
15 self.0.update(data)
16 }
17}
18
19impl 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
26impl OutputSizeUser for Sha512 {
27 type OutputSize = U64;
28}
29
30impl HashMarker for Sha512 {}
diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs
new file mode 100644
index 000000000..9b4b4b60c
--- /dev/null
+++ b/embassy-boot/boot/src/digest_adapters/mod.rs
@@ -0,0 +1,5 @@
1#[cfg(feature = "ed25519-dalek")]
2pub(crate) mod ed25519_dalek;
3
4#[cfg(feature = "ed25519-salty")]
5pub(crate) mod salty;
diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs
new file mode 100644
index 000000000..2b5dcf3af
--- /dev/null
+++ b/embassy-boot/boot/src/digest_adapters/salty.rs
@@ -0,0 +1,29 @@
1use digest::typenum::U64;
2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
3
4pub struct Sha512(salty::Sha512);
5
6impl Default for Sha512 {
7 fn default() -> Self {
8 Self(salty::Sha512::new())
9 }
10}
11
12impl Update for Sha512 {
13 fn update(&mut self, data: &[u8]) {
14 self.0.update(data)
15 }
16}
17
18impl 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
25impl OutputSizeUser for Sha512 {
26 type OutputSize = U64;
27}
28
29impl HashMarker for Sha512 {}
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs
new file mode 100644
index 000000000..20731ee0a
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/asynch.rs
@@ -0,0 +1,299 @@
1use digest::Digest;
2#[cfg(target_os = "none")]
3use embassy_embedded_hal::flash::partition::Partition;
4#[cfg(target_os = "none")]
5use embassy_sync::blocking_mutex::raw::NoopRawMutex;
6use embedded_storage_async::nor_flash::NorFlash;
7
8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_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
13pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU,
15 state: STATE,
16}
17
18#[cfg(target_os = "none")]
19impl<'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
50impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<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>) -> Self {
53 Self {
54 dfu: config.dfu,
55 state: config.state,
56 }
57 }
58
59 // Make sure we are running a booted firmware to avoid reverting to a bad state.
60 async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
61 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
62 if self.get_state(aligned).await? == State::Boot {
63 Ok(())
64 } else {
65 Err(FirmwareUpdaterError::BadState)
66 }
67 }
68
69 /// Obtain the current state.
70 ///
71 /// This is useful to check if the bootloader has just done a swap, in order
72 /// to do verifications and self-tests of the new image before calling
73 /// `mark_booted`.
74 pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
75 self.state.read(0, aligned).await?;
76
77 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
78 Ok(State::Swap)
79 } else {
80 Ok(State::Boot)
81 }
82 }
83
84 /// Verify the DFU given a public key. If there is an error then DO NOT
85 /// proceed with updating the firmware as it must be signed with a
86 /// corresponding private key (otherwise it could be malicious firmware).
87 ///
88 /// Mark to trigger firmware swap on next boot if verify suceeds.
89 ///
90 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
91 /// been generated from a SHA-512 digest of the firmware bytes.
92 ///
93 /// If no signature feature is set then this method will always return a
94 /// signature error.
95 ///
96 /// # Safety
97 ///
98 /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
99 /// and written to.
100 #[cfg(feature = "_verify")]
101 pub async fn verify_and_mark_updated(
102 &mut self,
103 _public_key: &[u8],
104 _signature: &[u8],
105 _update_len: u32,
106 _aligned: &mut [u8],
107 ) -> Result<(), FirmwareUpdaterError> {
108 assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
109 assert!(_update_len <= self.dfu.capacity() as u32);
110
111 self.verify_booted(_aligned).await?;
112
113 #[cfg(feature = "ed25519-dalek")]
114 {
115 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
116
117 use crate::digest_adapters::ed25519_dalek::Sha512;
118
119 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
120
121 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
122 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
123
124 let mut message = [0; 64];
125 self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
126
127 public_key.verify(&message, &signature).map_err(into_signature_error)?
128 }
129 #[cfg(feature = "ed25519-salty")]
130 {
131 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
132 use salty::{PublicKey, Signature};
133
134 use crate::digest_adapters::salty::Sha512;
135
136 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
137 FirmwareUpdaterError::Signature(signature::Error::default())
138 }
139
140 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
141 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
142 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
143 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
144
145 let mut message = [0; 64];
146 self.hash::<Sha512>(_update_len, _aligned, &mut message).await?;
147
148 let r = public_key.verify(&message, &signature);
149 trace!(
150 "Verifying with public key {}, signature {} and message {} yields ok: {}",
151 public_key.to_bytes(),
152 signature.to_bytes(),
153 message,
154 r.is_ok()
155 );
156 r.map_err(into_signature_error)?
157 }
158
159 self.set_magic(_aligned, SWAP_MAGIC).await
160 }
161
162 /// Verify the update in DFU with any digest.
163 pub async fn hash<D: Digest>(
164 &mut self,
165 update_len: u32,
166 chunk_buf: &mut [u8],
167 output: &mut [u8],
168 ) -> Result<(), FirmwareUpdaterError> {
169 let mut digest = D::new();
170 for offset in (0..update_len).step_by(chunk_buf.len()) {
171 self.dfu.read(offset, chunk_buf).await?;
172 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
173 digest.update(&chunk_buf[..len]);
174 }
175 output.copy_from_slice(digest.finalize().as_slice());
176 Ok(())
177 }
178
179 /// Mark to trigger firmware swap on next boot.
180 ///
181 /// # Safety
182 ///
183 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
184 #[cfg(not(feature = "_verify"))]
185 pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
186 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
187 self.set_magic(aligned, SWAP_MAGIC).await
188 }
189
190 /// Mark firmware boot successful and stop rollback on reset.
191 ///
192 /// # Safety
193 ///
194 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
195 pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
196 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
197 self.set_magic(aligned, BOOT_MAGIC).await
198 }
199
200 async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
201 self.state.read(0, aligned).await?;
202
203 if aligned.iter().any(|&b| b != magic) {
204 // Read progress validity
205 self.state.read(STATE::WRITE_SIZE as u32, aligned).await?;
206
207 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
208 // The current progress validity marker is invalid
209 } else {
210 // Invalidate progress
211 aligned.fill(!STATE_ERASE_VALUE);
212 self.state.write(STATE::WRITE_SIZE as u32, aligned).await?;
213 }
214
215 // Clear magic and progress
216 self.state.erase(0, self.state.capacity() as u32).await?;
217
218 // Set magic
219 aligned.fill(magic);
220 self.state.write(0, aligned).await?;
221 }
222 Ok(())
223 }
224
225 /// Write data to a flash page.
226 ///
227 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
228 ///
229 /// # Safety
230 ///
231 /// Failing to meet alignment and size requirements may result in a panic.
232 pub async fn write_firmware(
233 &mut self,
234 aligned: &mut [u8],
235 offset: usize,
236 data: &[u8],
237 ) -> Result<(), FirmwareUpdaterError> {
238 assert!(data.len() >= DFU::ERASE_SIZE);
239 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
240
241 self.verify_booted(aligned).await?;
242
243 self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?;
244
245 self.dfu.write(offset as u32, data).await?;
246
247 Ok(())
248 }
249
250 /// Prepare for an incoming DFU update by erasing the entire DFU area and
251 /// returning its `Partition`.
252 ///
253 /// Using this instead of `write_firmware` allows for an optimized API in
254 /// exchange for added complexity.
255 ///
256 /// # Safety
257 ///
258 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
259 pub async fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
260 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
261 self.verify_booted(aligned).await?;
262
263 self.dfu.erase(0, self.dfu.capacity() as u32).await?;
264
265 Ok(&mut self.dfu)
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use embassy_embedded_hal::flash::partition::Partition;
272 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
273 use embassy_sync::mutex::Mutex;
274 use futures::executor::block_on;
275 use sha1::{Digest, Sha1};
276
277 use super::*;
278 use crate::mem_flash::MemFlash;
279
280 #[test]
281 fn can_verify_sha1() {
282 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
283 let state = Partition::new(&flash, 0, 4096);
284 let dfu = Partition::new(&flash, 65536, 65536);
285 let mut aligned = [0; 8];
286
287 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
288 let mut to_write = [0; 4096];
289 to_write[..7].copy_from_slice(update.as_slice());
290
291 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
292 block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap();
293 let mut chunk_buf = [0; 2];
294 let mut hash = [0; 20];
295 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
296
297 assert_eq!(Sha1::digest(update).as_slice(), hash);
298 }
299}
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs
new file mode 100644
index 000000000..f03f53e4d
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/blocking.rs
@@ -0,0 +1,302 @@
1use digest::Digest;
2#[cfg(target_os = "none")]
3use embassy_embedded_hal::flash::partition::BlockingPartition;
4#[cfg(target_os = "none")]
5use embassy_sync::blocking_mutex::raw::NoopRawMutex;
6use embedded_storage::nor_flash::NorFlash;
7
8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_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
13pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU,
15 state: STATE,
16}
17
18#[cfg(target_os = "none")]
19impl<'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
52impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> {
53 /// Create a firmware updater instance with partition ranges for the update and state partitions.
54 pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self {
55 Self {
56 dfu: config.dfu,
57 state: config.state,
58 }
59 }
60
61 // Make sure we are running a booted firmware to avoid reverting to a bad state.
62 fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
63 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
64 if self.get_state(aligned)? == State::Boot {
65 Ok(())
66 } else {
67 Err(FirmwareUpdaterError::BadState)
68 }
69 }
70
71 /// Obtain the current state.
72 ///
73 /// This is useful to check if the bootloader has just done a swap, in order
74 /// to do verifications and self-tests of the new image before calling
75 /// `mark_booted`.
76 pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> {
77 self.state.read(0, aligned)?;
78
79 if !aligned.iter().any(|&b| b != SWAP_MAGIC) {
80 Ok(State::Swap)
81 } else {
82 Ok(State::Boot)
83 }
84 }
85
86 /// Verify the DFU given a public key. If there is an error then DO NOT
87 /// proceed with updating the firmware as it must be signed with a
88 /// corresponding private key (otherwise it could be malicious firmware).
89 ///
90 /// Mark to trigger firmware swap on next boot if verify suceeds.
91 ///
92 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
93 /// been generated from a SHA-512 digest of the firmware bytes.
94 ///
95 /// If no signature feature is set then this method will always return a
96 /// signature error.
97 ///
98 /// # Safety
99 ///
100 /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
101 /// and written to.
102 #[cfg(feature = "_verify")]
103 pub fn verify_and_mark_updated(
104 &mut self,
105 _public_key: &[u8],
106 _signature: &[u8],
107 _update_len: u32,
108 _aligned: &mut [u8],
109 ) -> Result<(), FirmwareUpdaterError> {
110 assert_eq!(_aligned.len(), STATE::WRITE_SIZE);
111 assert!(_update_len <= self.dfu.capacity() as u32);
112
113 self.verify_booted(_aligned)?;
114
115 #[cfg(feature = "ed25519-dalek")]
116 {
117 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier};
118
119 use crate::digest_adapters::ed25519_dalek::Sha512;
120
121 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
122
123 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?;
124 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?;
125
126 let mut message = [0; 64];
127 self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
128
129 public_key.verify(&message, &signature).map_err(into_signature_error)?
130 }
131 #[cfg(feature = "ed25519-salty")]
132 {
133 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
134 use salty::{PublicKey, Signature};
135
136 use crate::digest_adapters::salty::Sha512;
137
138 fn into_signature_error<E>(_: E) -> FirmwareUpdaterError {
139 FirmwareUpdaterError::Signature(signature::Error::default())
140 }
141
142 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?;
143 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?;
144 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
145 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
146
147 let mut message = [0; 64];
148 self.hash::<Sha512>(_update_len, _aligned, &mut message)?;
149
150 let r = public_key.verify(&message, &signature);
151 trace!(
152 "Verifying with public key {}, signature {} and message {} yields ok: {}",
153 public_key.to_bytes(),
154 signature.to_bytes(),
155 message,
156 r.is_ok()
157 );
158 r.map_err(into_signature_error)?
159 }
160
161 self.set_magic(_aligned, SWAP_MAGIC)
162 }
163
164 /// Verify the update in DFU with any digest.
165 pub fn hash<D: Digest>(
166 &mut self,
167 update_len: u32,
168 chunk_buf: &mut [u8],
169 output: &mut [u8],
170 ) -> Result<(), FirmwareUpdaterError> {
171 let mut digest = D::new();
172 for offset in (0..update_len).step_by(chunk_buf.len()) {
173 self.dfu.read(offset, chunk_buf)?;
174 let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len());
175 digest.update(&chunk_buf[..len]);
176 }
177 output.copy_from_slice(digest.finalize().as_slice());
178 Ok(())
179 }
180
181 /// Mark to trigger firmware swap on next boot.
182 ///
183 /// # Safety
184 ///
185 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
186 #[cfg(not(feature = "_verify"))]
187 pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
188 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
189 self.set_magic(aligned, SWAP_MAGIC)
190 }
191
192 /// Mark firmware boot successful and stop rollback on reset.
193 ///
194 /// # Safety
195 ///
196 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
197 pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> {
198 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
199 self.set_magic(aligned, BOOT_MAGIC)
200 }
201
202 fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> {
203 self.state.read(0, aligned)?;
204
205 if aligned.iter().any(|&b| b != magic) {
206 // Read progress validity
207 self.state.read(STATE::WRITE_SIZE as u32, aligned)?;
208
209 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
210 // The current progress validity marker is invalid
211 } else {
212 // Invalidate progress
213 aligned.fill(!STATE_ERASE_VALUE);
214 self.state.write(STATE::WRITE_SIZE as u32, aligned)?;
215 }
216
217 // Clear magic and progress
218 self.state.erase(0, self.state.capacity() as u32)?;
219
220 // Set magic
221 aligned.fill(magic);
222 self.state.write(0, aligned)?;
223 }
224 Ok(())
225 }
226
227 /// Write data to a flash page.
228 ///
229 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
230 ///
231 /// # Safety
232 ///
233 /// Failing to meet alignment and size requirements may result in a panic.
234 pub fn write_firmware(
235 &mut self,
236 aligned: &mut [u8],
237 offset: usize,
238 data: &[u8],
239 ) -> Result<(), FirmwareUpdaterError> {
240 assert!(data.len() >= DFU::ERASE_SIZE);
241 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
242 self.verify_booted(aligned)?;
243
244 self.dfu.erase(offset as u32, (offset + data.len()) as u32)?;
245
246 self.dfu.write(offset as u32, data)?;
247
248 Ok(())
249 }
250
251 /// Prepare for an incoming DFU update by erasing the entire DFU area and
252 /// returning its `Partition`.
253 ///
254 /// Using this instead of `write_firmware` allows for an optimized API in
255 /// exchange for added complexity.
256 ///
257 /// # Safety
258 ///
259 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to.
260 pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> {
261 assert_eq!(aligned.len(), STATE::WRITE_SIZE);
262 self.verify_booted(aligned)?;
263 self.dfu.erase(0, self.dfu.capacity() as u32)?;
264
265 Ok(&mut self.dfu)
266 }
267}
268
269#[cfg(test)]
270mod tests {
271 use core::cell::RefCell;
272
273 use embassy_embedded_hal::flash::partition::BlockingPartition;
274 use embassy_sync::blocking_mutex::raw::NoopRawMutex;
275 use embassy_sync::blocking_mutex::Mutex;
276 use sha1::{Digest, Sha1};
277
278 use super::*;
279 use crate::mem_flash::MemFlash;
280
281 #[test]
282 fn can_verify_sha1() {
283 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
284 let state = BlockingPartition::new(&flash, 0, 4096);
285 let dfu = BlockingPartition::new(&flash, 65536, 65536);
286
287 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
288 let mut to_write = [0; 4096];
289 to_write[..7].copy_from_slice(update.as_slice());
290
291 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state });
292 let mut aligned = [0; 8];
293 updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap();
294 let mut chunk_buf = [0; 2];
295 let mut hash = [0; 20];
296 updater
297 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
298 .unwrap();
299
300 assert_eq!(Sha1::digest(update).as_slice(), hash);
301 }
302}
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs
new file mode 100644
index 000000000..55ce8f363
--- /dev/null
+++ b/embassy-boot/boot/src/firmware_updater/mod.rs
@@ -0,0 +1,51 @@
1#[cfg(feature = "nightly")]
2mod asynch;
3mod blocking;
4
5#[cfg(feature = "nightly")]
6pub use asynch::FirmwareUpdater;
7pub use blocking::BlockingFirmwareUpdater;
8use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
9
10/// Firmware updater flash configuration holding the two flashes used by the updater
11///
12/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
13/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
14/// the provided flash according to symbols defined in the linkerfile.
15pub struct FirmwareUpdaterConfig<DFU, STATE> {
16 /// The dfu flash partition
17 pub dfu: DFU,
18 /// The state flash partition
19 pub state: STATE,
20}
21
22/// Errors returned by FirmwareUpdater
23#[derive(Debug)]
24pub enum FirmwareUpdaterError {
25 /// Error from flash.
26 Flash(NorFlashErrorKind),
27 /// Signature errors.
28 Signature(signature::Error),
29 /// Bad state.
30 BadState,
31}
32
33#[cfg(feature = "defmt")]
34impl defmt::Format for FirmwareUpdaterError {
35 fn format(&self, fmt: defmt::Formatter) {
36 match self {
37 FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"),
38 FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"),
39 FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"),
40 }
41 }
42}
43
44impl<E> From<E> for FirmwareUpdaterError
45where
46 E: NorFlashError,
47{
48 fn from(error: E) -> Self {
49 FirmwareUpdaterError::Flash(error.kind())
50 }
51}
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 51e1056cf..016362b86 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -1,674 +1,76 @@
1#![feature(type_alias_impl_trait)] 1#![cfg_attr(feature = "nightly", feature(async_fn_in_trait))]
2#![feature(generic_associated_types)]
3#![feature(generic_const_exprs)]
4#![allow(incomplete_features)]
5#![no_std] 2#![no_std]
6///! embassy-boot is a bootloader and firmware updater for embedded devices with flash 3#![warn(missing_docs)]
7///! storage implemented using embedded-storage 4#![doc = include_str!("../README.md")]
8///!
9///! The bootloader works in conjunction with the firmware application, and only has the
10///! ability to manage two flash banks with an active and a updatable part. It implements
11///! a swap algorithm that is power-failure safe, and allows reverting to the previous
12///! version of the firmware, should the application crash and fail to mark itself as booted.
13///!
14///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf,
15///! which defines the limits and flash type for that particular platform.
16///!
17mod fmt; 5mod fmt;
18 6
19use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; 7mod boot_loader;
20use embedded_storage_async::nor_flash::AsyncNorFlash; 8mod digest_adapters;
9mod firmware_updater;
10#[cfg(test)]
11mod mem_flash;
12#[cfg(test)]
13mod test_flash;
21 14
22const BOOT_MAGIC: u8 = 0xD0; 15// The expected value of the flash after an erase
23const SWAP_MAGIC: u8 = 0xF0; 16// TODO: Use the value provided by NorFlash when available
17pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF;
18pub use boot_loader::{BootError, BootLoader, BootLoaderConfig};
19#[cfg(feature = "nightly")]
20pub use firmware_updater::FirmwareUpdater;
21pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError};
24 22
25#[derive(Copy, Clone, Debug)] 23pub(crate) const BOOT_MAGIC: u8 = 0xD0;
26#[cfg_attr(feature = "defmt", derive(defmt::Format))] 24pub(crate) const SWAP_MAGIC: u8 = 0xF0;
27pub struct Partition {
28 pub from: usize,
29 pub to: usize,
30}
31 25
32impl Partition { 26/// The state of the bootloader after running prepare.
33 pub const fn new(from: usize, to: usize) -> Self { 27#[derive(PartialEq, Eq, Debug)]
34 Self { from, to }
35 }
36 pub const fn len(&self) -> usize {
37 self.to - self.from
38 }
39}
40
41#[derive(PartialEq, Debug)]
42#[cfg_attr(feature = "defmt", derive(defmt::Format))] 28#[cfg_attr(feature = "defmt", derive(defmt::Format))]
43pub enum State { 29pub enum State {
30 /// Bootloader is ready to boot the active partition.
44 Boot, 31 Boot,
32 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
45 Swap, 33 Swap,
46} 34}
47 35
48#[derive(PartialEq, Debug)] 36/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
49pub enum BootError {
50 Flash(NorFlashErrorKind),
51 BadMagic,
52}
53
54impl<E> From<E> for BootError
55where
56 E: NorFlashError,
57{
58 fn from(error: E) -> Self {
59 BootError::Flash(error.kind())
60 }
61}
62
63pub trait FlashConfig {
64 const BLOCK_SIZE: usize;
65 const ERASE_VALUE: u8;
66 type FLASH: NorFlash + ReadNorFlash;
67
68 fn flash(&mut self) -> &mut Self::FLASH;
69}
70
71/// Trait defining the flash handles used for active and DFU partition
72pub trait FlashProvider {
73 type STATE: FlashConfig;
74 type ACTIVE: FlashConfig;
75 type DFU: FlashConfig;
76
77 /// Return flash instance used to write/read to/from active partition.
78 fn active(&mut self) -> &mut Self::ACTIVE;
79 /// Return flash instance used to write/read to/from dfu partition.
80 fn dfu(&mut self) -> &mut Self::DFU;
81 /// Return flash instance used to write/read to/from bootloader state.
82 fn state(&mut self) -> &mut Self::STATE;
83}
84
85/// BootLoader works with any flash implementing embedded_storage and can also work with
86/// different page sizes and flash write sizes.
87///
88/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes.
89pub struct BootLoader<const PAGE_SIZE: usize> {
90 // Page with current state of bootloader. The state partition has the following format:
91 // | Range | Description |
92 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
93 // | WRITE_SIZE - N | Progress index used while swapping or reverting |
94 state: Partition,
95 // Location of the partition which will be booted from
96 active: Partition,
97 // Location of the partition which will be swapped in when requested
98 dfu: Partition,
99}
100
101impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
102 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self {
103 assert_eq!(active.len() % PAGE_SIZE, 0);
104 assert_eq!(dfu.len() % PAGE_SIZE, 0);
105 // DFU partition must have an extra page
106 assert!(dfu.len() - active.len() >= PAGE_SIZE);
107 Self { active, dfu, state }
108 }
109
110 pub fn boot_address(&self) -> usize {
111 self.active.from
112 }
113
114 /// Perform necessary boot preparations like swapping images.
115 ///
116 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
117 /// algorithm to work correctly.
118 ///
119 /// SWAPPING
120 ///
121 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
122 /// The swap index contains the copy progress, as to allow continuation of the copy process on
123 /// power failure. The index counter is represented within 1 or more pages (depending on total
124 /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE)
125 /// contains a zero value. This ensures that index updates can be performed atomically and
126 /// avoid a situation where the wrong index value is set (page write size is "atomic").
127 ///
128 /// +-----------+------------+--------+--------+--------+--------+
129 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
130 /// +-----------+------------+--------+--------+--------+--------+
131 /// | Active | 0 | 1 | 2 | 3 | - |
132 /// | DFU | 0 | 3 | 2 | 1 | X |
133 /// +-----------+-------+--------+--------+--------+--------+
134 ///
135 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
136 /// as follows:
137 ///
138 /// +-----------+------------+--------+--------+--------+--------+
139 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
140 /// +-----------+------------+--------+--------+--------+--------+
141 /// | Active | 1 | 1 | 2 | 1 | - |
142 /// | DFU | 1 | 3 | 2 | 1 | 3 |
143 /// +-----------+------------+--------+--------+--------+--------+
144 ///
145 /// The next iteration performs the same steps
146 ///
147 /// +-----------+------------+--------+--------+--------+--------+
148 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
149 /// +-----------+------------+--------+--------+--------+--------+
150 /// | Active | 2 | 1 | 2 | 1 | - |
151 /// | DFU | 2 | 3 | 2 | 2 | 3 |
152 /// +-----------+------------+--------+--------+--------+--------+
153 ///
154 /// And again until we're done
155 ///
156 /// +-----------+------------+--------+--------+--------+--------+
157 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
158 /// +-----------+------------+--------+--------+--------+--------+
159 /// | Active | 3 | 3 | 2 | 1 | - |
160 /// | DFU | 3 | 3 | 1 | 2 | 3 |
161 /// +-----------+------------+--------+--------+--------+--------+
162 ///
163 /// REVERTING
164 ///
165 /// The reverting algorithm uses the swap index to discover that images were swapped, but that
166 /// the application failed to mark the boot successful. In this case, the revert algorithm will
167 /// run.
168 ///
169 /// The revert index is located separately from the swap index, to ensure that revert can continue
170 /// on power failure.
171 ///
172 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
173 ///
174 /// +-----------+--------------+--------+--------+--------+--------+
175 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
176 //*/
177 /// +-----------+--------------+--------+--------+--------+--------+
178 /// | Active | 3 | 1 | 2 | 1 | - |
179 /// | DFU | 3 | 3 | 1 | 2 | 3 |
180 /// +-----------+--------------+--------+--------+--------+--------+
181 ///
182 ///
183 /// +-----------+--------------+--------+--------+--------+--------+
184 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
185 /// +-----------+--------------+--------+--------+--------+--------+
186 /// | Active | 3 | 1 | 2 | 1 | - |
187 /// | DFU | 3 | 3 | 2 | 2 | 3 |
188 /// +-----------+--------------+--------+--------+--------+--------+
189 ///
190 /// +-----------+--------------+--------+--------+--------+--------+
191 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
192 /// +-----------+--------------+--------+--------+--------+--------+
193 /// | Active | 3 | 1 | 2 | 3 | - |
194 /// | DFU | 3 | 3 | 2 | 1 | 3 |
195 /// +-----------+--------------+--------+--------+--------+--------+
196 ///
197 pub fn prepare_boot<P: FlashProvider>(&mut self, p: &mut P) -> Result<State, BootError>
198 where
199 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
200 [(); <<P as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:,
201 {
202 // Ensure we have enough progress pages to store copy progress
203 assert!(
204 self.active.len() / PAGE_SIZE
205 <= (self.state.len() - <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE)
206 / <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE
207 );
208
209 // Copy contents from partition N to active
210 let state = self.read_state(p.state())?;
211 match state {
212 State::Swap => {
213 //
214 // Check if we already swapped. If we're in the swap state, this means we should revert
215 // since the app has failed to mark boot as successful
216 //
217 if !self.is_swapped(p.state())? {
218 trace!("Swapping");
219 self.swap(p)?;
220 trace!("Swapping done");
221 } else {
222 trace!("Reverting");
223 self.revert(p)?;
224
225 // Overwrite magic and reset progress
226 let fstate = p.state().flash();
227 let aligned = Aligned(
228 [!P::STATE::ERASE_VALUE; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE],
229 );
230 fstate.write(self.state.from as u32, &aligned.0)?;
231 fstate.erase(self.state.from as u32, self.state.to as u32)?;
232 let aligned =
233 Aligned([BOOT_MAGIC; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]);
234 fstate.write(self.state.from as u32, &aligned.0)?;
235 }
236 }
237 _ => {}
238 }
239 Ok(state)
240 }
241
242 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P) -> Result<bool, BootError>
243 where
244 [(); P::FLASH::WRITE_SIZE]:,
245 {
246 let page_count = self.active.len() / P::FLASH::ERASE_SIZE;
247 let progress = self.current_progress(p)?;
248
249 Ok(progress >= page_count * 2)
250 }
251
252 fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError>
253 where
254 [(); P::FLASH::WRITE_SIZE]:,
255 {
256 let write_size = P::FLASH::WRITE_SIZE;
257 let max_index = ((self.state.len() - write_size) / write_size) - 1;
258 let flash = p.flash();
259 let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
260 for i in 0..max_index {
261 flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?;
262 if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] {
263 return Ok(i);
264 }
265 }
266 Ok(max_index)
267 }
268
269 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError>
270 where
271 [(); P::FLASH::WRITE_SIZE]:,
272 {
273 let flash = p.flash();
274 let write_size = P::FLASH::WRITE_SIZE;
275 let w = self.state.from + write_size + idx * write_size;
276 let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]);
277 flash.write(w as u32, &aligned.0)?;
278 Ok(())
279 }
280
281 fn active_addr(&self, n: usize) -> usize {
282 self.active.from + n * PAGE_SIZE
283 }
284
285 fn dfu_addr(&self, n: usize) -> usize {
286 self.dfu.from + n * PAGE_SIZE
287 }
288
289 fn copy_page_once_to_active<P: FlashProvider>(
290 &mut self,
291 idx: usize,
292 from_page: usize,
293 to_page: usize,
294 p: &mut P,
295 ) -> Result<(), BootError>
296 where
297 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
298 {
299 let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
300 if self.current_progress(p.state())? <= idx {
301 let mut offset = from_page;
302 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
303 p.dfu().flash().read(offset as u32, chunk)?;
304 offset += chunk.len();
305 }
306
307 p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?;
308
309 let mut offset = to_page;
310 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
311 p.active().flash().write(offset as u32, &chunk)?;
312 offset += chunk.len();
313 }
314 self.update_progress(idx, p.state())?;
315 }
316 Ok(())
317 }
318
319 fn copy_page_once_to_dfu<P: FlashProvider>(
320 &mut self,
321 idx: usize,
322 from_page: usize,
323 to_page: usize,
324 p: &mut P,
325 ) -> Result<(), BootError>
326 where
327 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
328 {
329 let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE];
330 if self.current_progress(p.state())? <= idx {
331 let mut offset = from_page;
332 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
333 p.active().flash().read(offset as u32, chunk)?;
334 offset += chunk.len();
335 }
336
337 p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?;
338
339 let mut offset = to_page;
340 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
341 p.dfu().flash().write(offset as u32, chunk)?;
342 offset += chunk.len();
343 }
344 self.update_progress(idx, p.state())?;
345 }
346 Ok(())
347 }
348
349 fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError>
350 where
351 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
352 {
353 let page_count = self.active.len() / PAGE_SIZE;
354 trace!("Page count: {}", page_count);
355 for page in 0..page_count {
356 trace!("COPY PAGE {}", page);
357 // Copy active page to the 'next' DFU page.
358 let active_page = self.active_addr(page_count - 1 - page);
359 let dfu_page = self.dfu_addr(page_count - page);
360 //trace!("Copy active {} to dfu {}", active_page, dfu_page);
361 self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?;
362
363 // Copy DFU page to the active page
364 let active_page = self.active_addr(page_count - 1 - page);
365 let dfu_page = self.dfu_addr(page_count - 1 - page);
366 //trace!("Copy dfy {} to active {}", dfu_page, active_page);
367 self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?;
368 }
369
370 Ok(())
371 }
372
373 fn revert<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError>
374 where
375 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:,
376 {
377 let page_count = self.active.len() / PAGE_SIZE;
378 for page in 0..page_count {
379 // Copy the bad active page to the DFU page
380 let active_page = self.active_addr(page);
381 let dfu_page = self.dfu_addr(page);
382 self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?;
383
384 // Copy the DFU page back to the active page
385 let active_page = self.active_addr(page);
386 let dfu_page = self.dfu_addr(page + 1);
387 self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?;
388 }
389
390 Ok(())
391 }
392
393 fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError>
394 where
395 [(); P::FLASH::WRITE_SIZE]:,
396 {
397 let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE];
398 let flash = p.flash();
399 flash.read(self.state.from as u32, &mut magic)?;
400
401 if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] {
402 Ok(State::Swap)
403 } else {
404 Ok(State::Boot)
405 }
406 }
407}
408
409/// Convenience provider that uses a single flash for everything
410pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF>
411where
412 F: NorFlash + ReadNorFlash,
413{
414 config: SingleFlashConfig<'a, F, ERASE_VALUE>,
415}
416
417impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE>
418where
419 F: NorFlash + ReadNorFlash,
420{
421 pub fn new(flash: &'a mut F) -> Self {
422 Self {
423 config: SingleFlashConfig { flash },
424 }
425 }
426}
427
428pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF>
429where
430 F: NorFlash + ReadNorFlash,
431{
432 flash: &'a mut F,
433}
434
435impl<'a, F> FlashProvider for SingleFlashProvider<'a, F>
436where
437 F: NorFlash + ReadNorFlash,
438{
439 type STATE = SingleFlashConfig<'a, F>;
440 type ACTIVE = SingleFlashConfig<'a, F>;
441 type DFU = SingleFlashConfig<'a, F>;
442
443 fn active(&mut self) -> &mut Self::STATE {
444 &mut self.config
445 }
446 fn dfu(&mut self) -> &mut Self::ACTIVE {
447 &mut self.config
448 }
449 fn state(&mut self) -> &mut Self::DFU {
450 &mut self.config
451 }
452}
453
454impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE>
455where
456 F: NorFlash + ReadNorFlash,
457{
458 const BLOCK_SIZE: usize = F::ERASE_SIZE;
459 const ERASE_VALUE: u8 = ERASE_VALUE;
460 type FLASH = F;
461 fn flash(&mut self) -> &mut F {
462 self.flash
463 }
464}
465
466/// Convenience provider that uses a single flash for everything
467pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU>
468where
469 ACTIVE: NorFlash + ReadNorFlash,
470 STATE: NorFlash + ReadNorFlash,
471 DFU: NorFlash + ReadNorFlash,
472{
473 active: SingleFlashConfig<'a, ACTIVE>,
474 state: SingleFlashConfig<'a, STATE>,
475 dfu: SingleFlashConfig<'a, DFU>,
476}
477
478impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU>
479where
480 ACTIVE: NorFlash + ReadNorFlash,
481 STATE: NorFlash + ReadNorFlash,
482 DFU: NorFlash + ReadNorFlash,
483{
484 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
485 Self {
486 active: SingleFlashConfig { flash: active },
487 state: SingleFlashConfig { flash: state },
488 dfu: SingleFlashConfig { flash: dfu },
489 }
490 }
491}
492
493impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU>
494where
495 ACTIVE: NorFlash + ReadNorFlash,
496 STATE: NorFlash + ReadNorFlash,
497 DFU: NorFlash + ReadNorFlash,
498{
499 type STATE = SingleFlashConfig<'a, STATE>;
500 type ACTIVE = SingleFlashConfig<'a, ACTIVE>;
501 type DFU = SingleFlashConfig<'a, DFU>;
502
503 fn active(&mut self) -> &mut Self::ACTIVE {
504 &mut self.active
505 }
506 fn dfu(&mut self) -> &mut Self::DFU {
507 &mut self.dfu
508 }
509 fn state(&mut self) -> &mut Self::STATE {
510 &mut self.state
511 }
512}
513
514/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
515/// 'mess up' the internal bootloader state
516pub struct FirmwareUpdater {
517 state: Partition,
518 dfu: Partition,
519}
520
521// NOTE: Aligned to the largest write size supported by flash
522#[repr(align(32))] 37#[repr(align(32))]
523pub struct Aligned<const N: usize>([u8; N]); 38pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
524
525impl Default for FirmwareUpdater {
526 fn default() -> Self {
527 extern "C" {
528 static __bootloader_state_start: u32;
529 static __bootloader_state_end: u32;
530 static __bootloader_dfu_start: u32;
531 static __bootloader_dfu_end: u32;
532 }
533
534 let dfu = unsafe {
535 Partition::new(
536 &__bootloader_dfu_start as *const u32 as usize,
537 &__bootloader_dfu_end as *const u32 as usize,
538 )
539 };
540 let state = unsafe {
541 Partition::new(
542 &__bootloader_state_start as *const u32 as usize,
543 &__bootloader_state_end as *const u32 as usize,
544 )
545 };
546
547 trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to);
548 trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to);
549 FirmwareUpdater::new(dfu, state)
550 }
551}
552
553impl FirmwareUpdater {
554 pub const fn new(dfu: Partition, state: Partition) -> Self {
555 Self { dfu, state }
556 }
557
558 /// Return the length of the DFU area
559 pub fn firmware_len(&self) -> usize {
560 self.dfu.len()
561 }
562
563 /// Instruct bootloader that DFU should commence at next boot.
564 /// Must be provided with an aligned buffer to use for reading and writing magic;
565 pub async fn update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error>
566 where
567 [(); F::WRITE_SIZE]:,
568 {
569 let mut aligned = Aligned([0; { F::WRITE_SIZE }]);
570 self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await
571 }
572 39
573 /// Mark firmware boot successfully 40impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> {
574 pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> 41 fn as_ref(&self) -> &[u8] {
575 where 42 &self.0
576 [(); F::WRITE_SIZE]:,
577 {
578 let mut aligned = Aligned([0; { F::WRITE_SIZE }]);
579 self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await
580 } 43 }
44}
581 45
582 async fn set_magic<F: AsyncNorFlash>( 46impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
583 &mut self, 47 fn as_mut(&mut self) -> &mut [u8] {
584 aligned: &mut [u8], 48 &mut self.0
585 magic: u8,
586 flash: &mut F,
587 ) -> Result<(), F::Error> {
588 flash.read(self.state.from as u32, aligned).await?;
589
590 if aligned.iter().find(|&&b| b != magic).is_some() {
591 aligned.fill(0);
592
593 flash.write(self.state.from as u32, aligned).await?;
594 flash.erase(self.state.from as u32, self.state.to as u32).await?;
595
596 aligned.fill(magic);
597 flash.write(self.state.from as u32, aligned).await?;
598 }
599 Ok(())
600 }
601
602 // Write to a region of the DFU page
603 pub async fn write_firmware<F: AsyncNorFlash>(
604 &mut self,
605 offset: usize,
606 data: &[u8],
607 flash: &mut F,
608 block_size: usize,
609 ) -> Result<(), F::Error> {
610 assert!(data.len() >= F::ERASE_SIZE);
611
612 trace!(
613 "Writing firmware at offset 0x{:x} len {}",
614 self.dfu.from + offset,
615 data.len()
616 );
617
618 flash
619 .erase(
620 (self.dfu.from + offset) as u32,
621 (self.dfu.from + offset + data.len()) as u32,
622 )
623 .await?;
624
625 trace!(
626 "Erased from {} to {}",
627 self.dfu.from + offset,
628 self.dfu.from + offset + data.len()
629 );
630
631 let mut write_offset = self.dfu.from + offset;
632 for chunk in data.chunks(block_size) {
633 trace!("Wrote chunk at {}: {:?}", write_offset, chunk);
634 flash.write(write_offset as u32, chunk).await?;
635 write_offset += chunk.len();
636 }
637 /*
638 trace!("Wrote data, reading back for verification");
639
640 let mut buf: [u8; 4096] = [0; 4096];
641 let mut data_offset = 0;
642 let mut read_offset = self.dfu.from + offset;
643 for chunk in buf.chunks_mut(block_size) {
644 flash.read(read_offset as u32, chunk).await?;
645 trace!("Read chunk at {}: {:?}", read_offset, chunk);
646 assert_eq!(&data[data_offset..data_offset + block_size], chunk);
647 read_offset += chunk.len();
648 data_offset += chunk.len();
649 }
650 */
651
652 Ok(())
653 } 49 }
654} 50}
655 51
656#[cfg(test)] 52#[cfg(test)]
657mod tests { 53mod tests {
658 use core::convert::Infallible; 54 #![allow(unused_imports)]
659 use core::future::Future;
660 55
661 use embedded_storage::nor_flash::ErrorType; 56 use embedded_storage::nor_flash::{NorFlash, ReadNorFlash};
662 use embedded_storage_async::nor_flash::AsyncReadNorFlash; 57 #[cfg(feature = "nightly")]
58 use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash;
663 use futures::executor::block_on; 59 use futures::executor::block_on;
664 60
665 use super::*; 61 use super::*;
62 use crate::boot_loader::BootLoaderConfig;
63 use crate::firmware_updater::FirmwareUpdaterConfig;
64 use crate::mem_flash::MemFlash;
65 #[cfg(feature = "nightly")]
66 use crate::test_flash::AsyncTestFlash;
67 use crate::test_flash::BlockingTestFlash;
666 68
667 /* 69 /*
668 #[test] 70 #[test]
669 fn test_bad_magic() { 71 fn test_bad_magic() {
670 let mut flash = MemFlash([0xff; 131072]); 72 let mut flash = MemFlash([0xff; 131072]);
671 let mut flash = SingleFlashProvider::new(&mut flash); 73 let mut flash = SingleFlashConfig::new(&mut flash);
672 74
673 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); 75 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
674 76
@@ -681,281 +83,229 @@ mod tests {
681 83
682 #[test] 84 #[test]
683 fn test_boot_state() { 85 fn test_boot_state() {
684 const STATE: Partition = Partition::new(0, 4096); 86 let flash = BlockingTestFlash::new(BootLoaderConfig {
685 const ACTIVE: Partition = Partition::new(4096, 61440); 87 active: MemFlash::<57344, 4096, 4>::default(),
686 const DFU: Partition = Partition::new(61440, 122880); 88 dfu: MemFlash::<61440, 4096, 4>::default(),
89 state: MemFlash::<4096, 4096, 4>::default(),
90 });
687 91
688 let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); 92 flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap();
689 flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
690 let mut flash = SingleFlashProvider::new(&mut flash);
691 93
692 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 94 let mut bootloader = BootLoader::new(BootLoaderConfig {
95 active: flash.active(),
96 dfu: flash.dfu(),
97 state: flash.state(),
98 });
693 99
694 assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); 100 let mut page = [0; 4096];
101 assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
695 } 102 }
696 103
697 #[test] 104 #[test]
105 #[cfg(all(feature = "nightly", not(feature = "_verify")))]
698 fn test_swap_state() { 106 fn test_swap_state() {
699 const STATE: Partition = Partition::new(0, 4096); 107 const FIRMWARE_SIZE: usize = 57344;
700 const ACTIVE: Partition = Partition::new(4096, 61440); 108 let flash = AsyncTestFlash::new(BootLoaderConfig {
701 const DFU: Partition = Partition::new(61440, 122880); 109 active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(),
702 let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); 110 dfu: MemFlash::<61440, 4096, 4>::default(),
703 111 state: MemFlash::<4096, 4096, 4>::default(),
704 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 112 });
705 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 113
706 114 const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
707 for i in ACTIVE.from..ACTIVE.to { 115 const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
708 flash.0[i] = original[i - ACTIVE.from]; 116 let mut aligned = [0; 4];
709 } 117
710 118 block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
711 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 119 block_on(flash.active().write(0, &ORIGINAL)).unwrap();
712 let mut updater = FirmwareUpdater::new(DFU, STATE); 120
713 let mut offset = 0; 121 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
714 for chunk in update.chunks(4096) { 122 dfu: flash.dfu(),
715 block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); 123 state: flash.state(),
716 offset += chunk.len(); 124 });
717 } 125 block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
718 block_on(updater.update(&mut flash)).unwrap(); 126 block_on(updater.mark_updated(&mut aligned)).unwrap();
719 127
720 assert_eq!( 128 // Writing after marking updated is not allowed until marked as booted.
721 State::Swap, 129 let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE));
722 bootloader 130 assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState)));
723 .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) 131
724 .unwrap() 132 let flash = flash.into_blocking();
725 ); 133 let mut bootloader = BootLoader::new(BootLoaderConfig {
726 134 active: flash.active(),
727 for i in ACTIVE.from..ACTIVE.to { 135 dfu: flash.dfu(),
728 assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i); 136 state: flash.state(),
729 } 137 });
730 138
139 let mut page = [0; 1024];
140 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
141
142 let mut read_buf = [0; FIRMWARE_SIZE];
143 flash.active().read(0, &mut read_buf).unwrap();
144 assert_eq!(UPDATE, read_buf);
731 // First DFU page is untouched 145 // First DFU page is untouched
732 for i in DFU.from + 4096..DFU.to { 146 flash.dfu().read(4096, &mut read_buf).unwrap();
733 assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i); 147 assert_eq!(ORIGINAL, read_buf);
734 }
735 148
736 // Running again should cause a revert 149 // Running again should cause a revert
737 assert_eq!( 150 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
738 State::Swap,
739 bootloader
740 .prepare_boot(&mut SingleFlashProvider::new(&mut flash))
741 .unwrap()
742 );
743 151
744 for i in ACTIVE.from..ACTIVE.to { 152 let mut read_buf = [0; FIRMWARE_SIZE];
745 assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i); 153 flash.active().read(0, &mut read_buf).unwrap();
746 } 154 assert_eq!(ORIGINAL, read_buf);
747 155 // Last DFU page is untouched
748 // Last page is untouched 156 flash.dfu().read(0, &mut read_buf).unwrap();
749 for i in DFU.from..DFU.to - 4096 { 157 assert_eq!(UPDATE, read_buf);
750 assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i);
751 }
752 158
753 // Mark as booted 159 // Mark as booted
754 block_on(updater.mark_booted(&mut flash)).unwrap(); 160 let flash = flash.into_async();
755 assert_eq!( 161 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
756 State::Boot, 162 dfu: flash.dfu(),
757 bootloader 163 state: flash.state(),
758 .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) 164 });
759 .unwrap() 165 block_on(updater.mark_booted(&mut aligned)).unwrap();
760 ); 166
167 let flash = flash.into_blocking();
168 let mut bootloader = BootLoader::new(BootLoaderConfig {
169 active: flash.active(),
170 dfu: flash.dfu(),
171 state: flash.state(),
172 });
173 assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap());
761 } 174 }
762 175
763 #[test] 176 #[test]
764 fn test_separate_flash_active_page_biggest() { 177 #[cfg(all(feature = "nightly", not(feature = "_verify")))]
765 const STATE: Partition = Partition::new(2048, 4096); 178 fn test_swap_state_active_page_biggest() {
766 const ACTIVE: Partition = Partition::new(4096, 16384); 179 const FIRMWARE_SIZE: usize = 12288;
767 const DFU: Partition = Partition::new(0, 16384); 180 let flash = AsyncTestFlash::new(BootLoaderConfig {
768 181 active: MemFlash::<12288, 4096, 8>::random(),
769 let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); 182 dfu: MemFlash::<16384, 2048, 8>::random(),
770 let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); 183 state: MemFlash::<2048, 128, 4>::random(),
771 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); 184 });
772 185
773 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 186 const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
774 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 187 const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
775 188 let mut aligned = [0; 4];
776 for i in ACTIVE.from..ACTIVE.to { 189
777 active.0[i] = original[i - ACTIVE.from]; 190 block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
778 } 191 block_on(flash.active().write(0, &ORIGINAL)).unwrap();
779 192
780 let mut updater = FirmwareUpdater::new(DFU, STATE); 193 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
781 194 dfu: flash.dfu(),
782 let mut offset = 0; 195 state: flash.state(),
783 for chunk in update.chunks(2048) { 196 });
784 block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); 197 block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
785 offset += chunk.len(); 198 block_on(updater.mark_updated(&mut aligned)).unwrap();
786 } 199
787 block_on(updater.update(&mut state)).unwrap(); 200 let flash = flash.into_blocking();
788 201 let mut bootloader = BootLoader::new(BootLoaderConfig {
789 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 202 active: flash.active(),
790 assert_eq!( 203 dfu: flash.dfu(),
791 State::Swap, 204 state: flash.state(),
792 bootloader 205 });
793 .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) 206
794 .unwrap() 207 let mut page = [0; 4096];
795 ); 208 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
796 209
797 for i in ACTIVE.from..ACTIVE.to { 210 let mut read_buf = [0; FIRMWARE_SIZE];
798 assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); 211 flash.active().read(0, &mut read_buf).unwrap();
799 } 212 assert_eq!(UPDATE, read_buf);
800
801 // First DFU page is untouched 213 // First DFU page is untouched
802 for i in DFU.from + 4096..DFU.to { 214 flash.dfu().read(4096, &mut read_buf).unwrap();
803 assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); 215 assert_eq!(ORIGINAL, read_buf);
804 }
805 } 216 }
806 217
807 #[test] 218 #[test]
808 fn test_separate_flash_dfu_page_biggest() { 219 #[cfg(all(feature = "nightly", not(feature = "_verify")))]
809 const STATE: Partition = Partition::new(2048, 4096); 220 fn test_swap_state_dfu_page_biggest() {
810 const ACTIVE: Partition = Partition::new(4096, 16384); 221 const FIRMWARE_SIZE: usize = 12288;
811 const DFU: Partition = Partition::new(0, 16384); 222 let flash = AsyncTestFlash::new(BootLoaderConfig {
812 223 active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(),
813 let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); 224 dfu: MemFlash::<16384, 4096, 8>::random(),
814 let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); 225 state: MemFlash::<2048, 128, 4>::random(),
815 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); 226 });
816 227
817 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 228 const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE];
818 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 229 const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE];
819 230 let mut aligned = [0; 4];
820 for i in ACTIVE.from..ACTIVE.to { 231
821 active.0[i] = original[i - ACTIVE.from]; 232 block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap();
822 } 233 block_on(flash.active().write(0, &ORIGINAL)).unwrap();
823 234
824 let mut updater = FirmwareUpdater::new(DFU, STATE); 235 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
825 236 dfu: flash.dfu(),
826 let mut offset = 0; 237 state: flash.state(),
827 for chunk in update.chunks(4096) { 238 });
828 block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); 239 block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap();
829 offset += chunk.len(); 240 block_on(updater.mark_updated(&mut aligned)).unwrap();
830 } 241
831 block_on(updater.update(&mut state)).unwrap(); 242 let flash = flash.into_blocking();
832 243 let mut bootloader = BootLoader::new(BootLoaderConfig {
833 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 244 active: flash.active(),
834 assert_eq!( 245 dfu: flash.dfu(),
835 State::Swap, 246 state: flash.state(),
836 bootloader 247 });
837 .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) 248 let mut page = [0; 4096];
838 .unwrap() 249 assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap());
839 ); 250
840 251 let mut read_buf = [0; FIRMWARE_SIZE];
841 for i in ACTIVE.from..ACTIVE.to { 252 flash.active().read(0, &mut read_buf).unwrap();
842 assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); 253 assert_eq!(UPDATE, read_buf);
843 }
844
845 // First DFU page is untouched 254 // First DFU page is untouched
846 for i in DFU.from + 4096..DFU.to { 255 flash.dfu().read(4096, &mut read_buf).unwrap();
847 assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); 256 assert_eq!(ORIGINAL, read_buf);
848 }
849 }
850
851 struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize>([u8; SIZE]);
852
853 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
854 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
855 {
856 const WRITE_SIZE: usize = WRITE_SIZE;
857 const ERASE_SIZE: usize = ERASE_SIZE;
858 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
859 let from = from as usize;
860 let to = to as usize;
861 assert!(from % ERASE_SIZE == 0);
862 assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
863 for i in from..to {
864 self.0[i] = 0xFF;
865 }
866 Ok(())
867 }
868
869 fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
870 assert!(data.len() % WRITE_SIZE == 0);
871 assert!(offset as usize % WRITE_SIZE == 0);
872 assert!(offset as usize + data.len() <= SIZE);
873
874 self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
875
876 Ok(())
877 }
878 } 257 }
879 258
880 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType 259 #[test]
881 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> 260 #[cfg(all(feature = "nightly", feature = "_verify"))]
882 { 261 fn test_verify() {
883 type Error = Infallible; 262 // The following key setup is based on:
884 } 263 // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
885 264
886 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash 265 use ed25519_dalek::Keypair;
887 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> 266 use rand::rngs::OsRng;
888 { 267
889 const READ_SIZE: usize = 4; 268 let mut csprng = OsRng {};
890 269 let keypair: Keypair = Keypair::generate(&mut csprng);
891 fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { 270
892 let len = buf.len(); 271 use ed25519_dalek::{Digest, Sha512, Signature, Signer};
893 buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); 272 let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU.";
894 Ok(()) 273 let mut digest = Sha512::new();
895 } 274 digest.update(&firmware);
896 275 let message = digest.finalize();
897 fn capacity(&self) -> usize { 276 let signature: Signature = keypair.sign(&message);
898 SIZE 277
899 } 278 use ed25519_dalek::PublicKey;
900 } 279 let public_key: PublicKey = keypair.public;
901 280
902 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash 281 // Setup flash
903 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> 282 let flash = BlockingTestFlash::new(BootLoaderConfig {
904 { 283 active: MemFlash::<0, 0, 0>::default(),
905 const READ_SIZE: usize = 4; 284 dfu: MemFlash::<4096, 4096, 4>::default(),
906 285 state: MemFlash::<4096, 4096, 4>::default(),
907 type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; 286 });
908 fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { 287
909 async move { 288 let firmware_len = firmware.len();
910 let len = buf.len(); 289
911 buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); 290 let mut write_buf = [0; 4096];
912 Ok(()) 291 write_buf[0..firmware_len].copy_from_slice(firmware);
913 } 292 flash.dfu().write(0, &write_buf).unwrap();
914 } 293
915 294 // On with the test
916 fn capacity(&self) -> usize { 295 let flash = flash.into_async();
917 SIZE 296 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig {
918 } 297 dfu: flash.dfu(),
919 } 298 state: flash.state(),
920 299 });
921 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash 300
922 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> 301 let mut aligned = [0; 4];
923 { 302
924 const WRITE_SIZE: usize = WRITE_SIZE; 303 assert!(block_on(updater.verify_and_mark_updated(
925 const ERASE_SIZE: usize = ERASE_SIZE; 304 &public_key.to_bytes(),
926 305 &signature.to_bytes(),
927 type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; 306 firmware_len as u32,
928 fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { 307 &mut aligned,
929 async move { 308 ))
930 let from = from as usize; 309 .is_ok());
931 let to = to as usize;
932 assert!(from % ERASE_SIZE == 0);
933 assert!(to % ERASE_SIZE == 0);
934 for i in from..to {
935 self.0[i] = 0xFF;
936 }
937 Ok(())
938 }
939 }
940
941 type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a;
942 fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> {
943 info!("Writing {} bytes to 0x{:x}", data.len(), offset);
944 async move {
945 assert!(data.len() % WRITE_SIZE == 0);
946 assert!(offset as usize % WRITE_SIZE == 0);
947 assert!(
948 offset as usize + data.len() <= SIZE,
949 "OFFSET: {}, LEN: {}, FLASH SIZE: {}",
950 offset,
951 data.len(),
952 SIZE
953 );
954
955 self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data);
956
957 Ok(())
958 }
959 }
960 } 310 }
961} 311}
diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs
new file mode 100644
index 000000000..2728e9720
--- /dev/null
+++ b/embassy-boot/boot/src/mem_flash.rs
@@ -0,0 +1,173 @@
1#![allow(unused)]
2
3use core::ops::{Bound, Range, RangeBounds};
4
5use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
6#[cfg(feature = "nightly")]
7use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash};
8
9pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
10 pub mem: [u8; SIZE],
11 pub pending_write_successes: Option<usize>,
12}
13
14#[derive(Debug)]
15pub struct MemFlashError;
16
17impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> {
18 pub const fn new(fill: u8) -> Self {
19 Self {
20 mem: [fill; SIZE],
21 pending_write_successes: None,
22 }
23 }
24
25 #[cfg(test)]
26 pub fn random() -> Self {
27 let mut mem = [0; SIZE];
28 for byte in mem.iter_mut() {
29 *byte = rand::random::<u8>();
30 }
31 Self {
32 mem,
33 pending_write_successes: None,
34 }
35 }
36
37 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> {
38 let len = bytes.len();
39 bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]);
40 Ok(())
41 }
42
43 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
44 let offset = offset as usize;
45 assert!(bytes.len() % WRITE_SIZE == 0);
46 assert!(offset % WRITE_SIZE == 0);
47 assert!(offset + bytes.len() <= SIZE);
48
49 if let Some(pending_successes) = self.pending_write_successes {
50 if pending_successes > 0 {
51 self.pending_write_successes = Some(pending_successes - 1);
52 } else {
53 return Err(MemFlashError);
54 }
55 }
56
57 for ((offset, mem_byte), new_byte) in self
58 .mem
59 .iter_mut()
60 .enumerate()
61 .skip(offset)
62 .take(bytes.len())
63 .zip(bytes)
64 {
65 assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
66 *mem_byte = *new_byte;
67 }
68
69 Ok(())
70 }
71
72 fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> {
73 let from = from as usize;
74 let to = to as usize;
75 assert!(from % ERASE_SIZE == 0);
76 assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE);
77 for i in from..to {
78 self.mem[i] = 0xFF;
79 }
80 Ok(())
81 }
82
83 pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> {
84 let offset = offset as usize;
85 assert!(bytes.len() % WRITE_SIZE == 0);
86 assert!(offset % WRITE_SIZE == 0);
87 assert!(offset + bytes.len() <= SIZE);
88
89 self.mem[offset..offset + bytes.len()].copy_from_slice(bytes);
90
91 Ok(())
92 }
93}
94
95impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
96 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
97{
98 fn default() -> Self {
99 Self::new(0xFF)
100 }
101}
102
103impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType
104 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
105{
106 type Error = MemFlashError;
107}
108
109impl NorFlashError for MemFlashError {
110 fn kind(&self) -> NorFlashErrorKind {
111 NorFlashErrorKind::Other
112 }
113}
114
115impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash
116 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
117{
118 const READ_SIZE: usize = 1;
119
120 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
121 self.read(offset, bytes)
122 }
123
124 fn capacity(&self) -> usize {
125 SIZE
126 }
127}
128
129impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash
130 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
131{
132 const WRITE_SIZE: usize = WRITE_SIZE;
133 const ERASE_SIZE: usize = ERASE_SIZE;
134
135 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
136 self.write(offset, bytes)
137 }
138
139 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
140 self.erase(from, to)
141 }
142}
143
144#[cfg(feature = "nightly")]
145impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
146 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
147{
148 const READ_SIZE: usize = 1;
149
150 async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
151 self.read(offset, bytes)
152 }
153
154 fn capacity(&self) -> usize {
155 SIZE
156 }
157}
158
159#[cfg(feature = "nightly")]
160impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash
161 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
162{
163 const WRITE_SIZE: usize = WRITE_SIZE;
164 const ERASE_SIZE: usize = ERASE_SIZE;
165
166 async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
167 self.write(offset, bytes)
168 }
169
170 async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
171 self.erase(from, to)
172 }
173}
diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs
new file mode 100644
index 000000000..3ac9e71ab
--- /dev/null
+++ b/embassy-boot/boot/src/test_flash/asynch.rs
@@ -0,0 +1,64 @@
1use embassy_embedded_hal::flash::partition::Partition;
2use embassy_sync::blocking_mutex::raw::NoopRawMutex;
3use embassy_sync::mutex::Mutex;
4use embedded_storage_async::nor_flash::NorFlash;
5
6use crate::BootLoaderConfig;
7
8pub struct AsyncTestFlash<ACTIVE, DFU, STATE>
9where
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
19impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
20where
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
50impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE>
51where
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/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs
new file mode 100644
index 000000000..ba33c9208
--- /dev/null
+++ b/embassy-boot/boot/src/test_flash/blocking.rs
@@ -0,0 +1,69 @@
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;
7
8use crate::BootLoaderConfig;
9
10pub struct BlockingTestFlash<ACTIVE, DFU, STATE>
11where
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
21impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
22where
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#[cfg(feature = "nightly")]
55impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE>
56where
57 ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
58 DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash,
59 STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash,
60{
61 pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> {
62 let config = BootLoaderConfig {
63 active: self.active.into_inner().into_inner(),
64 dfu: self.dfu.into_inner().into_inner(),
65 state: self.state.into_inner().into_inner(),
66 };
67 super::AsyncTestFlash::new(config)
68 }
69}
diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/boot/src/test_flash/mod.rs
new file mode 100644
index 000000000..a0672322e
--- /dev/null
+++ b/embassy-boot/boot/src/test_flash/mod.rs
@@ -0,0 +1,7 @@
1#[cfg(feature = "nightly")]
2mod asynch;
3mod blocking;
4
5#[cfg(feature = "nightly")]
6pub(crate) use asynch::AsyncTestFlash;
7pub(crate) use blocking::BlockingTestFlash;