embassy_usb/class/dfu/
app_mode.rs1use embassy_time::{Duration, Instant};
2use embassy_usb_driver::Driver;
3
4use super::consts::{
5 APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT, DfuAttributes, Request, State, Status,
6 USB_CLASS_APPN_SPEC,
7};
8use crate::control::{InResponse, OutResponse, Recipient, Request as ControlRequest, RequestType};
9use crate::{Builder, FunctionBuilder};
10
11pub trait Handler {
15 fn enter_dfu(&mut self);
21}
22
23pub struct DfuState<H: Handler> {
25 handler: H,
26 state: State,
27 attrs: DfuAttributes,
28 detach_start: Option<Instant>,
29 timeout: Duration,
30}
31
32impl<H: Handler> DfuState<H> {
33 pub fn new(handler: H, attrs: DfuAttributes, timeout: Duration) -> Self {
35 DfuState {
36 handler,
37 state: State::AppIdle,
38 attrs,
39 detach_start: None,
40 timeout,
41 }
42 }
43
44 pub fn handler_mut(&mut self) -> &mut H {
46 &mut self.handler
47 }
48}
49
50impl<H: Handler> crate::Handler for DfuState<H> {
51 fn reset(&mut self) {
52 if let Some(start) = self.detach_start {
53 let delta = Instant::now() - start;
54 trace!(
55 "Received RESET with delta = {}, timeout = {}",
56 delta.as_millis(),
57 self.timeout.as_millis()
58 );
59 if delta < self.timeout {
60 self.handler.enter_dfu();
61 }
62 }
63 }
64
65 fn control_out(&mut self, req: ControlRequest, _: &[u8]) -> Option<OutResponse> {
66 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
67 return None;
68 }
69
70 trace!("Received out request {:?}", req);
71
72 match Request::try_from(req.request) {
73 Ok(Request::Detach) => {
74 trace!("Received DETACH");
75 self.state = State::AppDetach;
76 self.detach_start = Some(Instant::now());
77 if self.attrs.contains(DfuAttributes::WILL_DETACH) {
78 trace!("WILL_DETACH set, performing reset");
79 self.handler.enter_dfu();
80 } else {
81 trace!("Awaiting USB reset");
82 }
83 Some(OutResponse::Accepted)
84 }
85 _ => None,
86 }
87 }
88
89 fn control_in<'a>(&'a mut self, req: ControlRequest, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
90 if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
91 return None;
92 }
93
94 trace!("Received in request {:?}", req);
95
96 match Request::try_from(req.request) {
97 Ok(Request::GetStatus) => {
98 let timeout_ms = self.timeout.as_millis() as u16;
99 buf[0..6].copy_from_slice(&[
100 Status::Ok as u8,
101 (timeout_ms & 0xff) as u8,
102 ((timeout_ms >> 8) & 0xff) as u8,
103 0x00,
104 self.state as u8,
105 0x00,
106 ]);
107 Some(InResponse::Accepted(buf))
108 }
109 _ => None,
110 }
111 }
112}
113
114pub fn usb_dfu<'d, D: Driver<'d>, H: Handler>(
124 builder: &mut Builder<'d, D>,
125 state: &'d mut DfuState<H>,
126 func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>),
127) {
128 let mut func = builder.function(0x00, 0x00, 0x00);
129
130 func_modifier(&mut func);
133
134 let timeout_ms = state.timeout.as_millis() as u16;
135 let mut iface = func.interface();
136 let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None);
137 alt.descriptor(
138 DESC_DFU_FUNCTIONAL,
139 &[
140 state.attrs.bits(),
141 (timeout_ms & 0xff) as u8,
142 ((timeout_ms >> 8) & 0xff) as u8,
143 0x40,
144 0x00, 0x10,
146 0x01, ],
148 );
149
150 drop(func);
151 builder.handler(state);
152}