use usb_device::class_prelude::*;
use usb_device::Result;
const USB_CLASS_APPLICATION_SPECIFIC: u8 = 0xfe;
const DFU_SUBCLASS_FIRMWARE_UPGRADE: u8 = 0x01;
const DFU_PROTOCOL_RUNTIME: u8 = 0x01;
const DFU_TYPE_FUNCTIONAL: u8 = 0x21;
const DFU_REQ_DETACH: u8 = 0;
const DFU_REQ_GETSTATUS: u8 = 0x03;
#[repr(u8)]
#[derive(Clone, Copy)]
enum DfuState {
AppIdle = 0,
AppDetach = 1,
}
#[repr(u8)]
#[derive(Clone, Copy)]
enum DfuStatusCode {
OK = 0x00,
}
pub struct DfuRuntimeClass<T: DfuRuntimeOps> {
ops: T,
iface: InterfaceNumber,
timeout: Option<u16>,
state: DfuState,
}
pub trait DfuRuntimeOps {
fn detach(&mut self);
fn allow(&mut self, timeout: u16) -> Option<u16> {
Some(timeout)
}
const WILL_DETACH: bool = true;
const MANIFESTATION_TOLERANT: bool = false;
const CAN_DNLOAD: bool = true;
const CAN_UPLOAD: bool = true;
const DETACH_TIMEOUT_MS: u16 = 255;
const MAX_TRANSFER_SIZE: u16 = 2048; }
impl<T: DfuRuntimeOps> DfuRuntimeClass<T> {
pub fn new<B: UsbBus>(alloc: &UsbBusAllocator<B>, ops: T) -> Self {
Self {
ops,
iface: alloc.interface(),
timeout: None,
state: DfuState::AppIdle,
}
}
pub fn tick(&mut self, elapsed_time_ms: u16) {
if let Some(timeout) = self.timeout {
let new = timeout.saturating_sub(elapsed_time_ms);
if new == 0 {
self.timeout = None;
if T::WILL_DETACH {
self.ops.detach();
}
} else {
self.timeout = Some(new);
}
}
}
pub fn ops(&self) -> &T {
&self.ops
}
pub fn ops_mut(&mut self) -> &mut T {
&mut self.ops
}
pub fn interface(&self) -> InterfaceNumber {
self.iface
}
const fn dfu_bm_attributes() -> u8 {
(T::WILL_DETACH as u8) << 3
| (T::MANIFESTATION_TOLERANT as u8) << 2
| (T::CAN_DNLOAD as u8) << 1
| T::CAN_DNLOAD as u8
}
}
impl<T: DfuRuntimeOps, B: UsbBus> UsbClass<B> for DfuRuntimeClass<T> {
fn get_configuration_descriptors(&self, writer: &mut DescriptorWriter) -> Result<()> {
writer.iad(
self.iface,
1,
USB_CLASS_APPLICATION_SPECIFIC,
DFU_SUBCLASS_FIRMWARE_UPGRADE,
DFU_PROTOCOL_RUNTIME,
None)?;
writer.interface(
self.iface,
USB_CLASS_APPLICATION_SPECIFIC,
DFU_SUBCLASS_FIRMWARE_UPGRADE,
DFU_PROTOCOL_RUNTIME)?;
let detach_timeout: u16 = T::DETACH_TIMEOUT_MS;
let transfer_size: u16 = T::MAX_TRANSFER_SIZE;
let dfu_version: u16 = 0x011a;
writer.write(
DFU_TYPE_FUNCTIONAL, &[
Self::dfu_bm_attributes(), detach_timeout.to_le_bytes()[0], detach_timeout.to_le_bytes()[1], transfer_size.to_le_bytes()[0], transfer_size.to_le_bytes()[1], dfu_version.to_le_bytes()[0], dfu_version.to_le_bytes()[1], ])
}
fn control_in(&mut self, xfer:ControlIn<B>) {
let req = xfer.request();
if !(req.request_type == control::RequestType::Class
&& req.recipient == control::Recipient::Interface
&& req.index == u8::from(self.iface) as u16)
{
return;
}
match req.request {
DFU_REQ_GETSTATUS => {
let status: [u8;6] = [DfuStatusCode::OK as u8,
0,0,0, self.state as u8,
0]; xfer.accept_with(&status).ok();
},
_ => {
xfer.reject().ok();
},
}
}
fn control_out(&mut self, xfer: ControlOut<B>) {
let req = xfer.request();
if !(req.request_type == control::RequestType::Class
&& req.recipient == control::Recipient::Interface
&& req.index == u8::from(self.iface) as u16)
{
return;
}
match req.request {
DFU_REQ_DETACH => {
self.timeout = self.ops.allow(req.value);
if self.timeout.is_some() {
self.state = DfuState::AppDetach;
xfer.accept().ok();
} else {
xfer.reject().ok();
}
},
_ => { xfer.reject().ok(); },
}
}
fn reset(&mut self) {
if !T::WILL_DETACH && self.timeout.is_some() {
self.timeout = None;
self.ops.detach();
}
}
}