aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorKaitlyn Kenwell <[email protected]>2023-12-13 14:40:49 -0500
committerKaitlyn Kenwell <[email protected]>2023-12-13 14:40:49 -0500
commit976a7ae22aa222213861c12d515115aac87bd2e0 (patch)
tree88f802c7d57df011c02142ef309ff5cffe8bd3b0
parent14f41a71b6ea9dedb4ee5b9c741fe10575772c7d (diff)
Add embassy-usb-dfu
-rw-r--r--embassy-boot/boot/src/boot_loader.rs4
-rw-r--r--embassy-boot/boot/src/firmware_updater/asynch.rs13
-rw-r--r--embassy-boot/boot/src/firmware_updater/blocking.rs17
-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.toml31
-rw-r--r--embassy-usb-dfu/src/application.rs114
-rw-r--r--embassy-usb-dfu/src/bootloader.rs196
-rw-r--r--embassy-usb-dfu/src/consts.rs96
-rw-r--r--embassy-usb-dfu/src/lib.rs16
10 files changed, 492 insertions, 7 deletions
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
index a8c19197b..c0deca22b 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, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_MAGIC};
9 9
10/// Errors returned by bootloader 10/// Errors returned by bootloader
11#[derive(PartialEq, Eq, Debug)] 11#[derive(PartialEq, Eq, Debug)]
@@ -384,6 +384,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S
384 384
385 if !state_word.iter().any(|&b| b != SWAP_MAGIC) { 385 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
386 Ok(State::Swap) 386 Ok(State::Swap)
387 } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) {
388 Ok(State::DfuDetach)
387 } else { 389 } else {
388 Ok(State::Boot) 390 Ok(State::Boot)
389 } 391 }
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs
index ae713bb6f..0a3cbc136 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, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_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
@@ -247,6 +253,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> {
247 self.set_magic(SWAP_MAGIC).await 253 self.set_magic(SWAP_MAGIC).await
248 } 254 }
249 255
256 /// Mark to trigger USB DFU on next boot.
257 pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
258 self.set_magic(DFU_DETACH_MAGIC).await
259 }
260
250 /// Mark firmware boot successful and stop rollback on reset. 261 /// Mark firmware boot successful and stop rollback on reset.
251 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 262 pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
252 self.set_magic(BOOT_MAGIC).await 263 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..b2a633d1e 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, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_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()
@@ -226,7 +232,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
226 232
227 // Make sure we are running a booted firmware to avoid reverting to a bad state. 233 // Make sure we are running a booted firmware to avoid reverting to a bad state.
228 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 234 fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
229 if self.get_state()? == State::Boot { 235 if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach {
230 Ok(()) 236 Ok(())
231 } else { 237 } else {
232 Err(FirmwareUpdaterError::BadState) 238 Err(FirmwareUpdaterError::BadState)
@@ -243,6 +249,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
243 249
244 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { 250 if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) {
245 Ok(State::Swap) 251 Ok(State::Swap)
252 } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) {
253 Ok(State::DfuDetach)
246 } else { 254 } else {
247 Ok(State::Boot) 255 Ok(State::Boot)
248 } 256 }
@@ -253,6 +261,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> {
253 self.set_magic(SWAP_MAGIC) 261 self.set_magic(SWAP_MAGIC)
254 } 262 }
255 263
264 /// Mark to trigger USB DFU on next boot.
265 pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> {
266 self.set_magic(DFU_DETACH_MAGIC)
267 }
268
256 /// Mark firmware boot successful and stop rollback on reset. 269 /// Mark firmware boot successful and stop rollback on reset.
257 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { 270 pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> {
258 self.set_magic(BOOT_MAGIC) 271 self.set_magic(BOOT_MAGIC)
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 9e70a4dca..451992945 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 DFU_DETACH request over USB, and is rebooting into the bootloader to apply a DFU.
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..62398afbc
--- /dev/null
+++ b/embassy-usb-dfu/Cargo.toml
@@ -0,0 +1,31 @@
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"] }
19defmt = { version = "0.3.5", optional = true }
20embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" }
21embassy-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" }
27
28[features]
29bootloader = []
30application = []
31defmt = ["dep:defmt"]
diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs
new file mode 100644
index 000000000..5a52a9fed
--- /dev/null
+++ b/embassy-usb-dfu/src/application.rs
@@ -0,0 +1,114 @@
1
2use embassy_boot::BlockingFirmwareUpdater;
3use embassy_time::{Instant, Duration};
4use embassy_usb::{Handler, control::{RequestType, Recipient, OutResponse, InResponse}, Builder, driver::Driver};
5use embedded_storage::nor_flash::NorFlash;
6
7use crate::consts::{DfuAttributes, Request, Status, State, USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT};
8
9/// Internal state for the DFU class
10pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> {
11 updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
12 attrs: DfuAttributes,
13 state: State,
14 timeout: Option<Duration>,
15 detach_start: Option<Instant>,
16}
17
18impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> {
19 pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self {
20 Control { updater, attrs, state: State::AppIdle, detach_start: None, timeout: None }
21 }
22}
23
24impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> {
25 fn reset(&mut self) {
26 if let Some(start) = self.detach_start {
27 let delta = Instant::now() - start;
28 let timeout = self.timeout.unwrap();
29 #[cfg(feature = "defmt")]
30 defmt::info!("Received RESET with delta = {}, timeout = {}", delta.as_millis(), timeout.as_millis());
31 if delta < timeout {
32 self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader");
33 cortex_m::asm::dsb();
34 cortex_m::peripheral::SCB::sys_reset();
35 }
36 }
37 }
38
39 fn control_out(&mut self, req: embassy_usb::control::Request, _: &[u8]) -> Option<embassy_usb::control::OutResponse> {
40 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
41 return None;
42 }
43
44 #[cfg(feature = "defmt")]
45 defmt::info!("Received request {}", req);
46
47 match Request::try_from(req.request) {
48 Ok(Request::Detach) => {
49 #[cfg(feature = "defmt")]
50 defmt::info!("Received DETACH, awaiting USB reset");
51 self.detach_start = Some(Instant::now());
52 self.timeout = Some(Duration::from_millis(req.value as u64));
53 self.state = State::AppDetach;
54 Some(OutResponse::Accepted)
55 }
56 _ => {
57 None
58 }
59 }
60 }
61
62 fn control_in<'a>(&'a mut self, req: embassy_usb::control::Request, buf: &'a mut [u8]) -> Option<embassy_usb::control::InResponse<'a>> {
63 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
64 return None;
65 }
66
67 #[cfg(feature = "defmt")]
68 defmt::info!("Received request {}", req);
69
70 match Request::try_from(req.request) {
71 Ok(Request::GetStatus) => {
72 buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
73 Some(InResponse::Accepted(buf))
74 }
75 _ => None
76 }
77 }
78}
79
80/// An implementation of the USB DFU 1.1 runtime protocol
81///
82/// 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.
83/// The handler is responsive to DFU GetStatus and Detach commands.
84///
85/// 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
86/// it should expose a DFU device, and a software reset will be issued.
87///
88/// 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.
89pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>(builder: &mut Builder<'d, D>, handler: &'d mut Control<'d, DFU, STATE>, timeout: Duration) {
90 #[cfg(feature = "defmt")]
91 defmt::info!("Application USB DFU initializing");
92 let mut func = builder.function(0x00, 0x00, 0x00);
93 let mut iface = func.interface();
94 let mut alt = iface.alt_setting(
95 USB_CLASS_APPN_SPEC,
96 APPN_SPEC_SUBCLASS_DFU,
97 DFU_PROTOCOL_RT,
98 None,
99 );
100 let timeout = timeout.as_millis() as u16;
101 alt.descriptor(
102 DESC_DFU_FUNCTIONAL,
103 &[
104 handler.attrs.bits(),
105 (timeout & 0xff) as u8,
106 ((timeout >> 8) & 0xff) as u8,
107 0x40, 0x00, // 64B control buffer size for application side
108 0x10, 0x01, // DFU 1.1
109 ],
110 );
111
112 drop(func);
113 builder.handler(handler);
114} \ No newline at end of file
diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs
new file mode 100644
index 000000000..7bcb0b258
--- /dev/null
+++ b/embassy-usb-dfu/src/bootloader.rs
@@ -0,0 +1,196 @@
1use embassy_boot::BlockingFirmwareUpdater;
2use embassy_usb::{
3 control::{InResponse, OutResponse, Recipient, RequestType},
4 driver::Driver,
5 Builder, Handler,
6};
7use embedded_storage::nor_flash::{NorFlashErrorKind, NorFlash};
8
9use crate::consts::{DfuAttributes, Request, State, Status, USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, DESC_DFU_FUNCTIONAL};
10
11/// Internal state for USB DFU
12pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> {
13 updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
14 attrs: DfuAttributes,
15 state: State,
16 status: Status,
17 offset: usize,
18}
19
20impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, BLOCK_SIZE> {
21 pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self {
22 Self {
23 updater,
24 attrs,
25 state: State::DfuIdle,
26 status: Status::Ok,
27 offset: 0,
28 }
29 }
30
31 fn reset_state(&mut self) {
32 self.offset = 0;
33 self.state = State::DfuIdle;
34 self.status = Status::Ok;
35 }
36}
37
38impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Control<'d, DFU, STATE, BLOCK_SIZE> {
39 fn control_out(
40 &mut self,
41 req: embassy_usb::control::Request,
42 data: &[u8],
43 ) -> Option<embassy_usb::control::OutResponse> {
44 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
45 return None;
46 }
47 match Request::try_from(req.request) {
48 Ok(Request::Abort) => {
49 self.reset_state();
50 Some(OutResponse::Accepted)
51 }
52 Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
53 if req.value == 0 {
54 self.state = State::Download;
55 self.offset = 0;
56 }
57
58 let mut buf = [0; BLOCK_SIZE];
59 buf[..data.len()].copy_from_slice(data);
60
61 if req.length == 0 {
62 match self.updater.mark_updated() {
63 Ok(_) => {
64 self.status = Status::Ok;
65 self.state = State::ManifestSync;
66 }
67 Err(e) => {
68 self.state = State::Error;
69 match e {
70 embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
71 NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
72 NorFlashErrorKind::OutOfBounds => {
73 self.status = Status::ErrAddress
74 }
75 _ => self.status = Status::ErrUnknown,
76 },
77 embassy_boot::FirmwareUpdaterError::Signature(_) => {
78 self.status = Status::ErrVerify
79 }
80 embassy_boot::FirmwareUpdaterError::BadState => {
81 self.status = Status::ErrUnknown
82 }
83 }
84 }
85 }
86 } else {
87 if self.state != State::Download {
88 // Unexpected DNLOAD while chip is waiting for a GETSTATUS
89 self.status = Status::ErrUnknown;
90 self.state = State::Error;
91 return Some(OutResponse::Rejected);
92 }
93 match self.updater.write_firmware(self.offset, &buf[..]) {
94 Ok(_) => {
95 self.status = Status::Ok;
96 self.state = State::DlSync;
97 self.offset += data.len();
98 }
99 Err(e) => {
100 self.state = State::Error;
101 match e {
102 embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
103 NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
104 NorFlashErrorKind::OutOfBounds => {
105 self.status = Status::ErrAddress
106 }
107 _ => self.status = Status::ErrUnknown,
108 },
109 embassy_boot::FirmwareUpdaterError::Signature(_) => {
110 self.status = Status::ErrVerify
111 }
112 embassy_boot::FirmwareUpdaterError::BadState => {
113 self.status = Status::ErrUnknown
114 }
115 }
116 }
117 }
118 }
119
120 Some(OutResponse::Accepted)
121 }
122 Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode
123 Ok(Request::ClrStatus) => {
124 self.reset_state();
125 Some(OutResponse::Accepted)
126 }
127 _ => None,
128 }
129 }
130
131 fn control_in<'a>(
132 &'a mut self,
133 req: embassy_usb::control::Request,
134 buf: &'a mut [u8],
135 ) -> Option<embassy_usb::control::InResponse<'a>> {
136 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
137 return None;
138 }
139 match Request::try_from(req.request) {
140 Ok(Request::GetStatus) => {
141 //TODO: Configurable poll timeout, ability to add string for Vendor error
142 buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
143 match self.state {
144 State::DlSync => self.state = State::Download,
145 State::ManifestSync => cortex_m::peripheral::SCB::sys_reset(),
146 _ => {}
147 }
148
149 Some(InResponse::Accepted(&buf[0..6]))
150 }
151 Ok(Request::GetState) => {
152 buf[0] = self.state as u8;
153 Some(InResponse::Accepted(&buf[0..1]))
154 }
155 Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
156 //TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload.
157 Some(InResponse::Rejected)
158 }
159 _ => None,
160 }
161 }
162}
163
164/// An implementation of the USB DFU 1.1 protocol
165///
166/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device
167/// The handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user.
168///
169/// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition.
170/// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware.
171pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize>(
172 builder: &mut Builder<'d, D>,
173 handler: &'d mut Control<'d, DFU, STATE, BLOCK_SIZE>,
174) {
175 let mut func = builder.function(0x00, 0x00, 0x00);
176 let mut iface = func.interface();
177 let mut alt = iface.alt_setting(
178 USB_CLASS_APPN_SPEC,
179 APPN_SPEC_SUBCLASS_DFU,
180 DFU_PROTOCOL_DFU,
181 None,
182 );
183 alt.descriptor(
184 DESC_DFU_FUNCTIONAL,
185 &[
186 handler.attrs.bits(),
187 0xc4, 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code
188 (BLOCK_SIZE & 0xff) as u8,
189 ((BLOCK_SIZE & 0xff00) >> 8) as u8,
190 0x10, 0x01, // DFU 1.1
191 ],
192 );
193
194 drop(func);
195 builder.handler(handler);
196}
diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs
new file mode 100644
index 000000000..b083af9de
--- /dev/null
+++ b/embassy-usb-dfu/src/consts.rs
@@ -0,0 +1,96 @@
1
2pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE;
3pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01;
4#[allow(unused)]
5pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02;
6#[allow(unused)]
7pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01;
8pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21;
9
10#[cfg(feature = "defmt")]
11defmt::bitflags! {
12 pub struct DfuAttributes: u8 {
13 const WILL_DETACH = 0b0000_1000;
14 const MANIFESTATION_TOLERANT = 0b0000_0100;
15 const CAN_UPLOAD = 0b0000_0010;
16 const CAN_DOWNLOAD = 0b0000_0001;
17 }
18}
19
20#[cfg(not(feature = "defmt"))]
21bitflags::bitflags! {
22 pub struct DfuAttributes: u8 {
23 const WILL_DETACH = 0b0000_1000;
24 const MANIFESTATION_TOLERANT = 0b0000_0100;
25 const CAN_UPLOAD = 0b0000_0010;
26 const CAN_DOWNLOAD = 0b0000_0001;
27 }
28}
29
30#[derive(Copy, Clone, PartialEq, Eq)]
31#[repr(u8)]
32#[allow(unused)]
33pub enum State {
34 AppIdle = 0,
35 AppDetach = 1,
36 DfuIdle = 2,
37 DlSync = 3,
38 DlBusy = 4,
39 Download = 5,
40 ManifestSync = 6,
41 Manifest = 7,
42 ManifestWaitReset = 8,
43 UploadIdle = 9,
44 Error = 10,
45}
46
47#[derive(Copy, Clone, PartialEq, Eq)]
48#[repr(u8)]
49#[allow(unused)]
50pub enum Status {
51 Ok = 0x00,
52 ErrTarget = 0x01,
53 ErrFile = 0x02,
54 ErrWrite = 0x03,
55 ErrErase = 0x04,
56 ErrCheckErased = 0x05,
57 ErrProg = 0x06,
58 ErrVerify = 0x07,
59 ErrAddress = 0x08,
60 ErrNotDone = 0x09,
61 ErrFirmware = 0x0A,
62 ErrVendor = 0x0B,
63 ErrUsbr = 0x0C,
64 ErrPor = 0x0D,
65 ErrUnknown = 0x0E,
66 ErrStalledPkt = 0x0F,
67}
68
69#[derive(Copy, Clone, PartialEq, Eq)]
70#[repr(u8)]
71pub enum Request {
72 Detach = 0,
73 Dnload = 1,
74 Upload = 2,
75 GetStatus = 3,
76 ClrStatus = 4,
77 GetState = 5,
78 Abort = 6,
79}
80
81impl TryFrom<u8> for Request {
82 type Error = ();
83
84 fn try_from(value: u8) -> Result<Self, Self::Error> {
85 match value {
86 0 => Ok(Request::Detach),
87 1 => Ok(Request::Dnload),
88 2 => Ok(Request::Upload),
89 3 => Ok(Request::GetStatus),
90 4 => Ok(Request::ClrStatus),
91 5 => Ok(Request::GetState),
92 6 => Ok(Request::Abort),
93 _ => Err(()),
94 }
95 }
96}
diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs
new file mode 100644
index 000000000..dcdb11b2a
--- /dev/null
+++ b/embassy-usb-dfu/src/lib.rs
@@ -0,0 +1,16 @@
1#![no_std]
2
3pub mod consts;
4
5#[cfg(feature = "bootloader")]
6mod bootloader;
7#[cfg(feature = "bootloader")]
8pub use self::bootloader::*;
9
10#[cfg(feature = "application")]
11mod application;
12#[cfg(feature = "application")]
13pub use self::application::*;
14
15#[cfg(any(all(feature = "bootloader", feature = "application"), not(any(feature = "bootloader", feature = "application"))))]
16compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features");