aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb-dfu/src/application.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-usb-dfu/src/application.rs')
-rw-r--r--embassy-usb-dfu/src/application.rs114
1 files changed, 114 insertions, 0 deletions
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