aboutsummaryrefslogtreecommitdiff
path: root/embassy-usb-dfu/src/application.rs
diff options
context:
space:
mode:
authorsodo <[email protected]>2024-01-02 01:37:00 +0900
committersodo <[email protected]>2024-01-02 13:34:22 +0900
commit6ee153a3e2eec284c0d9d87f31801265c0604f74 (patch)
tree8b801cbd15f9ad5052d5942c731e75736dc9d7eb /embassy-usb-dfu/src/application.rs
parentb7cd7952c890f585ff876c622482534e5d58d4a4 (diff)
parent0be9b0599aaf2e425d76ec7852ff4b3535defddf (diff)
Merge remote-tracking branch 'origin'
Diffstat (limited to 'embassy-usb-dfu/src/application.rs')
-rw-r--r--embassy-usb-dfu/src/application.rs136
1 files changed, 136 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..f0d7626f6
--- /dev/null
+++ b/embassy-usb-dfu/src/application.rs
@@ -0,0 +1,136 @@
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 /// Create a new DFU instance to expose a DFU interface.
28 pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self {
29 Control {
30 firmware_state,
31 attrs,
32 state: State::AppIdle,
33 detach_start: None,
34 timeout: None,
35 _rst: PhantomData,
36 }
37 }
38}
39
40impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> {
41 fn reset(&mut self) {
42 if let Some(start) = self.detach_start {
43 let delta = Instant::now() - start;
44 let timeout = self.timeout.unwrap();
45 trace!(
46 "Received RESET with delta = {}, timeout = {}",
47 delta.as_millis(),
48 timeout.as_millis()
49 );
50 if delta < timeout {
51 self.firmware_state
52 .mark_dfu()
53 .expect("Failed to mark DFU mode in bootloader");
54 RST::sys_reset()
55 }
56 }
57 }
58
59 fn control_out(
60 &mut self,
61 req: embassy_usb::control::Request,
62 _: &[u8],
63 ) -> Option<embassy_usb::control::OutResponse> {
64 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
65 return None;
66 }
67
68 trace!("Received request {}", req);
69
70 match Request::try_from(req.request) {
71 Ok(Request::Detach) => {
72 trace!("Received DETACH, awaiting USB reset");
73 self.detach_start = Some(Instant::now());
74 self.timeout = Some(Duration::from_millis(req.value as u64));
75 self.state = State::AppDetach;
76 Some(OutResponse::Accepted)
77 }
78 _ => None,
79 }
80 }
81
82 fn control_in<'a>(
83 &'a mut self,
84 req: embassy_usb::control::Request,
85 buf: &'a mut [u8],
86 ) -> Option<embassy_usb::control::InResponse<'a>> {
87 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
88 return None;
89 }
90
91 trace!("Received request {}", req);
92
93 match Request::try_from(req.request) {
94 Ok(Request::GetStatus) => {
95 buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
96 Some(InResponse::Accepted(buf))
97 }
98 _ => None,
99 }
100 }
101}
102
103/// An implementation of the USB DFU 1.1 runtime protocol
104///
105/// 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.
106/// The handler is responsive to DFU GetStatus and Detach commands.
107///
108/// 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
109/// it should expose a DFU device, and a software reset will be issued.
110///
111/// 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.
112pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>(
113 builder: &mut Builder<'d, D>,
114 handler: &'d mut Control<'d, STATE, RST>,
115 timeout: Duration,
116) {
117 let mut func = builder.function(0x00, 0x00, 0x00);
118 let mut iface = func.interface();
119 let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None);
120 let timeout = timeout.as_millis() as u16;
121 alt.descriptor(
122 DESC_DFU_FUNCTIONAL,
123 &[
124 handler.attrs.bits(),
125 (timeout & 0xff) as u8,
126 ((timeout >> 8) & 0xff) as u8,
127 0x40,
128 0x00, // 64B control buffer size for application side
129 0x10,
130 0x01, // DFU 1.1
131 ],
132 );
133
134 drop(func);
135 builder.handler(handler);
136}