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