aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/src
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/src
parentb452a6bcf6858893a85882614e2dcde5a3405748 (diff)
Flatten embassy-boot dir tree
Diffstat (limited to 'embassy-boot/src')
-rw-r--r--embassy-boot/src/boot_loader.rs411
-rw-r--r--embassy-boot/src/digest_adapters/ed25519_dalek.rs30
-rw-r--r--embassy-boot/src/digest_adapters/mod.rs5
-rw-r--r--embassy-boot/src/digest_adapters/salty.rs29
-rw-r--r--embassy-boot/src/firmware_updater/asynch.rs329
-rw-r--r--embassy-boot/src/firmware_updater/blocking.rs340
-rw-r--r--embassy-boot/src/firmware_updater/mod.rs49
-rw-r--r--embassy-boot/src/fmt.rs258
-rw-r--r--embassy-boot/src/lib.rs323
-rw-r--r--embassy-boot/src/mem_flash.rs170
-rw-r--r--embassy-boot/src/test_flash/asynch.rs64
-rw-r--r--embassy-boot/src/test_flash/blocking.rs68
-rw-r--r--embassy-boot/src/test_flash/mod.rs5
13 files changed, 2081 insertions, 0 deletions
diff --git a/embassy-boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs
new file mode 100644
index 000000000..e568001bc
--- /dev/null
+++ b/embassy-boot/src/boot_loader.rs
@@ -0,0 +1,411 @@
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/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs
new file mode 100644
index 000000000..2e4e03da3
--- /dev/null
+++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs
@@ -0,0 +1,30 @@
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/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs
new file mode 100644
index 000000000..9b4b4b60c
--- /dev/null
+++ b/embassy-boot/src/digest_adapters/mod.rs
@@ -0,0 +1,5 @@
1#[cfg(feature = "ed25519-dalek")]
2pub(crate) mod ed25519_dalek;
3
4#[cfg(feature = "ed25519-salty")]
5pub(crate) mod salty;
diff --git a/embassy-boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs
new file mode 100644
index 000000000..2b5dcf3af
--- /dev/null
+++ b/embassy-boot/src/digest_adapters/salty.rs
@@ -0,0 +1,29 @@
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/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
new file mode 100644
index 000000000..2e43e1cc1
--- /dev/null
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -0,0 +1,329 @@
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/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
new file mode 100644
index 000000000..f1368540d
--- /dev/null
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -0,0 +1,340 @@
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/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs
new file mode 100644
index 000000000..4814786bf
--- /dev/null
+++ b/embassy-boot/src/firmware_updater/mod.rs
@@ -0,0 +1,49 @@
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/src/fmt.rs b/embassy-boot/src/fmt.rs
new file mode 100644
index 000000000..78e583c1c
--- /dev/null
+++ b/embassy-boot/src/fmt.rs
@@ -0,0 +1,258 @@
1#![macro_use]
2#![allow(unused_macros)]
3
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/src/lib.rs b/embassy-boot/src/lib.rs
new file mode 100644
index 000000000..b4f03e01e
--- /dev/null
+++ b/embassy-boot/src/lib.rs
@@ -0,0 +1,323 @@
1#![no_std]
2#![allow(async_fn_in_trait)]
3#![warn(missing_docs)]
4#![doc = include_str!("../README.md")]
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/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs
new file mode 100644
index 000000000..40f352c8d
--- /dev/null
+++ b/embassy-boot/src/mem_flash.rs
@@ -0,0 +1,170 @@
1#![allow(unused)]
2
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/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs
new file mode 100644
index 000000000..3ac9e71ab
--- /dev/null
+++ b/embassy-boot/src/test_flash/asynch.rs
@@ -0,0 +1,64 @@
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/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs
new file mode 100644
index 000000000..5ec476c65
--- /dev/null
+++ b/embassy-boot/src/test_flash/blocking.rs
@@ -0,0 +1,68 @@
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/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs
new file mode 100644
index 000000000..79b15a081
--- /dev/null
+++ b/embassy-boot/src/test_flash/mod.rs
@@ -0,0 +1,5 @@
1mod asynch;
2mod blocking;
3
4pub(crate) use asynch::AsyncTestFlash;
5pub(crate) use blocking::BlockingTestFlash;