aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorUlf Lilleengen <[email protected]>2023-12-14 19:56:04 +0000
committerGitHub <[email protected]>2023-12-14 19:56:04 +0000
commit5ec2fbe3a2ce3234ed6477fe5923c3d2425b55a7 (patch)
tree94b50831f7e5d606643eba7e0c9c1e5c7bf1ec86
parent485765320aaef82adbd4865b25e7171fb8f4041a (diff)
parent33e8943e5b6e637b82f13c77bd88bb56d55ab515 (diff)
Merge pull request #2284 from Redrield/feature/embassy-usb-dfu
Add embassy-usb-dfu crate, with related modifications to embassy-boot
-rwxr-xr-xci.sh2
-rw-r--r--embassy-boot/boot/src/boot_loader.rs4
-rw-r--r--embassy-boot/boot/src/firmware_updater/asynch.rs23
-rw-r--r--embassy-boot/boot/src/firmware_updater/blocking.rs27
-rw-r--r--embassy-boot/boot/src/lib.rs3
-rw-r--r--embassy-boot/stm32/src/lib.rs9
-rw-r--r--embassy-usb-dfu/Cargo.toml32
-rw-r--r--embassy-usb-dfu/src/application.rs135
-rw-r--r--embassy-usb-dfu/src/bootloader.rs189
-rw-r--r--embassy-usb-dfu/src/consts.rs95
-rw-r--r--embassy-usb-dfu/src/fmt.rs258
-rw-r--r--embassy-usb-dfu/src/lib.rs51
-rw-r--r--examples/boot/application/stm32wb-dfu/.cargo/config.toml9
-rw-r--r--examples/boot/application/stm32wb-dfu/Cargo.toml32
-rw-r--r--examples/boot/application/stm32wb-dfu/README.md29
-rw-r--r--examples/boot/application/stm32wb-dfu/build.rs37
-rw-r--r--examples/boot/application/stm32wb-dfu/memory.x15
-rw-r--r--examples/boot/application/stm32wb-dfu/src/main.rs64
-rw-r--r--examples/boot/bootloader/stm32wb-dfu/Cargo.toml63
-rw-r--r--examples/boot/bootloader/stm32wb-dfu/README.md11
-rw-r--r--examples/boot/bootloader/stm32wb-dfu/build.rs27
-rw-r--r--examples/boot/bootloader/stm32wb-dfu/memory.x18
-rw-r--r--examples/boot/bootloader/stm32wb-dfu/src/main.rs93
23 files changed, 1219 insertions, 7 deletions
diff --git a/ci.sh b/ci.sh
index 8a5e206d2..83c05d1b9 100755
--- a/ci.sh
+++ b/ci.sh
@@ -173,10 +173,12 @@ cargo batch \
173 --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ 173 --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \
174 --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ 174 --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \
175 --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \ 175 --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \
176 --- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wb-dfu \
176 --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ 177 --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \
177 --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ 178 --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \
178 --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ 179 --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \
179 --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ 180 --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \
181 --- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf \
180 --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ 182 --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \
181 --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ 183 --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \
182 --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ 184 --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
index 65b12dc5f..e568001bc 100644
--- a/embassy-boot/boot/src/boot_loader.rs
+++ b/embassy-boot/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)]
@@ -371,6 +371,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
371 371
372 if !state_word.iter().any(|&b| b != SWAP_MAGIC) { 372 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
373 Ok(State::Swap) 373 Ok(State::Swap)
374 } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
375 Ok(State::DfuDetach)
374 } else { 376 } else {
375 Ok(State::Boot) 377 Ok(State::Boot)
376 } 378 }
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs
index ae713bb6f..d8d85c3d2 100644
--- a/embassy-boot/boot/src/firmware_updater/asynch.rs
+++ b/embassy-boot/boot/src/firmware_updater/asynch.rs
@@ -6,7 +6,7 @@ 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
@@ -161,6 +161,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> {
161 self.state.mark_updated().await 161 self.state.mark_updated().await
162 } 162 }
163 163
164 /// Mark to trigger USB DFU on next boot.
165 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
166 self.state.verify_booted().await?;
167 self.state.mark_dfu().await
168 }
169
164 /// Mark firmware boot successful and stop rollback on reset. 170 /// Mark firmware boot successful and stop rollback on reset.
165 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 171 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
166 self.state.mark_booted().await 172 self.state.mark_booted().await
@@ -207,6 +213,16 @@ pub struct FirmwareState<'d, STATE> {
207} 213}
208 214
209impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { 215impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
216 /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition.
217 ///
218 /// # Safety
219 ///
220 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
221 /// and written to.
222 pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
223 Self::new(config.state, aligned)
224 }
225
210 /// Create a firmware state instance with a buffer for magic content and state partition. 226 /// Create a firmware state instance with a buffer for magic content and state partition.
211 /// 227 ///
212 /// # Safety 228 /// # Safety
@@ -247,6 +263,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
247 self.set_magic(SWAP_MAGIC).await 263 self.set_magic(SWAP_MAGIC).await
248 } 264 }
249 265
266 /// Mark to trigger USB DFU on next boot.
267 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
268 self.set_magic(DFU_DETACH_MAGIC).await
269 }
270
250 /// Mark firmware boot successful and stop rollback on reset. 271 /// Mark firmware boot successful and stop rollback on reset.
251 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 272 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
252 self.set_magic(BOOT_MAGIC).await 273 self.set_magic(BOOT_MAGIC).await
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs
index 76e4264a0..c4c142169 100644
--- a/embassy-boot/boot/src/firmware_updater/blocking.rs
+++ b/embassy-boot/boot/src/firmware_updater/blocking.rs
@@ -6,7 +6,7 @@ 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
@@ -168,6 +168,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE>
168 self.state.mark_updated() 168 self.state.mark_updated()
169 } 169 }
170 170
171 /// Mark to trigger USB DFU device on next boot.
172 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
173 self.state.verify_booted()?;
174 self.state.mark_dfu()
175 }
176
171 /// Mark firmware boot successful and stop rollback on reset. 177 /// Mark firmware boot successful and stop rollback on reset.
172 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 178 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
173 self.state.mark_booted() 179 self.state.mark_booted()
@@ -213,6 +219,16 @@ pub struct BlockingFirmwareState<'d, STATE> {
213} 219}
214 220
215impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { 221impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
222 /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition.
223 ///
224 /// # Safety
225 ///
226 /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from
227 /// and written to.
228 pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self {
229 Self::new(config.state, aligned)
230 }
231
216 /// Create a firmware state instance with a buffer for magic content and state partition. 232 /// Create a firmware state instance with a buffer for magic content and state partition.
217 /// 233 ///
218 /// # Safety 234 /// # Safety
@@ -226,7 +242,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
226 242
227 // Make sure we are running a booted firmware to avoid reverting to a bad state. 243 // Make sure we are running a booted firmware to avoid reverting to a bad state.
228 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 244 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
229 if self.get_state()? == State::Boot { 245 if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
230 Ok(()) 246 Ok(())
231 } else { 247 } else {
232 Err(FirmwareUpdaterError::BadState) 248 Err(FirmwareUpdaterError::BadState)
@@ -243,6 +259,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
243 259
244 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { 260 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
245 Ok(State::Swap) 261 Ok(State::Swap)
262 } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
263 Ok(State::DfuDetach)
246 } else { 264 } else {
247 Ok(State::Boot) 265 Ok(State::Boot)
248 } 266 }
@@ -253,6 +271,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
253 self.set_magic(SWAP_MAGIC) 271 self.set_magic(SWAP_MAGIC)
254 } 272 }
255 273
274 /// Mark to trigger USB DFU on next boot.
275 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
276 self.set_magic(DFU_DETACH_MAGIC)
277 }
278
256 /// Mark firmware boot successful and stop rollback on reset. 279 /// Mark firmware boot successful and stop rollback on reset.
257 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 280 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
258 self.set_magic(BOOT_MAGIC) 281 self.set_magic(BOOT_MAGIC)
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 9e70a4dca..15b69f69d 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/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.
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs
index c418cb262..4b4091ac9 100644
--- a/embassy-boot/stm32/src/lib.rs
+++ b/embassy-boot/stm32/src/lib.rs
@@ -10,7 +10,10 @@ pub use embassy_boot::{
10use embedded_storage::nor_flash::NorFlash; 10use embedded_storage::nor_flash::NorFlash;
11 11
12/// A bootloader for STM32 devices. 12/// A bootloader for STM32 devices.
13pub struct BootLoader; 13pub struct BootLoader {
14 /// The reported state of the bootloader after preparing for boot
15 pub state: State,
16}
14 17
15impl BootLoader { 18impl BootLoader {
16 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware 19 /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware
@@ -19,8 +22,8 @@ impl BootLoader {
19 ) -> Self { 22 ) -> Self {
20 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); 23 let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]);
21 let mut boot = embassy_boot::BootLoader::new(config); 24 let mut boot = embassy_boot::BootLoader::new(config);
22 boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); 25 let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error");
23 Self 26 Self { state }
24 } 27 }
25 28
26 /// Boots the application. 29 /// Boots the application.
diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml
new file mode 100644
index 000000000..ee110ee87
--- /dev/null
+++ b/embassy-usb-dfu/Cargo.toml
@@ -0,0 +1,32 @@
1[package]
2edition = "2021"
3name = "embassy-usb-dfu"
4version = "0.1.0"
5description = "An implementation of the USB DFU 1.1 protocol, using embassy-boot"
6license = "MIT OR Apache-2.0"
7repository = "https://github.com/embassy-rs/embassy"
8categories = [
9 "embedded",
10 "no-std",
11 "asynchronous"
12]
13
14# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
15
16[dependencies]
17bitflags = "2.4.1"
18cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true }
19defmt = { version = "0.3.5", optional = true }
20embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" }
21# embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" }
22embassy-futures = { version = "0.1.1", path = "../embassy-futures" }
23embassy-sync = { version = "0.5.0", path = "../embassy-sync" }
24embassy-time = { version = "0.2.0", path = "../embassy-time" }
25embassy-usb = { version = "0.1.0", path = "../embassy-usb", default-features = false }
26embedded-storage = { version = "0.3.1" }
27esp32c3-hal = { version = "0.13.0", optional = true, default-features = false }
28
29[features]
30dfu = []
31application = []
32defmt = ["dep:defmt"]
diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs
new file mode 100644
index 000000000..75689db26
--- /dev/null
+++ b/embassy-usb-dfu/src/application.rs
@@ -0,0 +1,135 @@
1use core::marker::PhantomData;
2
3use embassy_boot::BlockingFirmwareState;
4use embassy_time::{Duration, Instant};
5use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
6use embassy_usb::driver::Driver;
7use embassy_usb::{Builder, Handler};
8use embedded_storage::nor_flash::NorFlash;
9
10use crate::consts::{
11 DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT,
12 USB_CLASS_APPN_SPEC,
13};
14use crate::Reset;
15
16/// Internal state for the DFU class
17pub struct Control<'d, STATE: NorFlash, RST: Reset> {
18 firmware_state: BlockingFirmwareState<'d, STATE>,
19 attrs: DfuAttributes,
20 state: State,
21 timeout: Option<Duration>,
22 detach_start: Option<Instant>,
23 _rst: PhantomData<RST>,
24}
25
26impl<'d, STATE: NorFlash, RST: Reset> Control<'d, STATE, RST> {
27 pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self {
28 Control {
29 firmware_state,
30 attrs,
31 state: State::AppIdle,
32 detach_start: None,
33 timeout: None,
34 _rst: PhantomData,
35 }
36 }
37}
38
39impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> {
40 fn reset(&mut self) {
41 if let Some(start) = self.detach_start {
42 let delta = Instant::now() - start;
43 let timeout = self.timeout.unwrap();
44 trace!(
45 "Received RESET with delta = {}, timeout = {}",
46 delta.as_millis(),
47 timeout.as_millis()
48 );
49 if delta < timeout {
50 self.firmware_state
51 .mark_dfu()
52 .expect("Failed to mark DFU mode in bootloader");
53 RST::sys_reset()
54 }
55 }
56 }
57
58 fn control_out(
59 &mut self,
60 req: embassy_usb::control::Request,
61 _: &[u8],
62 ) -> Option<embassy_usb::control::OutResponse> {
63 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
64 return None;
65 }
66
67 trace!("Received request {}", req);
68
69 match Request::try_from(req.request) {
70 Ok(Request::Detach) => {
71 trace!("Received DETACH, awaiting USB reset");
72 self.detach_start = Some(Instant::now());
73 self.timeout = Some(Duration::from_millis(req.value as u64));
74 self.state = State::AppDetach;
75 Some(OutResponse::Accepted)
76 }
77 _ => None,
78 }
79 }
80
81 fn control_in<'a>(
82 &'a mut self,
83 req: embassy_usb::control::Request,
84 buf: &'a mut [u8],
85 ) -> Option<embassy_usb::control::InResponse<'a>> {
86 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
87 return None;
88 }
89
90 trace!("Received request {}", req);
91
92 match Request::try_from(req.request) {
93 Ok(Request::GetStatus) => {
94 buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
95 Some(InResponse::Accepted(buf))
96 }
97 _ => None,
98 }
99 }
100}
101
102/// An implementation of the USB DFU 1.1 runtime protocol
103///
104/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device. The USB builder can be used as normal once this is complete.
105/// The handler is responsive to DFU GetStatus and Detach commands.
106///
107/// Once a detach command, followed by a USB reset is received by the host, a magic number will be written into the bootloader state partition to indicate that
108/// it should expose a DFU device, and a software reset will be issued.
109///
110/// To apply USB DFU updates, the bootloader must be capable of recognizing the DFU magic and exposing a device to handle the full DFU transaction with the host.
111pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>(
112 builder: &mut Builder<'d, D>,
113 handler: &'d mut Control<'d, STATE, RST>,
114 timeout: Duration,
115) {
116 let mut func = builder.function(0x00, 0x00, 0x00);
117 let mut iface = func.interface();
118 let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None);
119 let timeout = timeout.as_millis() as u16;
120 alt.descriptor(
121 DESC_DFU_FUNCTIONAL,
122 &[
123 handler.attrs.bits(),
124 (timeout & 0xff) as u8,
125 ((timeout >> 8) & 0xff) as u8,
126 0x40,
127 0x00, // 64B control buffer size for application side
128 0x10,
129 0x01, // DFU 1.1
130 ],
131 );
132
133 drop(func);
134 builder.handler(handler);
135}
diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs
new file mode 100644
index 000000000..d41e6280d
--- /dev/null
+++ b/embassy-usb-dfu/src/bootloader.rs
@@ -0,0 +1,189 @@
1use core::marker::PhantomData;
2
3use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater};
4use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
5use embassy_usb::driver::Driver;
6use embassy_usb::{Builder, Handler};
7use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind};
8
9use crate::consts::{
10 DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU,
11 USB_CLASS_APPN_SPEC,
12};
13use crate::Reset;
14
15/// Internal state for USB DFU
16pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> {
17 updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
18 attrs: DfuAttributes,
19 state: State,
20 status: Status,
21 offset: usize,
22 _rst: PhantomData<RST>,
23}
24
25impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> {
26 pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self {
27 Self {
28 updater,
29 attrs,
30 state: State::DfuIdle,
31 status: Status::Ok,
32 offset: 0,
33 _rst: PhantomData,
34 }
35 }
36
37 fn reset_state(&mut self) {
38 self.offset = 0;
39 self.state = State::DfuIdle;
40 self.status = Status::Ok;
41 }
42}
43
44impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler
45 for Control<'d, DFU, STATE, RST, BLOCK_SIZE>
46{
47 fn control_out(
48 &mut self,
49 req: embassy_usb::control::Request,
50 data: &[u8],
51 ) -> Option<embassy_usb::control::OutResponse> {
52 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
53 return None;
54 }
55 match Request::try_from(req.request) {
56 Ok(Request::Abort) => {
57 self.reset_state();
58 Some(OutResponse::Accepted)
59 }
60 Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
61 if req.value == 0 {
62 self.state = State::Download;
63 self.offset = 0;
64 }
65
66 let mut buf = AlignedBuffer([0; BLOCK_SIZE]);
67 buf.as_mut()[..data.len()].copy_from_slice(data);
68
69 if req.length == 0 {
70 match self.updater.mark_updated() {
71 Ok(_) => {
72 self.status = Status::Ok;
73 self.state = State::ManifestSync;
74 }
75 Err(e) => {
76 self.state = State::Error;
77 match e {
78 embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
79 NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
80 NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress,
81 _ => self.status = Status::ErrUnknown,
82 },
83 embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify,
84 embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown,
85 }
86 }
87 }
88 } else {
89 if self.state != State::Download {
90 // Unexpected DNLOAD while chip is waiting for a GETSTATUS
91 self.status = Status::ErrUnknown;
92 self.state = State::Error;
93 return Some(OutResponse::Rejected);
94 }
95 match self.updater.write_firmware(self.offset, buf.as_ref()) {
96 Ok(_) => {
97 self.status = Status::Ok;
98 self.state = State::DlSync;
99 self.offset += data.len();
100 }
101 Err(e) => {
102 self.state = State::Error;
103 match e {
104 embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
105 NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
106 NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress,
107 _ => self.status = Status::ErrUnknown,
108 },
109 embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify,
110 embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown,
111 }
112 }
113 }
114 }
115
116 Some(OutResponse::Accepted)
117 }
118 Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode
119 Ok(Request::ClrStatus) => {
120 self.reset_state();
121 Some(OutResponse::Accepted)
122 }
123 _ => None,
124 }
125 }
126
127 fn control_in<'a>(
128 &'a mut self,
129 req: embassy_usb::control::Request,
130 buf: &'a mut [u8],
131 ) -> Option<embassy_usb::control::InResponse<'a>> {
132 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
133 return None;
134 }
135 match Request::try_from(req.request) {
136 Ok(Request::GetStatus) => {
137 //TODO: Configurable poll timeout, ability to add string for Vendor error
138 buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
139 match self.state {
140 State::DlSync => self.state = State::Download,
141 State::ManifestSync => RST::sys_reset(),
142 _ => {}
143 }
144
145 Some(InResponse::Accepted(&buf[0..6]))
146 }
147 Ok(Request::GetState) => {
148 buf[0] = self.state as u8;
149 Some(InResponse::Accepted(&buf[0..1]))
150 }
151 Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
152 //TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload.
153 Some(InResponse::Rejected)
154 }
155 _ => None,
156 }
157 }
158}
159
160/// An implementation of the USB DFU 1.1 protocol
161///
162/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device
163/// The handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user.
164///
165/// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition.
166/// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware.
167pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>(
168 builder: &mut Builder<'d, D>,
169 handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>,
170) {
171 let mut func = builder.function(0x00, 0x00, 0x00);
172 let mut iface = func.interface();
173 let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None);
174 alt.descriptor(
175 DESC_DFU_FUNCTIONAL,
176 &[
177 handler.attrs.bits(),
178 0xc4,
179 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code
180 (BLOCK_SIZE & 0xff) as u8,
181 ((BLOCK_SIZE & 0xff00) >> 8) as u8,
182 0x10,
183 0x01, // DFU 1.1
184 ],
185 );
186
187 drop(func);
188 builder.handler(handler);
189}
diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs
new file mode 100644
index 000000000..b359a107e
--- /dev/null
+++ b/embassy-usb-dfu/src/consts.rs
@@ -0,0 +1,95 @@
1pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE;
2pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01;
3#[allow(unused)]
4pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02;
5#[allow(unused)]
6pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01;
7pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21;
8
9#[cfg(feature = "defmt")]
10defmt::bitflags! {
11 pub struct DfuAttributes: u8 {
12 const WILL_DETACH = 0b0000_1000;
13 const MANIFESTATION_TOLERANT = 0b0000_0100;
14 const CAN_UPLOAD = 0b0000_0010;
15 const CAN_DOWNLOAD = 0b0000_0001;
16 }
17}
18
19#[cfg(not(feature = "defmt"))]
20bitflags::bitflags! {
21 pub struct DfuAttributes: u8 {
22 const WILL_DETACH = 0b0000_1000;
23 const MANIFESTATION_TOLERANT = 0b0000_0100;
24 const CAN_UPLOAD = 0b0000_0010;
25 const CAN_DOWNLOAD = 0b0000_0001;
26 }
27}
28
29#[derive(Copy, Clone, PartialEq, Eq)]
30#[repr(u8)]
31#[allow(unused)]
32pub enum State {
33 AppIdle = 0,
34 AppDetach = 1,
35 DfuIdle = 2,
36 DlSync = 3,
37 DlBusy = 4,
38 Download = 5,
39 ManifestSync = 6,
40 Manifest = 7,
41 ManifestWaitReset = 8,
42 UploadIdle = 9,
43 Error = 10,
44}
45
46#[derive(Copy, Clone, PartialEq, Eq)]
47#[repr(u8)]
48#[allow(unused)]
49pub enum Status {
50 Ok = 0x00,
51 ErrTarget = 0x01,
52 ErrFile = 0x02,
53 ErrWrite = 0x03,
54 ErrErase = 0x04,
55 ErrCheckErased = 0x05,
56 ErrProg = 0x06,
57 ErrVerify = 0x07,
58 ErrAddress = 0x08,
59 ErrNotDone = 0x09,
60 ErrFirmware = 0x0A,
61 ErrVendor = 0x0B,
62 ErrUsbr = 0x0C,
63 ErrPor = 0x0D,
64 ErrUnknown = 0x0E,
65 ErrStalledPkt = 0x0F,
66}
67
68#[derive(Copy, Clone, PartialEq, Eq)]
69#[repr(u8)]
70pub enum Request {
71 Detach = 0,
72 Dnload = 1,
73 Upload = 2,
74 GetStatus = 3,
75 ClrStatus = 4,
76 GetState = 5,
77 Abort = 6,
78}
79
80impl TryFrom<u8> for Request {
81 type Error = ();
82
83 fn try_from(value: u8) -> Result<Self, Self::Error> {
84 match value {
85 0 => Ok(Request::Detach),
86 1 => Ok(Request::Dnload),
87 2 => Ok(Request::Upload),
88 3 => Ok(Request::GetStatus),
89 4 => Ok(Request::ClrStatus),
90 5 => Ok(Request::GetState),
91 6 => Ok(Request::Abort),
92 _ => Err(()),
93 }
94 }
95}
diff --git a/embassy-usb-dfu/src/fmt.rs b/embassy-usb-dfu/src/fmt.rs
new file mode 100644
index 000000000..78e583c1c
--- /dev/null
+++ b/embassy-usb-dfu/src/fmt.rs
@@ -0,0 +1,258 @@
1#![macro_use]
2#![allow(unused_macros)]
3
4use core::fmt::{Debug, Display, LowerHex};
5
6#[cfg(all(feature = "defmt", feature = "log"))]
7compile_error!("You may not enable both `defmt` and `log` features.");
8
9macro_rules! assert {
10 ($($x:tt)*) => {
11 {
12 #[cfg(not(feature = "defmt"))]
13 ::core::assert!($($x)*);
14 #[cfg(feature = "defmt")]
15 ::defmt::assert!($($x)*);
16 }
17 };
18}
19
20macro_rules! assert_eq {
21 ($($x:tt)*) => {
22 {
23 #[cfg(not(feature = "defmt"))]
24 ::core::assert_eq!($($x)*);
25 #[cfg(feature = "defmt")]
26 ::defmt::assert_eq!($($x)*);
27 }
28 };
29}
30
31macro_rules! assert_ne {
32 ($($x:tt)*) => {
33 {
34 #[cfg(not(feature = "defmt"))]
35 ::core::assert_ne!($($x)*);
36 #[cfg(feature = "defmt")]
37 ::defmt::assert_ne!($($x)*);
38 }
39 };
40}
41
42macro_rules! debug_assert {
43 ($($x:tt)*) => {
44 {
45 #[cfg(not(feature = "defmt"))]
46 ::core::debug_assert!($($x)*);
47 #[cfg(feature = "defmt")]
48 ::defmt::debug_assert!($($x)*);
49 }
50 };
51}
52
53macro_rules! debug_assert_eq {
54 ($($x:tt)*) => {
55 {
56 #[cfg(not(feature = "defmt"))]
57 ::core::debug_assert_eq!($($x)*);
58 #[cfg(feature = "defmt")]
59 ::defmt::debug_assert_eq!($($x)*);
60 }
61 };
62}
63
64macro_rules! debug_assert_ne {
65 ($($x:tt)*) => {
66 {
67 #[cfg(not(feature = "defmt"))]
68 ::core::debug_assert_ne!($($x)*);
69 #[cfg(feature = "defmt")]
70 ::defmt::debug_assert_ne!($($x)*);
71 }
72 };
73}
74
75macro_rules! todo {
76 ($($x:tt)*) => {
77 {
78 #[cfg(not(feature = "defmt"))]
79 ::core::todo!($($x)*);
80 #[cfg(feature = "defmt")]
81 ::defmt::todo!($($x)*);
82 }
83 };
84}
85
86#[cfg(not(feature = "defmt"))]
87macro_rules! unreachable {
88 ($($x:tt)*) => {
89 ::core::unreachable!($($x)*)
90 };
91}
92
93#[cfg(feature = "defmt")]
94macro_rules! unreachable {
95 ($($x:tt)*) => {
96 ::defmt::unreachable!($($x)*)
97 };
98}
99
100macro_rules! panic {
101 ($($x:tt)*) => {
102 {
103 #[cfg(not(feature = "defmt"))]
104 ::core::panic!($($x)*);
105 #[cfg(feature = "defmt")]
106 ::defmt::panic!($($x)*);
107 }
108 };
109}
110
111macro_rules! trace {
112 ($s:literal $(, $x:expr)* $(,)?) => {
113 {
114 #[cfg(feature = "log")]
115 ::log::trace!($s $(, $x)*);
116 #[cfg(feature = "defmt")]
117 ::defmt::trace!($s $(, $x)*);
118 #[cfg(not(any(feature = "log", feature="defmt")))]
119 let _ = ($( & $x ),*);
120 }
121 };
122}
123
124macro_rules! debug {
125 ($s:literal $(, $x:expr)* $(,)?) => {
126 {
127 #[cfg(feature = "log")]
128 ::log::debug!($s $(, $x)*);
129 #[cfg(feature = "defmt")]
130 ::defmt::debug!($s $(, $x)*);
131 #[cfg(not(any(feature = "log", feature="defmt")))]
132 let _ = ($( & $x ),*);
133 }
134 };
135}
136
137macro_rules! info {
138 ($s:literal $(, $x:expr)* $(,)?) => {
139 {
140 #[cfg(feature = "log")]
141 ::log::info!($s $(, $x)*);
142 #[cfg(feature = "defmt")]
143 ::defmt::info!($s $(, $x)*);
144 #[cfg(not(any(feature = "log", feature="defmt")))]
145 let _ = ($( & $x ),*);
146 }
147 };
148}
149
150macro_rules! warn {
151 ($s:literal $(, $x:expr)* $(,)?) => {
152 {
153 #[cfg(feature = "log")]
154 ::log::warn!($s $(, $x)*);
155 #[cfg(feature = "defmt")]
156 ::defmt::warn!($s $(, $x)*);
157 #[cfg(not(any(feature = "log", feature="defmt")))]
158 let _ = ($( & $x ),*);
159 }
160 };
161}
162
163macro_rules! error {
164 ($s:literal $(, $x:expr)* $(,)?) => {
165 {
166 #[cfg(feature = "log")]
167 ::log::error!($s $(, $x)*);
168 #[cfg(feature = "defmt")]
169 ::defmt::error!($s $(, $x)*);
170 #[cfg(not(any(feature = "log", feature="defmt")))]
171 let _ = ($( & $x ),*);
172 }
173 };
174}
175
176#[cfg(feature = "defmt")]
177macro_rules! unwrap {
178 ($($x:tt)*) => {
179 ::defmt::unwrap!($($x)*)
180 };
181}
182
183#[cfg(not(feature = "defmt"))]
184macro_rules! unwrap {
185 ($arg:expr) => {
186 match $crate::fmt::Try::into_result($arg) {
187 ::core::result::Result::Ok(t) => t,
188 ::core::result::Result::Err(e) => {
189 ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e);
190 }
191 }
192 };
193 ($arg:expr, $($msg:expr),+ $(,)? ) => {
194 match $crate::fmt::Try::into_result($arg) {
195 ::core::result::Result::Ok(t) => t,
196 ::core::result::Result::Err(e) => {
197 ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e);
198 }
199 }
200 }
201}
202
203#[derive(Debug, Copy, Clone, Eq, PartialEq)]
204pub struct NoneError;
205
206pub trait Try {
207 type Ok;
208 type Error;
209 fn into_result(self) -> Result<Self::Ok, Self::Error>;
210}
211
212impl<T> Try for Option<T> {
213 type Ok = T;
214 type Error = NoneError;
215
216 #[inline]
217 fn into_result(self) -> Result<T, NoneError> {
218 self.ok_or(NoneError)
219 }
220}
221
222impl<T, E> Try for Result<T, E> {
223 type Ok = T;
224 type Error = E;
225
226 #[inline]
227 fn into_result(self) -> Self {
228 self
229 }
230}
231
232#[allow(unused)]
233pub(crate) struct Bytes<'a>(pub &'a [u8]);
234
235impl<'a> Debug for Bytes<'a> {
236 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
237 write!(f, "{:#02x?}", self.0)
238 }
239}
240
241impl<'a> Display for Bytes<'a> {
242 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
243 write!(f, "{:#02x?}", self.0)
244 }
245}
246
247impl<'a> LowerHex for Bytes<'a> {
248 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
249 write!(f, "{:#02x?}", self.0)
250 }
251}
252
253#[cfg(feature = "defmt")]
254impl<'a> defmt::Format for Bytes<'a> {
255 fn format(&self, fmt: defmt::Formatter) {
256 defmt::write!(fmt, "{:02x}", self.0)
257 }
258}
diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs
new file mode 100644
index 000000000..389bb33f2
--- /dev/null
+++ b/embassy-usb-dfu/src/lib.rs
@@ -0,0 +1,51 @@
1#![no_std]
2mod fmt;
3
4pub mod consts;
5
6#[cfg(feature = "dfu")]
7mod bootloader;
8#[cfg(feature = "dfu")]
9pub use self::bootloader::*;
10
11#[cfg(feature = "application")]
12mod application;
13#[cfg(feature = "application")]
14pub use self::application::*;
15
16#[cfg(any(
17 all(feature = "dfu", feature = "application"),
18 not(any(feature = "dfu", feature = "application"))
19))]
20compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features");
21
22/// Provides a platform-agnostic interface for initiating a system reset.
23///
24/// This crate exposes `ResetImmediate` when compiled with cortex-m or esp32c3 support, which immediately issues a
25/// reset request without interfacing with any other peripherals.
26///
27/// If alternate behaviour is desired, a custom implementation of Reset can be provided as a type argument to the usb_dfu function.
28pub trait Reset {
29 fn sys_reset() -> !;
30}
31
32#[cfg(feature = "esp32c3-hal")]
33pub struct ResetImmediate;
34
35#[cfg(feature = "esp32c3-hal")]
36impl Reset for ResetImmediate {
37 fn sys_reset() -> ! {
38 esp32c3_hal::reset::software_reset();
39 loop {}
40 }
41}
42
43#[cfg(feature = "cortex-m")]
44pub struct ResetImmediate;
45
46#[cfg(feature = "cortex-m")]
47impl Reset for ResetImmediate {
48 fn sys_reset() -> ! {
49 cortex_m::peripheral::SCB::sys_reset()
50 }
51}
diff --git a/examples/boot/application/stm32wb-dfu/.cargo/config.toml b/examples/boot/application/stm32wb-dfu/.cargo/config.toml
new file mode 100644
index 000000000..4f8094ff2
--- /dev/null
+++ b/examples/boot/application/stm32wb-dfu/.cargo/config.toml
@@ -0,0 +1,9 @@
1[target.'cfg(all(target_arch = "arm", target_os = "none"))']
2# replace your chip as listed in `probe-rs chip list`
3runner = "probe-rs run --chip STM32WLE5JCIx"
4
5[build]
6target = "thumbv7em-none-eabihf"
7
8[env]
9DEFMT_LOG = "trace"
diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml
new file mode 100644
index 000000000..f6beea498
--- /dev/null
+++ b/examples/boot/application/stm32wb-dfu/Cargo.toml
@@ -0,0 +1,32 @@
1[package]
2edition = "2021"
3name = "embassy-boot-stm32wb-dfu-examples"
4version = "0.1.0"
5license = "MIT OR Apache-2.0"
6
7[dependencies]
8embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" }
9embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] }
10embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] }
11embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] }
12embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] }
13embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" }
14embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb" }
15embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] }
16
17defmt = { version = "0.3", optional = true }
18defmt-rtt = { version = "0.4", optional = true }
19panic-reset = { version = "0.1.1" }
20embedded-hal = { version = "0.2.6" }
21
22cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
23cortex-m-rt = "0.7.0"
24
25[features]
26defmt = [
27 "dep:defmt",
28 "dep:defmt-rtt",
29 "embassy-stm32/defmt",
30 "embassy-boot-stm32/defmt",
31 "embassy-sync/defmt",
32]
diff --git a/examples/boot/application/stm32wb-dfu/README.md b/examples/boot/application/stm32wb-dfu/README.md
new file mode 100644
index 000000000..c8dce0387
--- /dev/null
+++ b/examples/boot/application/stm32wb-dfu/README.md
@@ -0,0 +1,29 @@
1# Examples using bootloader
2
3Example for STM32WL demonstrating the bootloader. The example consists of application binaries, 'a'
4which allows you to press a button to start the DFU process, and 'b' which is the updated
5application.
6
7
8## Prerequisites
9
10* `cargo-binutils`
11* `cargo-flash`
12* `embassy-boot-stm32`
13
14## Usage
15
16```
17# Flash bootloader
18cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4 --chip STM32WLE5JCIx
19# Build 'b'
20cargo build --release --bin b
21# Generate binary for 'b'
22cargo objcopy --release --bin b -- -O binary b.bin
23```
24
25# Flash `a` (which includes b.bin)
26
27```
28cargo flash --release --bin a --chip STM32WLE5JCIx
29```
diff --git a/examples/boot/application/stm32wb-dfu/build.rs b/examples/boot/application/stm32wb-dfu/build.rs
new file mode 100644
index 000000000..e1da69328
--- /dev/null
+++ b/examples/boot/application/stm32wb-dfu/build.rs
@@ -0,0 +1,37 @@
1//! This build script copies the `memory.x` file from the crate root into
2//! a directory where the linker can always find it at build time.
3//! For many projects this is optional, as the linker always searches the
4//! project root directory -- wherever `Cargo.toml` is. However, if you
5//! are using a workspace or have a more complicated build setup, this
6//! build script becomes required. Additionally, by requesting that
7//! Cargo re-run the build script whenever `memory.x` is changed,
8//! updating `memory.x` ensures a rebuild of the application with the
9//! new memory settings.
10
11use std::env;
12use std::fs::File;
13use std::io::Write;
14use std::path::PathBuf;
15
16fn main() {
17 // Put `memory.x` in our output directory and ensure it's
18 // on the linker search path.
19 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
20 File::create(out.join("memory.x"))
21 .unwrap()
22 .write_all(include_bytes!("memory.x"))
23 .unwrap();
24 println!("cargo:rustc-link-search={}", out.display());
25
26 // By default, Cargo will re-run a build script whenever
27 // any file in the project changes. By specifying `memory.x`
28 // here, we ensure the build script is only re-run when
29 // `memory.x` is changed.
30 println!("cargo:rerun-if-changed=memory.x");
31
32 println!("cargo:rustc-link-arg-bins=--nmagic");
33 println!("cargo:rustc-link-arg-bins=-Tlink.x");
34 if env::var("CARGO_FEATURE_DEFMT").is_ok() {
35 println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
36 }
37}
diff --git a/examples/boot/application/stm32wb-dfu/memory.x b/examples/boot/application/stm32wb-dfu/memory.x
new file mode 100644
index 000000000..ff1b800d2
--- /dev/null
+++ b/examples/boot/application/stm32wb-dfu/memory.x
@@ -0,0 +1,15 @@
1MEMORY
2{
3 /* NOTE 1 K = 1 KiBi = 1024 bytes */
4 BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K
5 BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
6 FLASH : ORIGIN = 0x08008000, LENGTH = 128K
7 DFU : ORIGIN = 0x08028000, LENGTH = 132K
8 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K
9}
10
11__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER);
12__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER);
13
14__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER);
15__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER);
diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs
new file mode 100644
index 000000000..fbecbf23b
--- /dev/null
+++ b/examples/boot/application/stm32wb-dfu/src/main.rs
@@ -0,0 +1,64 @@
1#![no_std]
2#![no_main]
3#![feature(type_alias_impl_trait)]
4
5use core::cell::RefCell;
6
7#[cfg(feature = "defmt-rtt")]
8use defmt_rtt::*;
9use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareState, FirmwareUpdaterConfig};
10use embassy_executor::Spawner;
11use embassy_stm32::flash::{Flash, WRITE_SIZE};
12use embassy_stm32::rcc::WPAN_DEFAULT;
13use embassy_stm32::usb::{self, Driver};
14use embassy_stm32::{bind_interrupts, peripherals};
15use embassy_sync::blocking_mutex::Mutex;
16use embassy_time::Duration;
17use embassy_usb::Builder;
18use embassy_usb_dfu::consts::DfuAttributes;
19use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate};
20use panic_reset as _;
21
22bind_interrupts!(struct Irqs {
23 USB_LP => usb::InterruptHandler<peripherals::USB>;
24});
25
26#[embassy_executor::main]
27async fn main(_spawner: Spawner) {
28 let mut config = embassy_stm32::Config::default();
29 config.rcc = WPAN_DEFAULT;
30 let p = embassy_stm32::init(config);
31 let flash = Flash::new_blocking(p.FLASH);
32 let flash = Mutex::new(RefCell::new(flash));
33
34 let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
35 let mut magic = AlignedBuffer([0; WRITE_SIZE]);
36 let mut firmware_state = BlockingFirmwareState::from_config(config, &mut magic.0);
37 firmware_state.mark_booted().expect("Failed to mark booted");
38
39 let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
40 let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
41 config.manufacturer = Some("Embassy");
42 config.product = Some("USB-DFU Runtime example");
43 config.serial_number = Some("1235678");
44
45 let mut device_descriptor = [0; 256];
46 let mut config_descriptor = [0; 256];
47 let mut bos_descriptor = [0; 256];
48 let mut control_buf = [0; 64];
49 let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD);
50 let mut builder = Builder::new(
51 driver,
52 config,
53 &mut device_descriptor,
54 &mut config_descriptor,
55 &mut bos_descriptor,
56 &mut [],
57 &mut control_buf,
58 );
59
60 usb_dfu::<_, _, ResetImmediate>(&mut builder, &mut state, Duration::from_millis(2500));
61
62 let mut dev = builder.build();
63 dev.run().await
64}
diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml
new file mode 100644
index 000000000..ada073970
--- /dev/null
+++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml
@@ -0,0 +1,63 @@
1[package]
2edition = "2021"
3name = "stm32wb-dfu-bootloader-example"
4version = "0.1.0"
5description = "Example USB DFUbootloader for the STM32WB series of chips"
6license = "MIT OR Apache-2.0"
7
8[dependencies]
9defmt = { version = "0.3", optional = true }
10defmt-rtt = { version = "0.4", optional = true }
11
12embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] }
13embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" }
14cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
15embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" }
16cortex-m-rt = { version = "0.7" }
17embedded-storage = "0.3.1"
18embedded-storage-async = "0.4.0"
19cfg-if = "1.0.0"
20embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["dfu", "cortex-m"] }
21embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb", default-features = false }
22embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" }
23
24[features]
25defmt = [
26 "dep:defmt",
27 "embassy-boot-stm32/defmt",
28 "embassy-stm32/defmt",
29 "embassy-usb/defmt",
30 "embassy-usb-dfu/defmt"
31]
32debug = ["defmt-rtt", "defmt"]
33
34[profile.dev]
35debug = 2
36debug-assertions = true
37incremental = false
38opt-level = 'z'
39overflow-checks = true
40
41[profile.release]
42codegen-units = 1
43debug = 2
44debug-assertions = false
45incremental = false
46lto = 'fat'
47opt-level = 'z'
48overflow-checks = false
49
50# do not optimize proc-macro crates = faster builds from scratch
51[profile.dev.build-override]
52codegen-units = 8
53debug = false
54debug-assertions = false
55opt-level = 0
56overflow-checks = false
57
58[profile.release.build-override]
59codegen-units = 8
60debug = false
61debug-assertions = false
62opt-level = 0
63overflow-checks = false
diff --git a/examples/boot/bootloader/stm32wb-dfu/README.md b/examples/boot/bootloader/stm32wb-dfu/README.md
new file mode 100644
index 000000000..a82b730b9
--- /dev/null
+++ b/examples/boot/bootloader/stm32wb-dfu/README.md
@@ -0,0 +1,11 @@
1# Bootloader for STM32
2
3The bootloader uses `embassy-boot` to interact with the flash.
4
5# Usage
6
7Flash the bootloader
8
9```
10cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx
11```
diff --git a/examples/boot/bootloader/stm32wb-dfu/build.rs b/examples/boot/bootloader/stm32wb-dfu/build.rs
new file mode 100644
index 000000000..fd605991f
--- /dev/null
+++ b/examples/boot/bootloader/stm32wb-dfu/build.rs
@@ -0,0 +1,27 @@
1use std::env;
2use std::fs::File;
3use std::io::Write;
4use std::path::PathBuf;
5
6fn main() {
7 // Put `memory.x` in our output directory and ensure it's
8 // on the linker search path.
9 let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap());
10 File::create(out.join("memory.x"))
11 .unwrap()
12 .write_all(include_bytes!("memory.x"))
13 .unwrap();
14 println!("cargo:rustc-link-search={}", out.display());
15
16 // By default, Cargo will re-run a build script whenever
17 // any file in the project changes. By specifying `memory.x`
18 // here, we ensure the build script is only re-run when
19 // `memory.x` is changed.
20 println!("cargo:rerun-if-changed=memory.x");
21
22 println!("cargo:rustc-link-arg-bins=--nmagic");
23 println!("cargo:rustc-link-arg-bins=-Tlink.x");
24 if env::var("CARGO_FEATURE_DEFMT").is_ok() {
25 println!("cargo:rustc-link-arg-bins=-Tdefmt.x");
26 }
27}
diff --git a/examples/boot/bootloader/stm32wb-dfu/memory.x b/examples/boot/bootloader/stm32wb-dfu/memory.x
new file mode 100644
index 000000000..858062631
--- /dev/null
+++ b/examples/boot/bootloader/stm32wb-dfu/memory.x
@@ -0,0 +1,18 @@
1MEMORY
2{
3 /* NOTE 1 K = 1 KiBi = 1024 bytes */
4 FLASH : ORIGIN = 0x08000000, LENGTH = 24K
5 BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K
6 ACTIVE : ORIGIN = 0x08008000, LENGTH = 128K
7 DFU : ORIGIN = 0x08028000, LENGTH = 132K
8 RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K
9}
10
11__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH);
12__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH);
13
14__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH);
15__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH);
16
17__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH);
18__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH);
diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs
new file mode 100644
index 000000000..a7ab813b6
--- /dev/null
+++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs
@@ -0,0 +1,93 @@
1#![no_std]
2#![no_main]
3
4use core::cell::RefCell;
5
6use cortex_m_rt::{entry, exception};
7#[cfg(feature = "defmt")]
8use defmt_rtt as _;
9use embassy_boot_stm32::*;
10use embassy_stm32::flash::{Flash, BANK1_REGION, WRITE_SIZE};
11use embassy_stm32::rcc::WPAN_DEFAULT;
12use embassy_stm32::usb::Driver;
13use embassy_stm32::{bind_interrupts, peripherals, usb};
14use embassy_sync::blocking_mutex::Mutex;
15use embassy_usb::Builder;
16use embassy_usb_dfu::consts::DfuAttributes;
17use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate};
18
19bind_interrupts!(struct Irqs {
20 USB_LP => usb::InterruptHandler<peripherals::USB>;
21});
22
23#[entry]
24fn main() -> ! {
25 let mut config = embassy_stm32::Config::default();
26 config.rcc = WPAN_DEFAULT;
27 let p = embassy_stm32::init(config);
28
29 // Prevent a hard fault when accessing flash 'too early' after boot.
30 #[cfg(feature = "defmt")]
31 for _ in 0..10000000 {
32 cortex_m::asm::nop();
33 }
34
35 let layout = Flash::new_blocking(p.FLASH).into_blocking_regions();
36 let flash = Mutex::new(RefCell::new(layout.bank1_region));
37
38 let config = BootLoaderConfig::from_linkerfile_blocking(&flash);
39 let active_offset = config.active.offset();
40 let bl = BootLoader::prepare::<_, _, _, 2048>(config);
41 if bl.state == State::DfuDetach {
42 let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11);
43 let mut config = embassy_usb::Config::new(0xc0de, 0xcafe);
44 config.manufacturer = Some("Embassy");
45 config.product = Some("USB-DFU Bootloader example");
46 config.serial_number = Some("1235678");
47
48 let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash);
49 let mut buffer = AlignedBuffer([0; WRITE_SIZE]);
50 let updater = BlockingFirmwareUpdater::new(fw_config, &mut buffer.0[..]);
51
52 let mut device_descriptor = [0; 256];
53 let mut config_descriptor = [0; 256];
54 let mut bos_descriptor = [0; 256];
55 let mut control_buf = [0; 4096];
56 let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD);
57 let mut builder = Builder::new(
58 driver,
59 config,
60 &mut device_descriptor,
61 &mut config_descriptor,
62 &mut bos_descriptor,
63 &mut [],
64 &mut control_buf,
65 );
66
67 usb_dfu::<_, _, _, ResetImmediate, 4096>(&mut builder, &mut state);
68
69 let mut dev = builder.build();
70 embassy_futures::block_on(dev.run());
71 }
72
73 unsafe { bl.load(BANK1_REGION.base + active_offset) }
74}
75
76#[no_mangle]
77#[cfg_attr(target_os = "none", link_section = ".HardFault.user")]
78unsafe extern "C" fn HardFault() {
79 cortex_m::peripheral::SCB::sys_reset();
80}
81
82#[exception]
83unsafe fn DefaultHandler(_: i16) -> ! {
84 const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32;
85 let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16;
86
87 panic!("DefaultHandler #{:?}", irqn);
88}
89
90#[panic_handler]
91fn panic(_info: &core::panic::PanicInfo) -> ! {
92 cortex_m::asm::udf();
93}