1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
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_WILL_DETACH: u8 = 1 << 3;
const DFU_MANIFESTATION_TOLERANT: u8 = 1 << 2;
const DFU_CAN_UPLOAD: u8 = 1 << 1;
const DFU_CAN_DNLOAD: u8 = 1 << 0;

const DFU_REQ_DETACH: u8 = 0;

/// Implementation of DFU runtime class.
///
/// Implements DFU_DETACH request and will call the [`DfuRuntimeOps::enter`] callback when
/// the request is received.
pub struct DfuRuntimeClass<T: DfuRuntimeOps> {
    dfu_ops: T,
    iface: InterfaceNumber,
    timeout: Option<u16>,
}

/// Trait that defines device-specific operations for [`DfuRuntimeClass`].
pub trait DfuRuntimeOps {
    /// Enter DFU mode.
    ///
    /// This is a callback that will be called after receiving the DFU_DETACH request.
    fn enter(&mut self);

    // TODO: get_time_ms, configurable DFU functional descriptor
}

impl<T: DfuRuntimeOps> DfuRuntimeClass<T> {
    /// Crate new DFU run-time class with the given device-specific implementations.
    pub fn new<B: UsbBus>(alloc: &UsbBusAllocator<B>, dfu_ops: T) -> Self {
        Self {
            dfu_ops,
            iface: alloc.interface(),
            timeout: None,
        }
    }
}

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)?;

        writer.interface(
            self.iface,
            USB_CLASS_APPLICATION_SPECIFIC,
            DFU_SUBCLASS_FIRMWARE_UPGRADE,
            DFU_PROTOCOL_RUNTIME)?;

        // Run-Time DFU Functional Descriptor
        let detach_timeout: u16 = 255;
        let transfer_size: u16 = 2048;
        let dfu_version: u16 = 0x011a;
        writer.write(
            DFU_TYPE_FUNCTIONAL,  // bDescriptorType
            &[
                (DFU_WILL_DETACH | DFU_CAN_UPLOAD | DFU_CAN_DNLOAD) & !DFU_MANIFESTATION_TOLERANT,  // bmAttributes
                // 0,  // bmAttributes
                detach_timeout.to_le_bytes()[0], detach_timeout.to_le_bytes()[1],  // wDetachTimeOut
                transfer_size.to_le_bytes()[0], transfer_size.to_le_bytes()[1], // wTransferSize
                dfu_version.to_le_bytes()[0], dfu_version.to_le_bytes()[1], // bcdDFUVersion
            ])
    }

    fn poll(&mut self) {
        // TODO: implement timeout
        if let Some(_timeout) = self.timeout.take() {
            self.dfu_ops.enter();
        }
    }

    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 = Some(req.value);
                xfer.accept().ok();
            },
            _ => { xfer.reject().ok(); },
        }
    }
}