aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot
diff options
context:
space:
mode:
authorFrostie314159 <[email protected]>2024-03-31 20:48:05 +0200
committerGitHub <[email protected]>2024-03-31 20:48:05 +0200
commit67c9cc2c4b886e6962ecdd6eff8794b14c1accdc (patch)
treef176ab269949d26f48e04c950cebc5489bae8c56 /embassy-boot
parenta2f9aa592ec61beb247065003016515f0d423c13 (diff)
parent6634cc90bcd3eb25b64712688920f383584b2964 (diff)
Merge branch 'embassy-rs:main' into ticker_send_sync
Diffstat (limited to 'embassy-boot')
-rw-r--r--embassy-boot/Cargo.toml (renamed from embassy-boot/boot/Cargo.toml)24
-rw-r--r--embassy-boot/README.md35
-rw-r--r--embassy-boot/boot/README.md31
-rw-r--r--embassy-boot/nrf/Cargo.toml38
-rw-r--r--embassy-boot/nrf/README.md26
-rw-r--r--embassy-boot/nrf/src/fmt.rs258
-rw-r--r--embassy-boot/nrf/src/lib.rs145
-rw-r--r--embassy-boot/rp/Cargo.toml73
-rw-r--r--embassy-boot/rp/README.md26
-rw-r--r--embassy-boot/rp/build.rs8
-rw-r--r--embassy-boot/rp/src/fmt.rs258
-rw-r--r--embassy-boot/rp/src/lib.rs90
-rw-r--r--embassy-boot/src/boot_loader.rs (renamed from embassy-boot/boot/src/boot_loader.rs)119
-rw-r--r--embassy-boot/src/digest_adapters/ed25519_dalek.rs (renamed from embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs)4
-rw-r--r--embassy-boot/src/digest_adapters/mod.rs (renamed from embassy-boot/boot/src/digest_adapters/mod.rs)0
-rw-r--r--embassy-boot/src/digest_adapters/salty.rs (renamed from embassy-boot/boot/src/digest_adapters/salty.rs)0
-rw-r--r--embassy-boot/src/firmware_updater/asynch.rs (renamed from embassy-boot/boot/src/firmware_updater/asynch.rs)215
-rw-r--r--embassy-boot/src/firmware_updater/blocking.rs (renamed from embassy-boot/boot/src/firmware_updater/blocking.rs)231
-rw-r--r--embassy-boot/src/firmware_updater/mod.rs (renamed from embassy-boot/boot/src/firmware_updater/mod.rs)2
-rw-r--r--embassy-boot/src/fmt.rs (renamed from embassy-boot/boot/src/fmt.rs)3
-rw-r--r--embassy-boot/src/lib.rs (renamed from embassy-boot/boot/src/lib.rs)11
-rw-r--r--embassy-boot/src/mem_flash.rs (renamed from embassy-boot/boot/src/mem_flash.rs)0
-rw-r--r--embassy-boot/src/test_flash/asynch.rs (renamed from embassy-boot/boot/src/test_flash/asynch.rs)0
-rw-r--r--embassy-boot/src/test_flash/blocking.rs (renamed from embassy-boot/boot/src/test_flash/blocking.rs)0
-rw-r--r--embassy-boot/src/test_flash/mod.rs (renamed from embassy-boot/boot/src/test_flash/mod.rs)0
-rw-r--r--embassy-boot/stm32/Cargo.toml64
-rw-r--r--embassy-boot/stm32/README.md24
-rw-r--r--embassy-boot/stm32/build.rs8
-rw-r--r--embassy-boot/stm32/src/fmt.rs258
-rw-r--r--embassy-boot/stm32/src/lib.rs41
30 files changed, 516 insertions, 1476 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/Cargo.toml
index dd2ff8158..242caa229 100644
--- a/embassy-boot/boot/Cargo.toml
+++ b/embassy-boot/Cargo.toml
@@ -1,10 +1,11 @@
1[package] 1[package]
2edition = "2021" 2edition = "2021"
3name = "embassy-boot" 3name = "embassy-boot"
4version = "0.1.1" 4version = "0.2.0"
5description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." 5description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks."
6license = "MIT OR Apache-2.0" 6license = "MIT OR Apache-2.0"
7repository = "https://github.com/embassy-rs/embassy" 7repository = "https://github.com/embassy-rs/embassy"
8documentation = "https://docs.embassy.dev/embassy-boot"
8categories = [ 9categories = [
9 "embedded", 10 "embedded",
10 "no-std", 11 "no-std",
@@ -12,8 +13,8 @@ categories = [
12] 13]
13 14
14[package.metadata.embassy_docs] 15[package.metadata.embassy_docs]
15src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" 16src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/src/"
16src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" 17src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/src/"
17target = "thumbv7em-none-eabi" 18target = "thumbv7em-none-eabi"
18features = ["defmt"] 19features = ["defmt"]
19 20
@@ -26,25 +27,22 @@ features = ["defmt"]
26defmt = { version = "0.3", optional = true } 27defmt = { version = "0.3", optional = true }
27digest = "0.10" 28digest = "0.10"
28log = { version = "0.4", optional = true } 29log = { version = "0.4", optional = true }
29ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } 30ed25519-dalek = { version = "2", default_features = false, features = ["digest"], optional = true }
30embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } 31embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }
31embassy-sync = { version = "0.5.0", path = "../../embassy-sync" } 32embassy-sync = { version = "0.5.0", path = "../embassy-sync" }
32embedded-storage = "0.3.1" 33embedded-storage = "0.3.1"
33embedded-storage-async = { version = "0.4.1" } 34embedded-storage-async = { version = "0.4.1" }
34salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } 35salty = { version = "0.3", optional = true }
35signature = { version = "1.6.4", default-features = false } 36signature = { version = "2.0", default-features = false }
36 37
37[dev-dependencies] 38[dev-dependencies]
38log = "0.4" 39log = "0.4"
39env_logger = "0.9" 40env_logger = "0.9"
40rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version 41rand = "0.8"
41futures = { version = "0.3", features = ["executor"] } 42futures = { version = "0.3", features = ["executor"] }
42sha1 = "0.10.5" 43sha1 = "0.10.5"
43critical-section = { version = "1.1.1", features = ["std"] } 44critical-section = { version = "1.1.1", features = ["std"] }
44 45ed25519-dalek = { version = "2", default_features = false, features = ["std", "rand_core", "digest"] }
45[dev-dependencies.ed25519-dalek]
46default_features = false
47features = ["rand", "std", "u32_backend"]
48 46
49[features] 47[features]
50ed25519-dalek = ["dep:ed25519-dalek", "_verify"] 48ed25519-dalek = ["dep:ed25519-dalek", "_verify"]
diff --git a/embassy-boot/README.md b/embassy-boot/README.md
new file mode 100644
index 000000000..3c2d45e96
--- /dev/null
+++ b/embassy-boot/README.md
@@ -0,0 +1,35 @@
1# embassy-boot
2
3An [Embassy](https://embassy.dev) project.
4
5A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks.
6
7The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts.
8
9By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
10
11## Overview
12
13The bootloader divides the storage into 4 main partitions, configurable when creating the bootloader instance or via linker scripts:
14
15* BOOTLOADER - Where the bootloader is placed. The bootloader itself consumes about 8kB of flash, but if you need to debug it and have space available, increasing this to 24kB will allow you to run the bootloader with probe-rs.
16* ACTIVE - Where the main application is placed. The bootloader will attempt to load the application at the start of this partition. The minimum size required for this partition is the size of your application.
17* DFU - Where the application-to-be-swapped is placed. This partition is written to by the application. This partition must be at least 1 page bigger than the ACTIVE partition.
18* BOOTLOADER STATE - Where the bootloader stores the current state describing if the active and dfu partitions need to be swapped.
19
20For any partition, the following preconditions are required:
21
22* Partitions must be aligned on the page size.
23* Partitions must be a multiple of the page size.
24
25The linker scripts for the application and bootloader look similar, but the FLASH region must point to the BOOTLOADER partition for the bootloader, and the ACTIVE partition for the application.
26
27For more details on the bootloader, see [the documentation](https://embassy.dev/book/dev/bootloader.html).
28
29## Hardware support
30
31The bootloader supports different hardware in separate crates:
32
33* `embassy-boot-nrf` - for the nRF microcontrollers.
34* `embassy-boot-rp` - for the RP2040 microcontrollers.
35* `embassy-boot-stm32` - for the STM32 microcontrollers.
diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md
deleted file mode 100644
index 07755bc6c..000000000
--- a/embassy-boot/boot/README.md
+++ /dev/null
@@ -1,31 +0,0 @@
1# embassy-boot
2
3An [Embassy](https://embassy.dev) project.
4
5A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks.
6
7The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts.
8
9By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself.
10
11## 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/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml
deleted file mode 100644
index eea29cf2e..000000000
--- a/embassy-boot/nrf/Cargo.toml
+++ /dev/null
@@ -1,38 +0,0 @@
1[package]
2edition = "2021"
3name = "embassy-boot-nrf"
4version = "0.1.0"
5description = "Bootloader lib for nRF chips"
6license = "MIT OR Apache-2.0"
7
8[package.metadata.embassy_docs]
9src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/"
10src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/nrf/src/"
11features = ["embassy-nrf/nrf52840"]
12target = "thumbv7em-none-eabi"
13
14[lib]
15
16[dependencies]
17defmt = { version = "0.3", optional = true }
18
19embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
20embassy-nrf = { path = "../../embassy-nrf" }
21embassy-boot = { path = "../boot", default-features = false }
22cortex-m = { version = "0.7.6" }
23cortex-m-rt = { version = "0.7" }
24embedded-storage = "0.3.1"
25embedded-storage-async = { version = "0.4.1" }
26cfg-if = "1.0.0"
27
28nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true }
29
30[features]
31defmt = [
32 "dep:defmt",
33 "embassy-boot/defmt",
34 "embassy-nrf/defmt",
35]
36softdevice = [
37 "nrf-softdevice-mbr",
38]
diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md
deleted file mode 100644
index fe581823d..000000000
--- a/embassy-boot/nrf/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
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/fmt.rs b/embassy-boot/nrf/src/fmt.rs
deleted file mode 100644
index 78e583c1c..000000000
--- a/embassy-boot/nrf/src/fmt.rs
+++ /dev/null
@@ -1,258 +0,0 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*)
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232#[allow(unused)]
233pub(crate) struct Bytes<'a>(pub &'a [u8]);
234
235impl<'a> Debug for Bytes<'a> {
236 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 write!(f, "{:#02x?}", self.0)
238 }
239}
240
241impl<'a> Display for Bytes<'a> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 write!(f, "{:#02x?}", self.0)
244 }
245}
246
247impl<'a> LowerHex for Bytes<'a> {
248 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249 write!(f, "{:#02x?}", self.0)
250 }
251}
252
253#[cfg(feature = "defmt")]
254impl<'a> defmt::Format for Bytes<'a> {
255 fn format(&self, fmt: defmt::Formatter) {
256 defmt::write!(fmt, "{:02x}", self.0)
257 }
258}
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs
deleted file mode 100644
index 5b20a93c6..000000000
--- a/embassy-boot/nrf/src/lib.rs
+++ /dev/null
@@ -1,145 +0,0 @@
1#![no_std]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4mod fmt;
5
6pub use embassy_boot::{
7 AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater,
8 FirmwareUpdaterConfig,
9};
10use embassy_nrf::nvmc::PAGE_SIZE;
11use embassy_nrf::peripherals::WDT;
12use embassy_nrf::wdt;
13use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
14
15/// A bootloader for nRF devices.
16pub struct BootLoader<const BUFFER_SIZE: usize = PAGE_SIZE>;
17
18impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
19 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware.
20 pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
21 config: BootLoaderConfig<ACTIVE, DFU, STATE>,
22 ) -> Self {
23 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
24 let mut boot = embassy_boot::BootLoader::new(config);
25 boot.prepare_boot(&mut aligned_buf.0).expect("Boot prepare error");
26 Self
27 }
28
29 /// Boots the application without softdevice mechanisms.
30 ///
31 /// # Safety
32 ///
33 /// This modifies the stack pointer and reset vector and will run code placed in the active partition.
34 #[cfg(not(feature = "softdevice"))]
35 pub unsafe fn load(self, start: u32) -> ! {
36 let mut p = cortex_m::Peripherals::steal();
37 p.SCB.invalidate_icache();
38 p.SCB.vtor.write(start);
39 cortex_m::asm::bootload(start as *const u32)
40 }
41
42 /// Boots the application assuming softdevice is present.
43 ///
44 /// # Safety
45 ///
46 /// This modifies the stack pointer and reset vector and will run code placed in the active partition.
47 #[cfg(feature = "softdevice")]
48 pub unsafe fn load(self, _app: u32) -> ! {
49 use nrf_softdevice_mbr as mbr;
50 const NRF_SUCCESS: u32 = 0;
51
52 // Address of softdevice which we'll forward interrupts to
53 let addr = 0x1000;
54 let mut cmd = mbr::sd_mbr_command_t {
55 command: mbr::NRF_MBR_COMMANDS_SD_MBR_COMMAND_IRQ_FORWARD_ADDRESS_SET,
56 params: mbr::sd_mbr_command_t__bindgen_ty_1 {
57 irq_forward_address_set: mbr::sd_mbr_command_irq_forward_address_set_t { address: addr },
58 },
59 };
60 let ret = mbr::sd_mbr_command(&mut cmd);
61 assert_eq!(ret, NRF_SUCCESS);
62
63 let msp = *(addr as *const u32);
64 let rv = *((addr + 4) as *const u32);
65
66 trace!("msp = {=u32:x}, rv = {=u32:x}", msp, rv);
67
68 // These instructions perform the following operations:
69 //
70 // * Modify control register to use MSP as stack pointer (clear spsel bit)
71 // * Synchronize instruction barrier
72 // * Initialize stack pointer (0x1000)
73 // * Set link register to not return (0xFF)
74 // * Jump to softdevice reset vector
75 core::arch::asm!(
76 "mrs {tmp}, CONTROL",
77 "bics {tmp}, {spsel}",
78 "msr CONTROL, {tmp}",
79 "isb",
80 "msr MSP, {msp}",
81 "mov lr, {new_lr}",
82 "bx {rv}",
83 // `out(reg) _` is not permitted in a `noreturn` asm! call,
84 // so instead use `in(reg) 0` and don't restore it afterwards.
85 tmp = in(reg) 0,
86 spsel = in(reg) 2,
87 new_lr = in(reg) 0xFFFFFFFFu32,
88 msp = in(reg) msp,
89 rv = in(reg) rv,
90 options(noreturn),
91 );
92 }
93}
94
95/// A flash implementation that wraps any flash and will pet a watchdog when touching flash.
96pub struct WatchdogFlash<FLASH> {
97 flash: FLASH,
98 wdt: wdt::WatchdogHandle,
99}
100
101impl<FLASH> WatchdogFlash<FLASH> {
102 /// Start a new watchdog with a given flash and WDT peripheral and a timeout
103 pub fn start(flash: FLASH, wdt: WDT, config: wdt::Config) -> Self {
104 let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) {
105 Ok(x) => x,
106 Err(_) => {
107 // In case the watchdog is already running, just spin and let it expire, since
108 // we can't configure it anyway. This usually happens when we first program
109 // the device and the watchdog was previously active
110 info!("Watchdog already active with wrong config, waiting for it to timeout...");
111 loop {}
112 }
113 };
114 Self { flash, wdt }
115 }
116}
117
118impl<FLASH: ErrorType> ErrorType for WatchdogFlash<FLASH> {
119 type Error = FLASH::Error;
120}
121
122impl<FLASH: NorFlash> NorFlash for WatchdogFlash<FLASH> {
123 const WRITE_SIZE: usize = FLASH::WRITE_SIZE;
124 const ERASE_SIZE: usize = FLASH::ERASE_SIZE;
125
126 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
127 self.wdt.pet();
128 self.flash.erase(from, to)
129 }
130 fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
131 self.wdt.pet();
132 self.flash.write(offset, data)
133 }
134}
135
136impl<FLASH: ReadNorFlash> ReadNorFlash for WatchdogFlash<FLASH> {
137 const READ_SIZE: usize = FLASH::READ_SIZE;
138 fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
139 self.wdt.pet();
140 self.flash.read(offset, data)
141 }
142 fn capacity(&self) -> usize {
143 self.flash.capacity()
144 }
145}
diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml
deleted file mode 100644
index 0f2dc4628..000000000
--- a/embassy-boot/rp/Cargo.toml
+++ /dev/null
@@ -1,73 +0,0 @@
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 = { version = "0.5.0", 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.1"
28embedded-storage-async = { version = "0.4.1" }
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"]
43
44[profile.dev]
45debug = 2
46debug-assertions = true
47incremental = false
48opt-level = 'z'
49overflow-checks = true
50
51[profile.release]
52codegen-units = 1
53debug = 2
54debug-assertions = false
55incremental = false
56lto = 'fat'
57opt-level = 'z'
58overflow-checks = false
59
60# do not optimize proc-macro crates = faster builds from scratch
61[profile.dev.build-override]
62codegen-units = 8
63debug = false
64debug-assertions = false
65opt-level = 0
66overflow-checks = false
67
68[profile.release.build-override]
69codegen-units = 8
70debug = false
71debug-assertions = false
72opt-level = 0
73overflow-checks = false
diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md
deleted file mode 100644
index 315d655e3..000000000
--- a/embassy-boot/rp/README.md
+++ /dev/null
@@ -1,26 +0,0 @@
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
deleted file mode 100644
index 2cbc7ef5e..000000000
--- a/embassy-boot/rp/build.rs
+++ /dev/null
@@ -1,8 +0,0 @@
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
deleted file mode 100644
index 78e583c1c..000000000
--- a/embassy-boot/rp/src/fmt.rs
+++ /dev/null
@@ -1,258 +0,0 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*)
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232#[allow(unused)]
233pub(crate) struct Bytes<'a>(pub &'a [u8]);
234
235impl<'a> Debug for Bytes<'a> {
236 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 write!(f, "{:#02x?}", self.0)
238 }
239}
240
241impl<'a> Display for Bytes<'a> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 write!(f, "{:#02x?}", self.0)
244 }
245}
246
247impl<'a> LowerHex for Bytes<'a> {
248 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249 write!(f, "{:#02x?}", self.0)
250 }
251}
252
253#[cfg(feature = "defmt")]
254impl<'a> defmt::Format for Bytes<'a> {
255 fn format(&self, fmt: defmt::Formatter) {
256 defmt::write!(fmt, "{:02x}", self.0)
257 }
258}
diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs
deleted file mode 100644
index 07a5b3f4d..000000000
--- a/embassy-boot/rp/src/lib.rs
+++ /dev/null
@@ -1,90 +0,0 @@
1#![no_std]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4mod fmt;
5
6pub use embassy_boot::{
7 AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater,
8 FirmwareUpdaterConfig, State,
9};
10use embassy_rp::flash::{Blocking, Flash, ERASE_SIZE};
11use embassy_rp::peripherals::{FLASH, WATCHDOG};
12use embassy_rp::watchdog::Watchdog;
13use embassy_time::Duration;
14use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash};
15
16/// A bootloader for RP2040 devices.
17pub struct BootLoader<const BUFFER_SIZE: usize = ERASE_SIZE>;
18
19impl<const BUFFER_SIZE: usize> BootLoader<BUFFER_SIZE> {
20 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
21 pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
22 config: BootLoaderConfig<ACTIVE, DFU, STATE>,
23 ) -> Self {
24 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
25 let mut boot = embassy_boot::BootLoader::new(config);
26 boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
27 Self
28 }
29
30 /// Boots the application.
31 ///
32 /// # Safety
33 ///
34 /// This modifies the stack pointer and reset vector and will run code placed in the active partition.
35 pub unsafe fn load(self, start: u32) -> ! {
36 trace!("Loading app at 0x{:x}", start);
37 #[allow(unused_mut)]
38 let mut p = cortex_m::Peripherals::steal();
39 #[cfg(not(armv6m))]
40 p.SCB.invalidate_icache();
41 p.SCB.vtor.write(start);
42
43 cortex_m::asm::bootload(start as *const u32)
44 }
45}
46
47/// A flash implementation that will feed a watchdog when touching flash.
48pub struct WatchdogFlash<'d, const SIZE: usize> {
49 flash: Flash<'d, FLASH, Blocking, SIZE>,
50 watchdog: Watchdog,
51}
52
53impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> {
54 /// Start a new watchdog with a given flash and watchdog peripheral and a timeout
55 pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self {
56 let flash = Flash::<_, Blocking, SIZE>::new_blocking(flash);
57 let mut watchdog = Watchdog::new(watchdog);
58 watchdog.start(timeout);
59 Self { flash, watchdog }
60 }
61}
62
63impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> {
64 type Error = <Flash<'d, FLASH, Blocking, SIZE> as ErrorType>::Error;
65}
66
67impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> {
68 const WRITE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::WRITE_SIZE;
69 const ERASE_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as NorFlash>::ERASE_SIZE;
70
71 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
72 self.watchdog.feed();
73 self.flash.blocking_erase(from, to)
74 }
75 fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> {
76 self.watchdog.feed();
77 self.flash.blocking_write(offset, data)
78 }
79}
80
81impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> {
82 const READ_SIZE: usize = <Flash<'d, FLASH, Blocking, SIZE> as ReadNorFlash>::READ_SIZE;
83 fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> {
84 self.watchdog.feed();
85 self.flash.blocking_read(offset, data)
86 }
87 fn capacity(&self) -> usize {
88 self.flash.capacity()
89 }
90}
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/src/boot_loader.rs
index a8c19197b..a38558056 100644
--- a/embassy-boot/boot/src/boot_loader.rs
+++ b/embassy-boot/src/boot_loader.rs
@@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex;
5use embassy_sync::blocking_mutex::Mutex; 5use embassy_sync::blocking_mutex::Mutex;
6use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; 6use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind};
7 7
8use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 8use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
9 9
10/// Errors returned by bootloader 10/// Errors returned by bootloader
11#[derive(PartialEq, Eq, Debug)] 11#[derive(PartialEq, Eq, Debug)]
@@ -49,16 +49,51 @@ pub struct BootLoaderConfig<ACTIVE, DFU, STATE> {
49 pub state: STATE, 49 pub state: STATE,
50} 50}
51 51
52impl<'a, FLASH: NorFlash> 52impl<'a, ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>
53 BootLoaderConfig< 53 BootLoaderConfig<
54 BlockingPartition<'a, NoopRawMutex, FLASH>, 54 BlockingPartition<'a, NoopRawMutex, ACTIVE>,
55 BlockingPartition<'a, NoopRawMutex, FLASH>, 55 BlockingPartition<'a, NoopRawMutex, DFU>,
56 BlockingPartition<'a, NoopRawMutex, FLASH>, 56 BlockingPartition<'a, NoopRawMutex, STATE>,
57 > 57 >
58{ 58{
59 /// Create a bootloader config from the flash and address symbols defined in the linkerfile 59 /// Constructs a `BootLoaderConfig` instance from flash memory and address symbols defined in the linker file.
60 ///
61 /// This method initializes `BlockingPartition` instances for the active, DFU (Device Firmware Update),
62 /// and state partitions, leveraging start and end addresses specified by the linker. These partitions
63 /// are critical for managing firmware updates, application state, and boot operations within the bootloader.
64 ///
65 /// # Parameters
66 /// - `active_flash`: A reference to a mutex-protected `RefCell` for the active partition's flash interface.
67 /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface.
68 /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface.
69 ///
70 /// # Safety
71 /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses
72 /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined
73 /// in the memory.x file to prevent undefined behavior.
74 ///
75 /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory
76 /// interfaces provided are compatible with these regions.
77 ///
78 /// # Returns
79 /// A `BootLoaderConfig` instance with `BlockingPartition` instances for the active, DFU, and state partitions.
80 ///
81 /// # Example
82 /// ```ignore
83 /// // Assume `active_flash`, `dfu_flash`, and `state_flash` all share the same flash memory interface.
84 /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
85 /// let flash = Mutex::new(RefCell::new(layout.bank1_region));
86 ///
87 /// let config = BootLoaderConfig::from_linkerfile_blocking(&flash, &flash, &flash);
88 /// // `config` can now be used to create a `BootLoader` instance for managing boot operations.
89 /// ```
90 /// Working examples can be found in the bootloader examples folder.
60 // #[cfg(target_os = "none")] 91 // #[cfg(target_os = "none")]
61 pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { 92 pub fn from_linkerfile_blocking(
93 active_flash: &'a Mutex<NoopRawMutex, RefCell<ACTIVE>>,
94 dfu_flash: &'a Mutex<NoopRawMutex, RefCell<DFU>>,
95 state_flash: &'a Mutex<NoopRawMutex, RefCell<STATE>>,
96 ) -> Self {
62 extern "C" { 97 extern "C" {
63 static __bootloader_state_start: u32; 98 static __bootloader_state_start: u32;
64 static __bootloader_state_end: u32; 99 static __bootloader_state_end: u32;
@@ -73,21 +108,21 @@ impl<'a, FLASH: NorFlash>
73 let end = &__bootloader_active_end as *const u32 as u32; 108 let end = &__bootloader_active_end as *const u32 as u32;
74 trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); 109 trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end);
75 110
76 BlockingPartition::new(flash, start, end - start) 111 BlockingPartition::new(active_flash, start, end - start)
77 }; 112 };
78 let dfu = unsafe { 113 let dfu = unsafe {
79 let start = &__bootloader_dfu_start as *const u32 as u32; 114 let start = &__bootloader_dfu_start as *const u32 as u32;
80 let end = &__bootloader_dfu_end as *const u32 as u32; 115 let end = &__bootloader_dfu_end as *const u32 as u32;
81 trace!("DFU: 0x{:x} - 0x{:x}", start, end); 116 trace!("DFU: 0x{:x} - 0x{:x}", start, end);
82 117
83 BlockingPartition::new(flash, start, end - start) 118 BlockingPartition::new(dfu_flash, start, end - start)
84 }; 119 };
85 let state = unsafe { 120 let state = unsafe {
86 let start = &__bootloader_state_start as *const u32 as u32; 121 let start = &__bootloader_state_start as *const u32 as u32;
87 let end = &__bootloader_state_end as *const u32 as u32; 122 let end = &__bootloader_state_end as *const u32 as u32;
88 trace!("STATE: 0x{:x} - 0x{:x}", start, end); 123 trace!("STATE: 0x{:x} - 0x{:x}", start, end);
89 124
90 BlockingPartition::new(flash, start, end - start) 125 BlockingPartition::new(state_flash, start, end - start)
91 }; 126 };
92 127
93 Self { active, dfu, state } 128 Self { active, dfu, state }
@@ -135,51 +170,44 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
135 /// The provided aligned_buf argument must satisfy any alignment requirements 170 /// The provided aligned_buf argument must satisfy any alignment requirements
136 /// given by the partition flashes. All flash operations will use this buffer. 171 /// given by the partition flashes. All flash operations will use this buffer.
137 /// 172 ///
138 /// SWAPPING 173 /// ## SWAPPING
139 /// 174 ///
140 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. 175 /// 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 176 /// 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 177 /// 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) 178 /// 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 179 /// 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"). 180 /// avoid a situation where the wrong index value is set (page write size is "atomic").
146 /// 181 ///
147 /// +-----------+------------+--------+--------+--------+--------+ 182 ///
148 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | 183 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
149 /// +-----------+------------+--------+--------+--------+--------+ 184 /// |-----------|------------|--------|--------|--------|--------|
150 /// | Active | 0 | 1 | 2 | 3 | - | 185 /// | Active | 0 | 1 | 2 | 3 | - |
151 /// | DFU | 0 | 3 | 2 | 1 | X | 186 /// | DFU | 0 | 4 | 5 | 6 | X |
152 /// +-----------+------------+--------+--------+--------+--------+
153 /// 187 ///
154 /// The algorithm starts by copying 'backwards', and after the first step, the layout is 188 /// The algorithm starts by copying 'backwards', and after the first step, the layout is
155 /// as follows: 189 /// as follows:
156 /// 190 ///
157 /// +-----------+------------+--------+--------+--------+--------+
158 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | 191 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
159 /// +-----------+------------+--------+--------+--------+--------+ 192 /// |-----------|------------|--------|--------|--------|--------|
160 /// | Active | 1 | 1 | 2 | 1 | - | 193 /// | Active | 1 | 1 | 2 | 6 | - |
161 /// | DFU | 1 | 3 | 2 | 1 | 3 | 194 /// | DFU | 1 | 4 | 5 | 6 | 3 |
162 /// +-----------+------------+--------+--------+--------+--------+
163 /// 195 ///
164 /// The next iteration performs the same steps 196 /// The next iteration performs the same steps
165 /// 197 ///
166 /// +-----------+------------+--------+--------+--------+--------+
167 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | 198 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
168 /// +-----------+------------+--------+--------+--------+--------+ 199 /// |-----------|------------|--------|--------|--------|--------|
169 /// | Active | 2 | 1 | 2 | 1 | - | 200 /// | Active | 2 | 1 | 5 | 6 | - |
170 /// | DFU | 2 | 3 | 2 | 2 | 3 | 201 /// | DFU | 2 | 4 | 5 | 2 | 3 |
171 /// +-----------+------------+--------+--------+--------+--------+
172 /// 202 ///
173 /// And again until we're done 203 /// And again until we're done
174 /// 204 ///
175 /// +-----------+------------+--------+--------+--------+--------+
176 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | 205 /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 |
177 /// +-----------+------------+--------+--------+--------+--------+ 206 /// |-----------|------------|--------|--------|--------|--------|
178 /// | Active | 3 | 3 | 2 | 1 | - | 207 /// | Active | 3 | 4 | 5 | 6 | - |
179 /// | DFU | 3 | 3 | 1 | 2 | 3 | 208 /// | DFU | 3 | 4 | 1 | 2 | 3 |
180 /// +-----------+------------+--------+--------+--------+--------+
181 /// 209 ///
182 /// REVERTING 210 /// ## REVERTING
183 /// 211 ///
184 /// The reverting algorithm uses the swap index to discover that images were swapped, but that 212 /// 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 213 /// the application failed to mark the boot successful. In this case, the revert algorithm will
@@ -190,28 +218,21 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
190 /// 218 ///
191 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. 219 /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start.
192 /// 220 ///
193 /// +-----------+--------------+--------+--------+--------+--------+
194 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | 221 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
195 //*/ 222 /// |-----------|--------------|--------|--------|--------|--------|
196 /// +-----------+--------------+--------+--------+--------+--------+ 223 /// | Active | 3 | 1 | 5 | 6 | - |
197 /// | Active | 3 | 1 | 2 | 1 | - | 224 /// | DFU | 3 | 4 | 1 | 2 | 3 |
198 /// | DFU | 3 | 3 | 1 | 2 | 3 |
199 /// +-----------+--------------+--------+--------+--------+--------+
200 /// 225 ///
201 /// 226 ///
202 /// +-----------+--------------+--------+--------+--------+--------+
203 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | 227 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
204 /// +-----------+--------------+--------+--------+--------+--------+ 228 /// |-----------|--------------|--------|--------|--------|--------|
205 /// | Active | 3 | 1 | 2 | 1 | - | 229 /// | Active | 3 | 1 | 2 | 6 | - |
206 /// | DFU | 3 | 3 | 2 | 2 | 3 | 230 /// | DFU | 3 | 4 | 5 | 2 | 3 |
207 /// +-----------+--------------+--------+--------+--------+--------+
208 /// 231 ///
209 /// +-----------+--------------+--------+--------+--------+--------+
210 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | 232 /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 |
211 /// +-----------+--------------+--------+--------+--------+--------+ 233 /// |-----------|--------------|--------|--------|--------|--------|
212 /// | Active | 3 | 1 | 2 | 3 | - | 234 /// | Active | 3 | 1 | 2 | 3 | - |
213 /// | DFU | 3 | 3 | 2 | 1 | 3 | 235 /// | DFU | 3 | 4 | 5 | 6 | 3 |
214 /// +-----------+--------------+--------+--------+--------+--------+
215 /// 236 ///
216 pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { 237 pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> {
217 // Ensure we have enough progress pages to store copy progress 238 // Ensure we have enough progress pages to store copy progress
@@ -224,6 +245,7 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
224 assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); 245 assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE);
225 assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); 246 assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE);
226 247
248 // Ensure our partitions are able to handle boot operations
227 assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); 249 assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE);
228 250
229 // Copy contents from partition N to active 251 // Copy contents from partition N to active
@@ -384,6 +406,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
384 406
385 if !state_word.iter().any(|&b| b != SWAP_MAGIC) { 407 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
386 Ok(State::Swap) 408 Ok(State::Swap)
409 } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
410 Ok(State::DfuDetach)
387 } else { 411 } else {
388 Ok(State::Boot) 412 Ok(State::Boot)
389 } 413 }
@@ -398,6 +422,7 @@ fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>(
398) { 422) {
399 assert_eq!(active.capacity() as u32 % page_size, 0); 423 assert_eq!(active.capacity() as u32 % page_size, 0);
400 assert_eq!(dfu.capacity() as u32 % page_size, 0); 424 assert_eq!(dfu.capacity() as u32 % page_size, 0);
425 // DFU partition has to be bigger than ACTIVE partition to handle swap algorithm
401 assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); 426 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); 427 assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32);
403} 428}
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/src/digest_adapters/ed25519_dalek.rs
index a184d1c51..2e4e03da3 100644
--- a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs
+++ b/embassy-boot/src/digest_adapters/ed25519_dalek.rs
@@ -1,6 +1,6 @@
1use digest::typenum::U64; 1use digest::typenum::U64;
2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; 2use digest::{FixedOutput, HashMarker, OutputSizeUser, Update};
3use ed25519_dalek::Digest as _; 3use ed25519_dalek::Digest;
4 4
5pub struct Sha512(ed25519_dalek::Sha512); 5pub struct Sha512(ed25519_dalek::Sha512);
6 6
@@ -12,7 +12,7 @@ impl Default for Sha512 {
12 12
13impl Update for Sha512 { 13impl Update for Sha512 {
14 fn update(&mut self, data: &[u8]) { 14 fn update(&mut self, data: &[u8]) {
15 self.0.update(data) 15 Digest::update(&mut self.0, data)
16 } 16 }
17} 17}
18 18
diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/src/digest_adapters/mod.rs
index 9b4b4b60c..9b4b4b60c 100644
--- a/embassy-boot/boot/src/digest_adapters/mod.rs
+++ b/embassy-boot/src/digest_adapters/mod.rs
diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/src/digest_adapters/salty.rs
index 2b5dcf3af..2b5dcf3af 100644
--- a/embassy-boot/boot/src/digest_adapters/salty.rs
+++ b/embassy-boot/src/digest_adapters/salty.rs
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/src/firmware_updater/asynch.rs
index ae713bb6f..26f65f295 100644
--- a/embassy-boot/boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/src/firmware_updater/asynch.rs
@@ -6,21 +6,25 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex;
6use embedded_storage_async::nor_flash::NorFlash; 6use embedded_storage_async::nor_flash::NorFlash;
7 7
8use super::FirmwareUpdaterConfig; 8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
10 10
11/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to 11/// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
12/// 'mess up' the internal bootloader state 12/// 'mess up' the internal bootloader state
13pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { 13pub struct FirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU, 14 dfu: DFU,
15 state: FirmwareState<'d, STATE>, 15 state: FirmwareState<'d, STATE>,
16 last_erased_dfu_sector_index: Option<usize>,
16} 17}
17 18
18#[cfg(target_os = "none")] 19#[cfg(target_os = "none")]
19impl<'a, FLASH: NorFlash> 20impl<'a, DFU: NorFlash, STATE: NorFlash>
20 FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> 21 FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, DFU>, Partition<'a, NoopRawMutex, STATE>>
21{ 22{
22 /// Create a firmware updater config from the flash and address symbols defined in the linkerfile 23 /// 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 pub fn from_linkerfile(
25 dfu_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, DFU>,
26 state_flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, STATE>,
27 ) -> Self {
24 extern "C" { 28 extern "C" {
25 static __bootloader_state_start: u32; 29 static __bootloader_state_start: u32;
26 static __bootloader_state_end: u32; 30 static __bootloader_state_end: u32;
@@ -33,14 +37,14 @@ impl<'a, FLASH: NorFlash>
33 let end = &__bootloader_dfu_end as *const u32 as u32; 37 let end = &__bootloader_dfu_end as *const u32 as u32;
34 trace!("DFU: 0x{:x} - 0x{:x}", start, end); 38 trace!("DFU: 0x{:x} - 0x{:x}", start, end);
35 39
36 Partition::new(flash, start, end - start) 40 Partition::new(dfu_flash, start, end - start)
37 }; 41 };
38 let state = unsafe { 42 let state = unsafe {
39 let start = &__bootloader_state_start as *const u32 as u32; 43 let start = &__bootloader_state_start as *const u32 as u32;
40 let end = &__bootloader_state_end as *const u32 as u32; 44 let end = &__bootloader_state_end as *const u32 as u32;
41 trace!("STATE: 0x{:x} - 0x{:x}", start, end); 45 trace!("STATE: 0x{:x} - 0x{:x}", start, end);
42 46
43 Partition::new(flash, start, end - start) 47 Partition::new(state_flash, start, end - start)
44 }; 48 };
45 49
46 Self { dfu, state } 50 Self { dfu, state }
@@ -53,6 +57,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
53 Self { 57 Self {
54 dfu: config.dfu, 58 dfu: config.dfu,
55 state: FirmwareState::new(config.state, aligned), 59 state: FirmwareState::new(config.state, aligned),
60 last_erased_dfu_sector_index: None,
56 } 61 }
57 } 62 }
58 63
@@ -69,7 +74,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
69 /// proceed with updating the firmware as it must be signed with a 74 /// proceed with updating the firmware as it must be signed with a
70 /// corresponding private key (otherwise it could be malicious firmware). 75 /// corresponding private key (otherwise it could be malicious firmware).
71 /// 76 ///
72 /// Mark to trigger firmware swap on next boot if verify suceeds. 77 /// Mark to trigger firmware swap on next boot if verify succeeds.
73 /// 78 ///
74 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have 79 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
75 /// been generated from a SHA-512 digest of the firmware bytes. 80 /// been generated from a SHA-512 digest of the firmware bytes.
@@ -79,8 +84,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
79 #[cfg(feature = "_verify")] 84 #[cfg(feature = "_verify")]
80 pub async fn verify_and_mark_updated( 85 pub async fn verify_and_mark_updated(
81 &mut self, 86 &mut self,
82 _public_key: &[u8], 87 _public_key: &[u8; 32],
83 _signature: &[u8], 88 _signature: &[u8; 64],
84 _update_len: u32, 89 _update_len: u32,
85 ) -> Result<(), FirmwareUpdaterError> { 90 ) -> Result<(), FirmwareUpdaterError> {
86 assert!(_update_len <= self.dfu.capacity() as u32); 91 assert!(_update_len <= self.dfu.capacity() as u32);
@@ -89,14 +94,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
89 94
90 #[cfg(feature = "ed25519-dalek")] 95 #[cfg(feature = "ed25519-dalek")]
91 { 96 {
92 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; 97 use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey};
93 98
94 use crate::digest_adapters::ed25519_dalek::Sha512; 99 use crate::digest_adapters::ed25519_dalek::Sha512;
95 100
96 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); 101 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
97 102
98 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; 103 let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?;
99 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; 104 let signature = Signature::from_bytes(_signature);
100 105
101 let mut chunk_buf = [0; 2]; 106 let mut chunk_buf = [0; 2];
102 let mut message = [0; 64]; 107 let mut message = [0; 64];
@@ -106,7 +111,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
106 } 111 }
107 #[cfg(feature = "ed25519-salty")] 112 #[cfg(feature = "ed25519-salty")]
108 { 113 {
109 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
110 use salty::{PublicKey, Signature}; 114 use salty::{PublicKey, Signature};
111 115
112 use crate::digest_adapters::salty::Sha512; 116 use crate::digest_adapters::salty::Sha512;
@@ -115,10 +119,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
115 FirmwareUpdaterError::Signature(signature::Error::default()) 119 FirmwareUpdaterError::Signature(signature::Error::default())
116 } 120 }
117 121
118 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; 122 let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?;
119 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; 123 let signature = Signature::try_from(_signature).map_err(into_signature_error)?;
120 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
121 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
122 124
123 let mut message = [0; 64]; 125 let mut message = [0; 64];
124 let mut chunk_buf = [0; 2]; 126 let mut chunk_buf = [0; 2];
@@ -161,26 +163,79 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
161 self.state.mark_updated().await 163 self.state.mark_updated().await
162 } 164 }
163 165
166 /// Mark to trigger USB DFU on next boot.
167 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
168 self.state.verify_booted().await?;
169 self.state.mark_dfu().await
170 }
171
164 /// Mark firmware boot successful and stop rollback on reset. 172 /// Mark firmware boot successful and stop rollback on reset.
165 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 173 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
166 self.state.mark_booted().await 174 self.state.mark_booted().await
167 } 175 }
168 176
169 /// Write data to a flash page. 177 /// Writes firmware data to the device.
170 /// 178 ///
171 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. 179 /// This function writes the given data to the firmware area starting at the specified offset.
180 /// It handles sector erasures and data writes while verifying the device is in a proper state
181 /// for firmware updates. The function ensures that only unerased sectors are erased before
182 /// writing and efficiently handles the writing process across sector boundaries and in
183 /// various configurations (data size, sector size, etc.).
172 /// 184 ///
173 /// # Safety 185 /// # Arguments
186 ///
187 /// * `offset` - The starting offset within the firmware area where data writing should begin.
188 /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
189 /// multiple of NorFlash WRITE_SIZE.
174 /// 190 ///
175 /// Failing to meet alignment and size requirements may result in a panic. 191 /// # Returns
192 ///
193 /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
194 ///
195 /// # Errors
196 ///
197 /// This function will return an error if:
198 ///
199 /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
200 /// - There is a failure erasing a sector before writing.
201 /// - There is a failure writing data to the device.
176 pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { 202 pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
177 assert!(data.len() >= DFU::ERASE_SIZE); 203 // Make sure we are running a booted firmware to avoid reverting to a bad state.
178
179 self.state.verify_booted().await?; 204 self.state.verify_booted().await?;
180 205
181 self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; 206 // Initialize variables to keep track of the remaining data and the current offset.
207 let mut remaining_data = data;
208 let mut offset = offset;
209
210 // Continue writing as long as there is data left to write.
211 while !remaining_data.is_empty() {
212 // Compute the current sector and its boundaries.
213 let current_sector = offset / DFU::ERASE_SIZE;
214 let sector_start = current_sector * DFU::ERASE_SIZE;
215 let sector_end = sector_start + DFU::ERASE_SIZE;
216 // Determine if the current sector needs to be erased before writing.
217 let need_erase = self
218 .last_erased_dfu_sector_index
219 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
220
221 // If the sector needs to be erased, erase it and update the last erased sector index.
222 if need_erase {
223 self.dfu.erase(sector_start as u32, sector_end as u32).await?;
224 self.last_erased_dfu_sector_index = Some(current_sector);
225 }
226
227 // Calculate the size of the data chunk that can be written in the current iteration.
228 let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
229 // Split the data to get the current chunk to be written and the remaining data.
230 let (data_chunk, rest) = remaining_data.split_at(write_size);
182 231
183 self.dfu.write(offset as u32, data).await?; 232 // Write the current data chunk.
233 self.dfu.write(offset as u32, data_chunk).await?;
234
235 // Update the offset and remaining data for the next iteration.
236 remaining_data = rest;
237 offset += write_size;
238 }
184 239
185 Ok(()) 240 Ok(())
186 } 241 }
@@ -207,14 +262,24 @@ pub struct FirmwareState<'d, STATE> {
207} 262}
208 263
209impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { 264impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
210 /// Create a firmware state instance with a buffer for magic content and state partition. 265 /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition.
211 /// 266 ///
212 /// # Safety 267 /// # Safety
213 /// 268 ///
214 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from 269 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
215 /// and written to. 270 /// and written to.
271 pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
272 Self::new(config.state, aligned)
273 }
274
275 /// Create a firmware state instance with a buffer for magic content and state partition.
276 ///
277 /// # Safety
278 ///
279 /// The `aligned` buffer must have a size of maximum of STATE::WRITE_SIZE and STATE::READ_SIZE,
280 /// and follow the alignment rules for the flash being read from and written to.
216 pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self { 281 pub fn new(state: STATE, aligned: &'d mut [u8]) -> Self {
217 assert_eq!(aligned.len(), STATE::WRITE_SIZE); 282 assert_eq!(aligned.len(), STATE::WRITE_SIZE.max(STATE::READ_SIZE));
218 Self { state, aligned } 283 Self { state, aligned }
219 } 284 }
220 285
@@ -247,6 +312,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
247 self.set_magic(SWAP_MAGIC).await 312 self.set_magic(SWAP_MAGIC).await
248 } 313 }
249 314
315 /// Mark to trigger USB DFU on next boot.
316 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
317 self.set_magic(DFU_DETACH_MAGIC).await
318 }
319
250 /// Mark firmware boot successful and stop rollback on reset. 320 /// Mark firmware boot successful and stop rollback on reset.
251 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 321 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
252 self.set_magic(BOOT_MAGIC).await 322 self.set_magic(BOOT_MAGIC).await
@@ -255,16 +325,25 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
255 async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> { 325 async fn set_magic(&mut self, magic: u8) -> Result<(), FirmwareUpdaterError> {
256 self.state.read(0, &mut self.aligned).await?; 326 self.state.read(0, &mut self.aligned).await?;
257 327
258 if self.aligned.iter().any(|&b| b != magic) { 328 if self.aligned[..STATE::WRITE_SIZE].iter().any(|&b| b != magic) {
259 // Read progress validity 329 // Read progress validity
260 self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?; 330 if STATE::READ_SIZE <= 2 * STATE::WRITE_SIZE {
331 self.state.read(STATE::WRITE_SIZE as u32, &mut self.aligned).await?;
332 } else {
333 self.aligned.rotate_left(STATE::WRITE_SIZE);
334 }
261 335
262 if self.aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { 336 if self.aligned[..STATE::WRITE_SIZE]
337 .iter()
338 .any(|&b| b != STATE_ERASE_VALUE)
339 {
263 // The current progress validity marker is invalid 340 // The current progress validity marker is invalid
264 } else { 341 } else {
265 // Invalidate progress 342 // Invalidate progress
266 self.aligned.fill(!STATE_ERASE_VALUE); 343 self.aligned.fill(!STATE_ERASE_VALUE);
267 self.state.write(STATE::WRITE_SIZE as u32, &self.aligned).await?; 344 self.state
345 .write(STATE::WRITE_SIZE as u32, &self.aligned[..STATE::WRITE_SIZE])
346 .await?;
268 } 347 }
269 348
270 // Clear magic and progress 349 // Clear magic and progress
@@ -272,7 +351,7 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
272 351
273 // Set magic 352 // Set magic
274 self.aligned.fill(magic); 353 self.aligned.fill(magic);
275 self.state.write(0, &self.aligned).await?; 354 self.state.write(0, &self.aligned[..STATE::WRITE_SIZE]).await?;
276 } 355 }
277 Ok(()) 356 Ok(())
278 } 357 }
@@ -308,4 +387,76 @@ mod tests {
308 387
309 assert_eq!(Sha1::digest(update).as_slice(), hash); 388 assert_eq!(Sha1::digest(update).as_slice(), hash);
310 } 389 }
390
391 #[test]
392 fn can_verify_sha1_sector_bigger_than_chunk() {
393 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default());
394 let state = Partition::new(&flash, 0, 4096);
395 let dfu = Partition::new(&flash, 65536, 65536);
396 let mut aligned = [0; 8];
397
398 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
399 let mut to_write = [0; 4096];
400 to_write[..7].copy_from_slice(update.as_slice());
401
402 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
403 let mut offset = 0;
404 for chunk in to_write.chunks(1024) {
405 block_on(updater.write_firmware(offset, chunk)).unwrap();
406 offset += chunk.len();
407 }
408 let mut chunk_buf = [0; 2];
409 let mut hash = [0; 20];
410 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
411
412 assert_eq!(Sha1::digest(update).as_slice(), hash);
413 }
414
415 #[test]
416 fn can_verify_sha1_sector_smaller_than_chunk() {
417 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
418 let state = Partition::new(&flash, 0, 4096);
419 let dfu = Partition::new(&flash, 65536, 65536);
420 let mut aligned = [0; 8];
421
422 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
423 let mut to_write = [0; 4096];
424 to_write[..7].copy_from_slice(update.as_slice());
425
426 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
427 let mut offset = 0;
428 for chunk in to_write.chunks(2048) {
429 block_on(updater.write_firmware(offset, chunk)).unwrap();
430 offset += chunk.len();
431 }
432 let mut chunk_buf = [0; 2];
433 let mut hash = [0; 20];
434 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
435
436 assert_eq!(Sha1::digest(update).as_slice(), hash);
437 }
438
439 #[test]
440 fn can_verify_sha1_cross_sector_boundary() {
441 let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 1024, 8>::default());
442 let state = Partition::new(&flash, 0, 4096);
443 let dfu = Partition::new(&flash, 65536, 65536);
444 let mut aligned = [0; 8];
445
446 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
447 let mut to_write = [0; 4096];
448 to_write[..7].copy_from_slice(update.as_slice());
449
450 let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
451 let mut offset = 0;
452 for chunk in to_write.chunks(896) {
453 block_on(updater.write_firmware(offset, chunk)).unwrap();
454 offset += chunk.len();
455 }
456 let mut chunk_buf = [0; 2];
457 let mut hash = [0; 20];
458 block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap();
459
460 assert_eq!(Sha1::digest(update).as_slice(), hash);
461 }
311} 462}
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/src/firmware_updater/blocking.rs
index 76e4264a0..35772a856 100644
--- a/embassy-boot/boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/src/firmware_updater/blocking.rs
@@ -6,22 +6,54 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex;
6use embedded_storage::nor_flash::NorFlash; 6use embedded_storage::nor_flash::NorFlash;
7 7
8use super::FirmwareUpdaterConfig; 8use super::FirmwareUpdaterConfig;
9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; 9use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC};
10 10
11/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to 11/// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to
12/// 'mess up' the internal bootloader state 12/// 'mess up' the internal bootloader state
13pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> { 13pub struct BlockingFirmwareUpdater<'d, DFU: NorFlash, STATE: NorFlash> {
14 dfu: DFU, 14 dfu: DFU,
15 state: BlockingFirmwareState<'d, STATE>, 15 state: BlockingFirmwareState<'d, STATE>,
16 last_erased_dfu_sector_index: Option<usize>,
16} 17}
17 18
18#[cfg(target_os = "none")] 19#[cfg(target_os = "none")]
19impl<'a, FLASH: NorFlash> 20impl<'a, DFU: NorFlash, STATE: NorFlash>
20 FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> 21 FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, DFU>, BlockingPartition<'a, NoopRawMutex, STATE>>
21{ 22{
22 /// Create a firmware updater config from the flash and address symbols defined in the linkerfile 23 /// Constructs a `FirmwareUpdaterConfig` instance from flash memory and address symbols defined in the linker file.
24 ///
25 /// This method initializes `BlockingPartition` instances for the DFU (Device Firmware Update), and state
26 /// partitions, leveraging start and end addresses specified by the linker. These partitions are critical
27 /// for managing firmware updates, application state, and boot operations within the bootloader.
28 ///
29 /// # Parameters
30 /// - `dfu_flash`: A reference to a mutex-protected `RefCell` for the DFU partition's flash interface.
31 /// - `state_flash`: A reference to a mutex-protected `RefCell` for the state partition's flash interface.
32 ///
33 /// # Safety
34 /// The method contains `unsafe` blocks for dereferencing raw pointers that represent the start and end addresses
35 /// of the bootloader's partitions in flash memory. It is crucial that these addresses are accurately defined
36 /// in the memory.x file to prevent undefined behavior.
37 ///
38 /// The caller must ensure that the memory regions defined by these symbols are valid and that the flash memory
39 /// interfaces provided are compatible with these regions.
40 ///
41 /// # Returns
42 /// A `FirmwareUpdaterConfig` instance with `BlockingPartition` instances for the DFU, and state partitions.
43 ///
44 /// # Example
45 /// ```ignore
46 /// // Assume `dfu_flash`, and `state_flash` share the same flash memory interface.
47 /// let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
48 /// let flash = Mutex::new(RefCell::new(layout.bank1_region));
49 ///
50 /// let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash, &flash);
51 /// // `config` can now be used to create a `FirmwareUpdater` instance for managing boot operations.
52 /// ```
53 /// Working examples can be found in the bootloader examples folder.
23 pub fn from_linkerfile_blocking( 54 pub fn from_linkerfile_blocking(
24 flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>, 55 dfu_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<DFU>>,
56 state_flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<STATE>>,
25 ) -> Self { 57 ) -> Self {
26 extern "C" { 58 extern "C" {
27 static __bootloader_state_start: u32; 59 static __bootloader_state_start: u32;
@@ -35,14 +67,14 @@ impl<'a, FLASH: NorFlash>
35 let end = &__bootloader_dfu_end as *const u32 as u32; 67 let end = &__bootloader_dfu_end as *const u32 as u32;
36 trace!("DFU: 0x{:x} - 0x{:x}", start, end); 68 trace!("DFU: 0x{:x} - 0x{:x}", start, end);
37 69
38 BlockingPartition::new(flash, start, end - start) 70 BlockingPartition::new(dfu_flash, start, end - start)
39 }; 71 };
40 let state = unsafe { 72 let state = unsafe {
41 let start = &__bootloader_state_start as *const u32 as u32; 73 let start = &__bootloader_state_start as *const u32 as u32;
42 let end = &__bootloader_state_end as *const u32 as u32; 74 let end = &__bootloader_state_end as *const u32 as u32;
43 trace!("STATE: 0x{:x} - 0x{:x}", start, end); 75 trace!("STATE: 0x{:x} - 0x{:x}", start, end);
44 76
45 BlockingPartition::new(flash, start, end - start) 77 BlockingPartition::new(state_flash, start, end - start)
46 }; 78 };
47 79
48 Self { dfu, state } 80 Self { dfu, state }
@@ -60,6 +92,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
60 Self { 92 Self {
61 dfu: config.dfu, 93 dfu: config.dfu,
62 state: BlockingFirmwareState::new(config.state, aligned), 94 state: BlockingFirmwareState::new(config.state, aligned),
95 last_erased_dfu_sector_index: None,
63 } 96 }
64 } 97 }
65 98
@@ -76,7 +109,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
76 /// proceed with updating the firmware as it must be signed with a 109 /// proceed with updating the firmware as it must be signed with a
77 /// corresponding private key (otherwise it could be malicious firmware). 110 /// corresponding private key (otherwise it could be malicious firmware).
78 /// 111 ///
79 /// Mark to trigger firmware swap on next boot if verify suceeds. 112 /// Mark to trigger firmware swap on next boot if verify succeeds.
80 /// 113 ///
81 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have 114 /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have
82 /// been generated from a SHA-512 digest of the firmware bytes. 115 /// been generated from a SHA-512 digest of the firmware bytes.
@@ -86,8 +119,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
86 #[cfg(feature = "_verify")] 119 #[cfg(feature = "_verify")]
87 pub fn verify_and_mark_updated( 120 pub fn verify_and_mark_updated(
88 &mut self, 121 &mut self,
89 _public_key: &[u8], 122 _public_key: &[u8; 32],
90 _signature: &[u8], 123 _signature: &[u8; 64],
91 _update_len: u32, 124 _update_len: u32,
92 ) -> Result<(), FirmwareUpdaterError> { 125 ) -> Result<(), FirmwareUpdaterError> {
93 assert!(_update_len <= self.dfu.capacity() as u32); 126 assert!(_update_len <= self.dfu.capacity() as u32);
@@ -96,14 +129,14 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
96 129
97 #[cfg(feature = "ed25519-dalek")] 130 #[cfg(feature = "ed25519-dalek")]
98 { 131 {
99 use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; 132 use ed25519_dalek::{Signature, SignatureError, Verifier, VerifyingKey};
100 133
101 use crate::digest_adapters::ed25519_dalek::Sha512; 134 use crate::digest_adapters::ed25519_dalek::Sha512;
102 135
103 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); 136 let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into());
104 137
105 let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; 138 let public_key = VerifyingKey::from_bytes(_public_key).map_err(into_signature_error)?;
106 let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; 139 let signature = Signature::from_bytes(_signature);
107 140
108 let mut message = [0; 64]; 141 let mut message = [0; 64];
109 let mut chunk_buf = [0; 2]; 142 let mut chunk_buf = [0; 2];
@@ -113,7 +146,6 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
113 } 146 }
114 #[cfg(feature = "ed25519-salty")] 147 #[cfg(feature = "ed25519-salty")]
115 { 148 {
116 use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH};
117 use salty::{PublicKey, Signature}; 149 use salty::{PublicKey, Signature};
118 150
119 use crate::digest_adapters::salty::Sha512; 151 use crate::digest_adapters::salty::Sha512;
@@ -122,10 +154,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
122 FirmwareUpdaterError::Signature(signature::Error::default()) 154 FirmwareUpdaterError::Signature(signature::Error::default())
123 } 155 }
124 156
125 let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; 157 let public_key = PublicKey::try_from(_public_key).map_err(into_signature_error)?;
126 let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; 158 let signature = Signature::try_from(_signature).map_err(into_signature_error)?;
127 let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?;
128 let signature = Signature::try_from(&signature).map_err(into_signature_error)?;
129 159
130 let mut message = [0; 64]; 160 let mut message = [0; 64];
131 let mut chunk_buf = [0; 2]; 161 let mut chunk_buf = [0; 2];
@@ -168,25 +198,79 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
168 self.state.mark_updated() 198 self.state.mark_updated()
169 } 199 }
170 200
201 /// Mark to trigger USB DFU device on next boot.
202 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
203 self.state.verify_booted()?;
204 self.state.mark_dfu()
205 }
206
171 /// Mark firmware boot successful and stop rollback on reset. 207 /// Mark firmware boot successful and stop rollback on reset.
172 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 208 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
173 self.state.mark_booted() 209 self.state.mark_booted()
174 } 210 }
175 211
176 /// Write data to a flash page. 212 /// Writes firmware data to the device.
177 /// 213 ///
178 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. 214 /// This function writes the given data to the firmware area starting at the specified offset.
215 /// It handles sector erasures and data writes while verifying the device is in a proper state
216 /// for firmware updates. The function ensures that only unerased sectors are erased before
217 /// writing and efficiently handles the writing process across sector boundaries and in
218 /// various configurations (data size, sector size, etc.).
179 /// 219 ///
180 /// # Safety 220 /// # Arguments
181 /// 221 ///
182 /// Failing to meet alignment and size requirements may result in a panic. 222 /// * `offset` - The starting offset within the firmware area where data writing should begin.
223 /// * `data` - A slice of bytes representing the firmware data to be written. It must be a
224 /// multiple of NorFlash WRITE_SIZE.
225 ///
226 /// # Returns
227 ///
228 /// A `Result<(), FirmwareUpdaterError>` indicating the success or failure of the write operation.
229 ///
230 /// # Errors
231 ///
232 /// This function will return an error if:
233 ///
234 /// - The device is not in a proper state to receive firmware updates (e.g., not booted).
235 /// - There is a failure erasing a sector before writing.
236 /// - There is a failure writing data to the device.
183 pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { 237 pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> {
184 assert!(data.len() >= DFU::ERASE_SIZE); 238 // Make sure we are running a booted firmware to avoid reverting to a bad state.
185 self.state.verify_booted()?; 239 self.state.verify_booted()?;
186 240
187 self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; 241 // Initialize variables to keep track of the remaining data and the current offset.
242 let mut remaining_data = data;
243 let mut offset = offset;
244
245 // Continue writing as long as there is data left to write.
246 while !remaining_data.is_empty() {
247 // Compute the current sector and its boundaries.
248 let current_sector = offset / DFU::ERASE_SIZE;
249 let sector_start = current_sector * DFU::ERASE_SIZE;
250 let sector_end = sector_start + DFU::ERASE_SIZE;
251 // Determine if the current sector needs to be erased before writing.
252 let need_erase = self
253 .last_erased_dfu_sector_index
254 .map_or(true, |last_erased_sector| current_sector != last_erased_sector);
255
256 // If the sector needs to be erased, erase it and update the last erased sector index.
257 if need_erase {
258 self.dfu.erase(sector_start as u32, sector_end as u32)?;
259 self.last_erased_dfu_sector_index = Some(current_sector);
260 }
188 261
189 self.dfu.write(offset as u32, data)?; 262 // Calculate the size of the data chunk that can be written in the current iteration.
263 let write_size = core::cmp::min(remaining_data.len(), sector_end - offset);
264 // Split the data to get the current chunk to be written and the remaining data.
265 let (data_chunk, rest) = remaining_data.split_at(write_size);
266
267 // Write the current data chunk.
268 self.dfu.write(offset as u32, data_chunk)?;
269
270 // Update the offset and remaining data for the next iteration.
271 remaining_data = rest;
272 offset += write_size;
273 }
190 274
191 Ok(()) 275 Ok(())
192 } 276 }
@@ -213,6 +297,16 @@ pub struct BlockingFirmwareState<'d, STATE> {
213} 297}
214 298
215impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { 299impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
300 /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition.
301 ///
302 /// # Safety
303 ///
304 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
305 /// and written to.
306 pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
307 Self::new(config.state, aligned)
308 }
309
216 /// Create a firmware state instance with a buffer for magic content and state partition. 310 /// Create a firmware state instance with a buffer for magic content and state partition.
217 /// 311 ///
218 /// # Safety 312 /// # Safety
@@ -226,7 +320,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
226 320
227 // Make sure we are running a booted firmware to avoid reverting to a bad state. 321 // Make sure we are running a booted firmware to avoid reverting to a bad state.
228 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 322 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
229 if self.get_state()? == State::Boot { 323 if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
230 Ok(()) 324 Ok(())
231 } else { 325 } else {
232 Err(FirmwareUpdaterError::BadState) 326 Err(FirmwareUpdaterError::BadState)
@@ -243,6 +337,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
243 337
244 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { 338 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
245 Ok(State::Swap) 339 Ok(State::Swap)
340 } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
341 Ok(State::DfuDetach)
246 } else { 342 } else {
247 Ok(State::Boot) 343 Ok(State::Boot)
248 } 344 }
@@ -253,6 +349,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
253 self.set_magic(SWAP_MAGIC) 349 self.set_magic(SWAP_MAGIC)
254 } 350 }
255 351
352 /// Mark to trigger USB DFU on next boot.
353 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
354 self.set_magic(DFU_DETACH_MAGIC)
355 }
356
256 /// Mark firmware boot successful and stop rollback on reset. 357 /// Mark firmware boot successful and stop rollback on reset.
257 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 358 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
258 self.set_magic(BOOT_MAGIC) 359 self.set_magic(BOOT_MAGIC)
@@ -317,4 +418,82 @@ mod tests {
317 418
318 assert_eq!(Sha1::digest(update).as_slice(), hash); 419 assert_eq!(Sha1::digest(update).as_slice(), hash);
319 } 420 }
421
422 #[test]
423 fn can_verify_sha1_sector_bigger_than_chunk() {
424 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default()));
425 let state = BlockingPartition::new(&flash, 0, 4096);
426 let dfu = BlockingPartition::new(&flash, 65536, 65536);
427 let mut aligned = [0; 8];
428
429 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
430 let mut to_write = [0; 4096];
431 to_write[..7].copy_from_slice(update.as_slice());
432
433 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
434 let mut offset = 0;
435 for chunk in to_write.chunks(1024) {
436 updater.write_firmware(offset, chunk).unwrap();
437 offset += chunk.len();
438 }
439 let mut chunk_buf = [0; 2];
440 let mut hash = [0; 20];
441 updater
442 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
443 .unwrap();
444
445 assert_eq!(Sha1::digest(update).as_slice(), hash);
446 }
447
448 #[test]
449 fn can_verify_sha1_sector_smaller_than_chunk() {
450 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
451 let state = BlockingPartition::new(&flash, 0, 4096);
452 let dfu = BlockingPartition::new(&flash, 65536, 65536);
453 let mut aligned = [0; 8];
454
455 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
456 let mut to_write = [0; 4096];
457 to_write[..7].copy_from_slice(update.as_slice());
458
459 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
460 let mut offset = 0;
461 for chunk in to_write.chunks(2048) {
462 updater.write_firmware(offset, chunk).unwrap();
463 offset += chunk.len();
464 }
465 let mut chunk_buf = [0; 2];
466 let mut hash = [0; 20];
467 updater
468 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
469 .unwrap();
470
471 assert_eq!(Sha1::digest(update).as_slice(), hash);
472 }
473
474 #[test]
475 fn can_verify_sha1_cross_sector_boundary() {
476 let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 1024, 8>::default()));
477 let state = BlockingPartition::new(&flash, 0, 4096);
478 let dfu = BlockingPartition::new(&flash, 65536, 65536);
479 let mut aligned = [0; 8];
480
481 let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66];
482 let mut to_write = [0; 4096];
483 to_write[..7].copy_from_slice(update.as_slice());
484
485 let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }, &mut aligned);
486 let mut offset = 0;
487 for chunk in to_write.chunks(896) {
488 updater.write_firmware(offset, chunk).unwrap();
489 offset += chunk.len();
490 }
491 let mut chunk_buf = [0; 2];
492 let mut hash = [0; 20];
493 updater
494 .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)
495 .unwrap();
496
497 assert_eq!(Sha1::digest(update).as_slice(), hash);
498 }
320} 499}
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/src/firmware_updater/mod.rs
index 4814786bf..4c4f4f10b 100644
--- a/embassy-boot/boot/src/firmware_updater/mod.rs
+++ b/embassy-boot/src/firmware_updater/mod.rs
@@ -8,7 +8,7 @@ use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind};
8/// Firmware updater flash configuration holding the two flashes used by the updater 8/// Firmware updater flash configuration holding the two flashes used by the updater
9/// 9///
10/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. 10/// If only a single flash is actually used, then that flash should be partitioned into two partitions before use.
11/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition 11/// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile_blocking`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition
12/// the provided flash according to symbols defined in the linkerfile. 12/// the provided flash according to symbols defined in the linkerfile.
13pub struct FirmwareUpdaterConfig<DFU, STATE> { 13pub struct FirmwareUpdaterConfig<DFU, STATE> {
14 /// The dfu flash partition 14 /// The dfu flash partition
diff --git a/embassy-boot/boot/src/fmt.rs b/embassy-boot/src/fmt.rs
index 78e583c1c..2ac42c557 100644
--- a/embassy-boot/boot/src/fmt.rs
+++ b/embassy-boot/src/fmt.rs
@@ -1,5 +1,5 @@
1#![macro_use] 1#![macro_use]
2#![allow(unused_macros)] 2#![allow(unused)]
3 3
4use core::fmt::{Debug, Display, LowerHex}; 4use core::fmt::{Debug, Display, LowerHex};
5 5
@@ -229,7 +229,6 @@ impl<T, E> Try for Result<T, E> {
229 } 229 }
230} 230}
231 231
232#[allow(unused)]
233pub(crate) struct Bytes<'a>(pub &'a [u8]); 232pub(crate) struct Bytes<'a>(pub &'a [u8]);
234 233
235impl<'a> Debug for Bytes<'a> { 234impl<'a> Debug for Bytes<'a> {
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/src/lib.rs
index 9e70a4dca..b4f03e01e 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/src/lib.rs
@@ -23,6 +23,7 @@ pub use firmware_updater::{
23 23
24pub(crate) const BOOT_MAGIC: u8 = 0xD0; 24pub(crate) const BOOT_MAGIC: u8 = 0xD0;
25pub(crate) const SWAP_MAGIC: u8 = 0xF0; 25pub(crate) const SWAP_MAGIC: u8 = 0xF0;
26pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0;
26 27
27/// The state of the bootloader after running prepare. 28/// The state of the bootloader after running prepare.
28#[derive(PartialEq, Eq, Debug)] 29#[derive(PartialEq, Eq, Debug)]
@@ -32,6 +33,8 @@ pub enum State {
32 Boot, 33 Boot,
33 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. 34 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
34 Swap, 35 Swap,
36 /// Application has received a request to reboot into DFU mode to apply an update.
37 DfuDetach,
35} 38}
36 39
37/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. 40/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
@@ -272,21 +275,19 @@ mod tests {
272 // The following key setup is based on: 275 // The following key setup is based on:
273 // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example 276 // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example
274 277
275 use ed25519_dalek::Keypair; 278 use ed25519_dalek::{Digest, Sha512, Signature, Signer, SigningKey, VerifyingKey};
276 use rand::rngs::OsRng; 279 use rand::rngs::OsRng;
277 280
278 let mut csprng = OsRng {}; 281 let mut csprng = OsRng {};
279 let keypair: Keypair = Keypair::generate(&mut csprng); 282 let keypair = SigningKey::generate(&mut csprng);
280 283
281 use ed25519_dalek::{Digest, Sha512, Signature, Signer};
282 let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; 284 let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU.";
283 let mut digest = Sha512::new(); 285 let mut digest = Sha512::new();
284 digest.update(&firmware); 286 digest.update(&firmware);
285 let message = digest.finalize(); 287 let message = digest.finalize();
286 let signature: Signature = keypair.sign(&message); 288 let signature: Signature = keypair.sign(&message);
287 289
288 use ed25519_dalek::PublicKey; 290 let public_key = keypair.verifying_key();
289 let public_key: PublicKey = keypair.public;
290 291
291 // Setup flash 292 // Setup flash
292 let flash = BlockingTestFlash::new(BootLoaderConfig { 293 let flash = BlockingTestFlash::new(BootLoaderConfig {
diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/src/mem_flash.rs
index 40f352c8d..40f352c8d 100644
--- a/embassy-boot/boot/src/mem_flash.rs
+++ b/embassy-boot/src/mem_flash.rs
diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/src/test_flash/asynch.rs
index 3ac9e71ab..3ac9e71ab 100644
--- a/embassy-boot/boot/src/test_flash/asynch.rs
+++ b/embassy-boot/src/test_flash/asynch.rs
diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/src/test_flash/blocking.rs
index 5ec476c65..5ec476c65 100644
--- a/embassy-boot/boot/src/test_flash/blocking.rs
+++ b/embassy-boot/src/test_flash/blocking.rs
diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/src/test_flash/mod.rs
index 79b15a081..79b15a081 100644
--- a/embassy-boot/boot/src/test_flash/mod.rs
+++ b/embassy-boot/src/test_flash/mod.rs
diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml
deleted file mode 100644
index bc8da6738..000000000
--- a/embassy-boot/stm32/Cargo.toml
+++ /dev/null
@@ -1,64 +0,0 @@
1[package]
2edition = "2021"
3name = "embassy-boot-stm32"
4version = "0.1.0"
5description = "Bootloader lib for STM32 chips"
6license = "MIT OR Apache-2.0"
7
8[package.metadata.embassy_docs]
9src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/"
10src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/stm32/src/"
11features = ["embassy-stm32/stm32f429zi"]
12target = "thumbv7em-none-eabi"
13
14[lib]
15
16[dependencies]
17defmt = { version = "0.3", optional = true }
18defmt-rtt = { version = "0.4", optional = true }
19log = { version = "0.4", optional = true }
20
21embassy-sync = { version = "0.5.0", path = "../../embassy-sync" }
22embassy-stm32 = { path = "../../embassy-stm32", default-features = false }
23embassy-boot = { path = "../boot", default-features = false }
24cortex-m = { version = "0.7.6" }
25cortex-m-rt = { version = "0.7" }
26embedded-storage = "0.3.1"
27embedded-storage-async = { version = "0.4.1" }
28cfg-if = "1.0.0"
29
30[features]
31defmt = ["dep:defmt", "embassy-boot/defmt", "embassy-stm32/defmt"]
32log = ["dep:log", "embassy-boot/log", "embassy-stm32/log"]
33debug = ["defmt-rtt"]
34
35[profile.dev]
36debug = 2
37debug-assertions = true
38incremental = false
39opt-level = 'z'
40overflow-checks = true
41
42[profile.release]
43codegen-units = 1
44debug = 2
45debug-assertions = false
46incremental = false
47lto = 'fat'
48opt-level = 'z'
49overflow-checks = false
50
51# do not optimize proc-macro crates = faster builds from scratch
52[profile.dev.build-override]
53codegen-units = 8
54debug = false
55debug-assertions = false
56opt-level = 0
57overflow-checks = false
58
59[profile.release.build-override]
60codegen-units = 8
61debug = false
62debug-assertions = false
63opt-level = 0
64overflow-checks = false
diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md
deleted file mode 100644
index b4d7ba5a4..000000000
--- a/embassy-boot/stm32/README.md
+++ /dev/null
@@ -1,24 +0,0 @@
1# embassy-boot-stm32
2
3An [Embassy](https://embassy.dev) project.
4
5An adaptation of `embassy-boot` for STM32.
6
7## Features
8
9* Configure bootloader partitions based on linker script.
10* Load applications from active partition.
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/build.rs b/embassy-boot/stm32/build.rs
deleted file mode 100644
index 2cbc7ef5e..000000000
--- a/embassy-boot/stm32/build.rs
+++ /dev/null
@@ -1,8 +0,0 @@
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/stm32/src/fmt.rs b/embassy-boot/stm32/src/fmt.rs
deleted file mode 100644
index 78e583c1c..000000000
--- a/embassy-boot/stm32/src/fmt.rs
+++ /dev/null
@@ -1,258 +0,0 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*)
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232#[allow(unused)]
233pub(crate) struct Bytes<'a>(pub &'a [u8]);
234
235impl<'a> Debug for Bytes<'a> {
236 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 write!(f, "{:#02x?}", self.0)
238 }
239}
240
241impl<'a> Display for Bytes<'a> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 write!(f, "{:#02x?}", self.0)
244 }
245}
246
247impl<'a> LowerHex for Bytes<'a> {
248 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249 write!(f, "{:#02x?}", self.0)
250 }
251}
252
253#[cfg(feature = "defmt")]
254impl<'a> defmt::Format for Bytes<'a> {
255 fn format(&self, fmt: defmt::Formatter) {
256 defmt::write!(fmt, "{:02x}", self.0)
257 }
258}
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
deleted file mode 100644
index c418cb262..000000000
--- a/embassy-boot/stm32/src/lib.rs
+++ /dev/null
@@ -1,41 +0,0 @@
1#![no_std]
2#![warn(missing_docs)]
3#![doc = include_str!("../README.md")]
4mod fmt;
5
6pub use embassy_boot::{
7 AlignedBuffer, BlockingFirmwareState, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareState, FirmwareUpdater,
8 FirmwareUpdaterConfig, State,
9};
10use embedded_storage::nor_flash::NorFlash;
11
12/// A bootloader for STM32 devices.
13pub struct BootLoader;
14
15impl BootLoader {
16 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
17 pub fn prepare<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize>(
18 config: BootLoaderConfig<ACTIVE, DFU, STATE>,
19 ) -> Self {
20 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
21 let mut boot = embassy_boot::BootLoader::new(config);
22 boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
23 Self
24 }
25
26 /// Boots the application.
27 ///
28 /// # Safety
29 ///
30 /// This modifies the stack pointer and reset vector and will run code placed in the active partition.
31 pub unsafe fn load(self, start: u32) -> ! {
32 trace!("Loading app at 0x{:x}", start);
33 #[allow(unused_mut)]
34 let mut p = cortex_m::Peripherals::steal();
35 #[cfg(not(armv6m))]
36 p.SCB.invalidate_icache();
37 p.SCB.vtor.write(start);
38
39 cortex_m::asm::bootload(start as *const u32)
40 }
41}