use embassy_time::{Duration, Instant};
use embassy_usb_driver::Driver;
use super::consts::{
APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT, DfuAttributes, Request, State, Status,
USB_CLASS_APPN_SPEC,
};
use crate::control::{InResponse, OutResponse, Recipient, Request as ControlRequest, RequestType};
use crate::{Builder, FunctionBuilder};
pub trait Handler {
fn enter_dfu(&mut self);
}
pub struct DfuState<H: Handler> {
handler: H,
state: State,
attrs: DfuAttributes,
detach_start: Option<Instant>,
timeout: Duration,
}
impl<H: Handler> DfuState<H> {
pub fn new(handler: H, attrs: DfuAttributes, timeout: Duration) -> Self {
DfuState {
handler,
state: State::AppIdle,
attrs,
detach_start: None,
timeout,
}
}
pub fn handler_mut(&mut self) -> &mut H {
&mut self.handler
}
}
impl<H: Handler> crate::Handler for DfuState<H> {
fn reset(&mut self) {
if let Some(start) = self.detach_start {
let delta = Instant::now() - start;
trace!(
"Received RESET with delta = {}, timeout = {}",
delta.as_millis(),
self.timeout.as_millis()
);
if delta < self.timeout {
self.handler.enter_dfu();
}
}
}
fn control_out(&mut self, req: ControlRequest, _: &[u8]) -> Option<OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
trace!("Received out request {:?}", req);
match Request::try_from(req.request) {
Ok(Request::Detach) => {
trace!("Received DETACH");
self.state = State::AppDetach;
self.detach_start = Some(Instant::now());
if self.attrs.contains(DfuAttributes::WILL_DETACH) {
trace!("WILL_DETACH set, performing reset");
self.handler.enter_dfu();
} else {
trace!("Awaiting USB reset");
}
Some(OutResponse::Accepted)
}
_ => None,
}
}
fn control_in<'a>(&'a mut self, req: ControlRequest, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
trace!("Received in request {:?}", req);
match Request::try_from(req.request) {
Ok(Request::GetStatus) => {
let timeout_ms = self.timeout.as_millis() as u16;
buf[0..6].copy_from_slice(&[
Status::Ok as u8,
(timeout_ms & 0xff) as u8,
((timeout_ms >> 8) & 0xff) as u8,
0x00,
self.state as u8,
0x00,
]);
Some(InResponse::Accepted(buf))
}
_ => None,
}
}
}
pub fn usb_dfu<'d, D: Driver<'d>, H: Handler>(
builder: &mut Builder<'d, D>,
state: &'d mut DfuState<H>,
func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>),
) {
let mut func = builder.function(0x00, 0x00, 0x00);
func_modifier(&mut func);
let timeout_ms = state.timeout.as_millis() as u16;
let mut iface = func.interface();
let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None);
alt.descriptor(
DESC_DFU_FUNCTIONAL,
&[
state.attrs.bits(),
(timeout_ms & 0xff) as u8,
((timeout_ms >> 8) & 0xff) as u8,
0x40,
0x00, 0x10,
0x01, ],
);
drop(func);
builder.handler(state);
}