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