Merge pull request #5216 from embassy-rs/usb-dfu-2

usb: add standalone dfu class implementation, use it in embassy-usb-dfu.
This commit is contained in:
Dario Nieuwenhuis 2026-01-13 13:39:04 +00:00 committed by GitHub
commit 8abb6ab381
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
16 changed files with 650 additions and 502 deletions

View File

@ -1,160 +1,4 @@
//! Application part of DFU logic
use embassy_boot::BlockingFirmwareState;
use embassy_time::{Duration, Instant};
use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
use embassy_usb::driver::Driver;
use embassy_usb::{Builder, FunctionBuilder, Handler};
use embedded_storage::nor_flash::NorFlash;
use crate::Reset;
use crate::consts::{
APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT, DfuAttributes, Request, State, Status,
USB_CLASS_APPN_SPEC,
};
/// Generic interface for a system that can signal to the bootloader that USB DFU mode is needed on the next boot.
///
/// By default this trait is implemented for `BlockingFirmwareState<'d, STATE>` but you could also implement this generic
/// interface yourself instead in more complex situations. This could for instance be when you cannot hand ownership of a
/// `BlockingFirmwareState` instance over directly to the DFU `Control` instance and need to use a more complex mechanism.
pub trait DfuMarker {
/// Signal to the bootloader that DFU mode should be used on the next boot.
fn mark_dfu(&mut self);
}
impl<'d, STATE: NorFlash> DfuMarker for BlockingFirmwareState<'d, STATE> {
fn mark_dfu(&mut self) {
self.mark_dfu().expect("Failed to mark DFU mode in bootloader")
}
}
/// Internal state for the DFU class
pub struct Control<MARK: DfuMarker, RST: Reset> {
dfu_marker: MARK,
attrs: DfuAttributes,
state: State,
timeout: Option<Duration>,
detach_start: Option<Instant>,
reset: RST,
}
impl<MARK: DfuMarker, RST: Reset> Control<MARK, RST> {
/// Create a new DFU instance to expose a DFU interface.
pub fn new(dfu_marker: MARK, attrs: DfuAttributes, reset: RST) -> Self {
Control {
dfu_marker,
attrs,
state: State::AppIdle,
detach_start: None,
timeout: None,
reset,
}
}
}
impl<MARK: DfuMarker, RST: Reset> Handler for Control<MARK, RST> {
fn reset(&mut self) {
if let Some(start) = self.detach_start {
let delta = Instant::now() - start;
let timeout = self.timeout.unwrap();
trace!(
"Received RESET with delta = {}, timeout = {}",
delta.as_millis(),
timeout.as_millis()
);
if delta < timeout {
self.dfu_marker.mark_dfu();
self.reset.sys_reset()
}
}
}
fn control_out(
&mut self,
req: embassy_usb::control::Request,
_: &[u8],
) -> Option<embassy_usb::control::OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
trace!("Received request {}", req);
match Request::try_from(req.request) {
Ok(Request::Detach) => {
self.detach_start = Some(Instant::now());
self.timeout = Some(Duration::from_millis(req.value as u64));
self.state = State::AppDetach;
if self.attrs.contains(DfuAttributes::WILL_DETACH) {
trace!("Received DETACH, performing reset");
self.reset();
} else {
trace!("Received DETACH, awaiting USB reset");
}
Some(OutResponse::Accepted)
}
_ => None,
}
}
fn control_in<'a>(
&'a mut self,
req: embassy_usb::control::Request,
buf: &'a mut [u8],
) -> Option<embassy_usb::control::InResponse<'a>> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
trace!("Received request {}", req);
match Request::try_from(req.request) {
Ok(Request::GetStatus) => {
buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
Some(InResponse::Accepted(buf))
}
_ => None,
}
}
}
/// An implementation of the USB DFU 1.1 runtime protocol
///
/// 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.
/// The handler is responsive to DFU GetStatus and Detach commands.
///
/// 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
/// it should expose a DFU device, and a software reset will be issued.
///
/// 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.
pub fn usb_dfu<'d, D: Driver<'d>, MARK: DfuMarker, RST: Reset>(
builder: &mut Builder<'d, D>,
handler: &'d mut Control<MARK, RST>,
timeout: Duration,
func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>),
) {
let mut func = builder.function(0x00, 0x00, 0x00);
// Here we give users the opportunity to add their own function level MSOS headers for instance.
// This is useful when DFU functionality is part of a composite USB device.
func_modifier(&mut func);
let mut iface = func.interface();
let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None);
let timeout = timeout.as_millis() as u16;
alt.descriptor(
DESC_DFU_FUNCTIONAL,
&[
handler.attrs.bits(),
(timeout & 0xff) as u8,
((timeout >> 8) & 0xff) as u8,
0x40,
0x00, // 64B control buffer size for application side
0x10,
0x01, // DFU 1.1
],
);
drop(func);
builder.handler(handler);
}
pub use embassy_usb::class::dfu::app_mode::{DfuState, Handler, usb_dfu};
pub use embassy_usb::class::dfu::consts::DfuAttributes;

View File

@ -1,144 +0,0 @@
//! USB DFU constants, taken from
//! https://www.usb.org/sites/default/files/DFU_1.1.pdf
/// Device Firmware Upgrade class, App specific
pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE;
/// Device Firmware Upgrade subclass, App specific
pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01;
#[allow(unused)]
/// USB interface alternative setting
pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02;
#[allow(unused)]
/// DFU runtime class
pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01;
/// DFU functional descriptor
pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21;
macro_rules! define_dfu_attributes {
($macro:path) => {
$macro! {
/// Attributes supported by the DFU controller.
pub struct DfuAttributes: u8 {
/// Generate WillDetache sequence on bus.
const WILL_DETACH = 0b0000_1000;
/// Device can communicate during manifestation phase.
const MANIFESTATION_TOLERANT = 0b0000_0100;
/// Capable of upload.
const CAN_UPLOAD = 0b0000_0010;
/// Capable of download.
const CAN_DOWNLOAD = 0b0000_0001;
}
}
};
}
#[cfg(feature = "defmt")]
define_dfu_attributes!(defmt::bitflags);
#[cfg(not(feature = "defmt"))]
define_dfu_attributes!(bitflags::bitflags);
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[allow(unused)]
/// An indication of the state that the device is going to enter immediately following transmission of this response.
pub(crate) enum State {
/// Device is running its normal application.
AppIdle = 0,
/// Device is running its normal application, has received the DFU_DETACH request, and is waiting for a USB reset.
AppDetach = 1,
/// Device is operating in the DFU mode and is waiting for requests.
DfuIdle = 2,
/// Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS.
DlSync = 3,
/// Device is programming a control-write block into its nonvolatile memories.
DlBusy = 4,
/// Device is processing a download operation. Expecting DFU_DNLOAD requests.
Download = 5,
/// Device has received the final block of firmware from the host, waits for DFU_GETSTATUS to start Manifestation phase or completed this phase
ManifestSync = 6,
/// Device is in the Manifestation phase. Not all devices will be able to respond to DFU_GETSTATUS when in this state.
Manifest = 7,
/// Device has programmed its memories and is waiting for a USB reset or a power on reset.
ManifestWaitReset = 8,
/// The device is processing an upload operation. Expecting DFU_UPLOAD requests.
UploadIdle = 9,
/// An error has occurred. Awaiting the DFU_CLRSTATUS request.
Error = 10,
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[allow(unused)]
/// An indication of the status resulting from the execution of the most recent request.
pub(crate) enum Status {
/// No error
Ok = 0x00,
/// File is not targeted for use by this device
ErrTarget = 0x01,
/// File is for this device but fails some vendor-specific verification test
ErrFile = 0x02,
/// Device is unable to write memory
ErrWrite = 0x03,
/// Memory erase function failed
ErrErase = 0x04,
/// Memory erase check failed
ErrCheckErased = 0x05,
/// Program memory function failed
ErrProg = 0x06,
/// Programmed memory failed verification
ErrVerify = 0x07,
/// Cannot program memory due to received address that is out of range
ErrAddress = 0x08,
/// Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet
ErrNotDone = 0x09,
/// Devices firmware is corrupt. It cannot return to run-time (non-DFU) operations
ErrFirmware = 0x0A,
/// iString indicates a vendor-specific error
ErrVendor = 0x0B,
/// Device detected unexpected USB reset signaling
ErrUsbr = 0x0C,
/// Device detected unexpected power on reset
ErrPor = 0x0D,
/// Something went wrong, but the device does not know what
ErrUnknown = 0x0E,
/// Device stalled an unexpected request
ErrStalledPkt = 0x0F,
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
/// DFU requests
pub(crate) enum Request {
/// Host instructs the device to generate a detach-attach sequence
Detach = 0,
/// Host initiates control-write transfers with this request, and sends a DFU_DNLOAD request
/// with wLength = 0 to indicate that it has completed transferring the firmware image file
Dnload = 1,
/// The DFU_UPLOAD request is employed by the host to solicit firmware from the device.
Upload = 2,
/// The host employs the DFU_GETSTATUS request to facilitate synchronization with the device.
GetStatus = 3,
/// Any time the device detects an error, it waits with transition until ClrStatus
ClrStatus = 4,
/// Requests a report about a state of the device
GetState = 5,
/// Enables the host to exit from certain states and return to the DFU_IDLE state
Abort = 6,
}
impl TryFrom<u8> for Request {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Request::Detach),
1 => Ok(Request::Dnload),
2 => Ok(Request::Upload),
3 => Ok(Request::GetStatus),
4 => Ok(Request::ClrStatus),
5 => Ok(Request::GetState),
6 => Ok(Request::Abort),
_ => Err(()),
}
}
}

View File

@ -1,22 +1,21 @@
//! DFU bootloader part of DFU logic
use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterError};
use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
use embassy_usb::class::dfu::consts::{DfuAttributes, Status};
/// Re-export DfuState from embassy-usb for convenience.
pub use embassy_usb::class::dfu::dfu_mode::DfuState as UsbDfuState;
use embassy_usb::class::dfu::dfu_mode::{self, DfuState};
use embassy_usb::driver::Driver;
use embassy_usb::{Builder, FunctionBuilder, Handler};
use embassy_usb::{Builder, FunctionBuilder};
use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind};
use crate::Reset;
use crate::consts::{
APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU, DfuAttributes, Request, State, Status,
USB_CLASS_APPN_SPEC,
};
/// Internal state for USB DFU
pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> {
/// Internal handler for USB DFU firmware updates.
///
/// This implements the `embassy_usb::class::dfu::dfu_mode::Handler` trait,
/// providing the firmware write logic using `BlockingFirmwareUpdater`.
pub struct FirmwareHandler<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> {
updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
attrs: DfuAttributes,
state: State,
status: Status,
offset: usize,
buf: AlignedBuffer<BLOCK_SIZE>,
reset: RST,
@ -25,19 +24,17 @@ pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_S
public_key: &'static [u8; 32],
}
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> {
/// Create a new DFU instance to handle DFU transfers.
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>
FirmwareHandler<'d, DFU, STATE, RST, BLOCK_SIZE>
{
/// Create a new firmware handler.
pub fn new(
updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
attrs: DfuAttributes,
reset: RST,
#[cfg(feature = "_verify")] public_key: &'static [u8; 32],
) -> Self {
Self {
updater,
attrs,
state: State::DfuIdle,
status: Status::Ok,
offset: 0,
buf: AlignedBuffer([0; BLOCK_SIZE]),
reset,
@ -46,170 +43,109 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Co
public_key,
}
}
}
fn reset_state(&mut self) {
self.offset = 0;
self.state = State::DfuIdle;
self.status = Status::Ok;
fn firmware_error_to_status(e: FirmwareUpdaterError) -> Status {
match e {
FirmwareUpdaterError::Flash(e) => match e {
NorFlashErrorKind::NotAligned => Status::ErrWrite,
NorFlashErrorKind::OutOfBounds => Status::ErrAddress,
_ => Status::ErrUnknown,
},
FirmwareUpdaterError::Signature(_) => Status::ErrVerify,
FirmwareUpdaterError::BadState => Status::ErrUnknown,
}
}
impl From<FirmwareUpdaterError> for Status {
fn from(e: FirmwareUpdaterError) -> Self {
match e {
FirmwareUpdaterError::Flash(e) => match e {
NorFlashErrorKind::NotAligned => Status::ErrWrite,
NorFlashErrorKind::OutOfBounds => Status::ErrAddress,
_ => Status::ErrUnknown,
},
FirmwareUpdaterError::Signature(_) => Status::ErrVerify,
FirmwareUpdaterError::BadState => Status::ErrUnknown,
}
}
}
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler
for Control<'d, DFU, STATE, RST, BLOCK_SIZE>
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> dfu_mode::Handler
for FirmwareHandler<'d, DFU, STATE, RST, BLOCK_SIZE>
{
fn control_out(
&mut self,
req: embassy_usb::control::Request,
data: &[u8],
) -> Option<embassy_usb::control::OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
debug!("Unknown out request: {:?}", req);
return None;
fn start(&mut self) {
info!("Download starting");
self.offset = 0;
}
fn write(&mut self, data: &[u8]) -> Result<(), Status> {
if data.len() > BLOCK_SIZE {
error!("USB data len exceeded block size");
return Err(Status::ErrUnknown);
}
match Request::try_from(req.request) {
Ok(Request::Abort) => {
info!("Abort requested");
self.reset_state();
Some(OutResponse::Accepted)
debug!("Copying {} bytes to buffer", data.len());
self.buf.as_mut()[..data.len()].copy_from_slice(data);
debug!("Writing {} bytes at {}", data.len(), self.offset);
match self.updater.write_firmware(self.offset, self.buf.as_ref()) {
Ok(_) => {
self.offset += data.len();
Ok(())
}
Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
if req.value == 0 {
info!("Download starting");
self.state = State::Download;
self.offset = 0;
}
if self.state != State::Download {
error!("Unexpected DNLOAD while chip is waiting for a GETSTATUS");
self.status = Status::ErrUnknown;
self.state = State::Error;
return Some(OutResponse::Rejected);
}
if data.len() > BLOCK_SIZE {
error!("USB data len exceeded block size");
self.status = Status::ErrUnknown;
self.state = State::Error;
return Some(OutResponse::Rejected);
}
debug!("Copying {} bytes to buffer", data.len());
self.buf.as_mut()[..data.len()].copy_from_slice(data);
let final_transfer = req.length == 0;
if final_transfer {
debug!("Receiving final transfer");
#[cfg(feature = "_verify")]
let update_res: Result<(), FirmwareUpdaterError> = {
const SIGNATURE_LEN: usize = 64;
let mut signature = [0; SIGNATURE_LEN];
let update_len = (self.offset - SIGNATURE_LEN) as u32;
self.updater.read_dfu(update_len, &mut signature).and_then(|_| {
self.updater
.verify_and_mark_updated(self.public_key, &signature, update_len)
})
};
#[cfg(not(feature = "_verify"))]
let update_res = self.updater.mark_updated();
match update_res {
Ok(_) => {
self.status = Status::Ok;
self.state = State::ManifestSync;
info!("Update complete");
}
Err(e) => {
error!("Error completing update: {}", e);
self.state = State::Error;
self.status = e.into();
}
}
} else {
debug!("Writing {} bytes at {}", data.len(), self.offset);
match self.updater.write_firmware(self.offset, self.buf.as_ref()) {
Ok(_) => {
self.status = Status::Ok;
self.state = State::DlSync;
self.offset += data.len();
}
Err(e) => {
error!("Error writing firmware: {:?}", e);
self.state = State::Error;
self.status = e.into();
}
}
}
Some(OutResponse::Accepted)
Err(e) => {
error!("Error writing firmware: {:?}", e);
Err(firmware_error_to_status(e))
}
Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode
Ok(Request::ClrStatus) => {
info!("Clear status requested");
self.reset_state();
Some(OutResponse::Accepted)
}
_ => None,
}
}
fn control_in<'a>(
&'a mut self,
req: embassy_usb::control::Request,
buf: &'a mut [u8],
) -> Option<embassy_usb::control::InResponse<'a>> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
debug!("Unknown in request: {:?}", req);
return None;
}
match Request::try_from(req.request) {
Ok(Request::GetStatus) => {
match self.state {
State::DlSync => self.state = State::Download,
State::ManifestSync => self.state = State::ManifestWaitReset,
_ => {}
}
fn finish(&mut self) -> Result<(), Status> {
debug!("Receiving final transfer");
//TODO: Configurable poll timeout, ability to add string for Vendor error
buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
Some(InResponse::Accepted(&buf[0..6]))
#[cfg(feature = "_verify")]
let update_res: Result<(), FirmwareUpdaterError> = {
const SIGNATURE_LEN: usize = 64;
let mut signature = [0; SIGNATURE_LEN];
let update_len = (self.offset - SIGNATURE_LEN) as u32;
self.updater.read_dfu(update_len, &mut signature).and_then(|_| {
self.updater
.verify_and_mark_updated(self.public_key, &signature, update_len)
})
};
#[cfg(not(feature = "_verify"))]
let update_res = self.updater.mark_updated();
match update_res {
Ok(_) => {
info!("Update complete");
Ok(())
}
Ok(Request::GetState) => {
buf[0] = self.state as u8;
Some(InResponse::Accepted(&buf[0..1]))
Err(e) => {
error!("Error completing update: {}", e);
Err(firmware_error_to_status(e))
}
Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
//TODO: FirmwareUpdater provides a way of reading the active partition so we could in theory add functionality to upload firmware.
Some(InResponse::Rejected)
}
_ => None,
}
}
fn reset(&mut self) {
if matches!(self.state, State::ManifestSync | State::ManifestWaitReset) {
self.reset.sys_reset()
}
fn system_reset(&mut self) {
self.reset.sys_reset()
}
}
/// Convenience type alias for the DFU state with firmware handler.
pub type State<'d, DFU, STATE, RST, const BLOCK_SIZE: usize> =
DfuState<FirmwareHandler<'d, DFU, STATE, RST, BLOCK_SIZE>>;
/// Create a new DFU state instance.
///
/// This creates a `DfuState` with a `FirmwareHandler` inside, ready to be
/// used with `usb_dfu`.
pub fn new_state<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>(
updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
attrs: DfuAttributes,
reset: RST,
#[cfg(feature = "_verify")] public_key: &'static [u8; 32],
) -> State<'d, DFU, STATE, RST, BLOCK_SIZE> {
let handler = FirmwareHandler::new(
updater,
reset,
#[cfg(feature = "_verify")]
public_key,
);
DfuState::new(handler, attrs)
}
/// An implementation of the USB DFU 1.1 protocol
///
/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device
@ -219,30 +155,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Ha
/// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware.
pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>(
builder: &mut Builder<'d, D>,
handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>,
state: &'d mut State<'d, DFU, STATE, RST, BLOCK_SIZE>,
func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>),
) {
let mut func = builder.function(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU);
// Here we give users the opportunity to add their own function level MSOS headers for instance.
// This is useful when DFU functionality is part of a composite USB device.
func_modifier(&mut func);
let mut iface = func.interface();
let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None);
alt.descriptor(
DESC_DFU_FUNCTIONAL,
&[
handler.attrs.bits(),
0xc4,
0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code
(BLOCK_SIZE & 0xff) as u8,
((BLOCK_SIZE & 0xff00) >> 8) as u8,
0x10,
0x01, // DFU 1.1
],
);
drop(func);
builder.handler(handler);
dfu_mode::usb_dfu(builder, state, BLOCK_SIZE, func_modifier);
}

View File

@ -3,7 +3,10 @@
#![warn(missing_docs)]
mod fmt;
pub mod consts;
/// Re-export DFU constants from embassy-usb.
pub mod consts {
pub use embassy_usb::class::dfu::consts::*;
}
#[cfg(feature = "dfu")]
pub mod dfu;

View File

@ -62,11 +62,13 @@ embassy-futures = { version = "0.1.2", path = "../embassy-futures" }
embassy-usb-driver = { version = "0.2.0", path = "../embassy-usb-driver" }
embassy-sync = { version = "0.7.2", path = "../embassy-sync" }
embassy-net-driver-channel = { version = "0.3.2", path = "../embassy-net-driver-channel" }
embassy-time = { version = "0.5.0", path = "../embassy-time" }
defmt = { version = "1", optional = true }
log = { version = "0.4.14", optional = true }
heapless = "0.8"
embedded-io-async = { version = "0.7.0" }
bitflags = "2.4.1"
# for HID
usbd-hid = { version = "0.9.0", optional = true }

View File

@ -0,0 +1,152 @@
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};
/// Handler trait for DFU runtime mode.
///
/// Implement this trait to handle entering DFU mode.
pub trait Handler {
/// Called when the device should enter DFU mode.
///
/// This is called after a valid detach sequence (detach request followed by
/// USB reset within the timeout period). The implementation should mark the
/// device for DFU mode and perform a system reset.
fn enter_dfu(&mut self);
}
/// Internal state for the DFU class
pub struct DfuState<H: Handler> {
handler: H,
state: State,
attrs: DfuAttributes,
detach_start: Option<Instant>,
timeout: Duration,
}
impl<H: Handler> DfuState<H> {
/// Create a new DFU instance to expose a DFU interface.
pub fn new(handler: H, attrs: DfuAttributes, timeout: Duration) -> Self {
DfuState {
handler,
state: State::AppIdle,
attrs,
detach_start: None,
timeout,
}
}
/// Get a mutable reference to the handler.
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,
}
}
}
/// An implementation of the USB DFU 1.1 runtime protocol
///
/// 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.
/// The handler is responsive to DFU GetStatus and Detach commands.
///
/// 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
/// it should expose a DFU device, and a software reset will be issued.
///
/// 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.
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);
// Here we give users the opportunity to add their own function level MSOS headers for instance.
// This is useful when DFU functionality is part of a composite USB device.
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, // 64B control buffer size for application side
0x10,
0x01, // DFU 1.1
],
);
drop(func);
builder.handler(state);
}

View File

@ -0,0 +1,124 @@
//! USB DFU constants and types.
pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE;
pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01;
#[allow(unused)]
pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02;
#[allow(unused)]
pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01;
pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21;
#[cfg(feature = "defmt")]
defmt::bitflags! {
/// Attributes supported by the DFU controller.
pub struct DfuAttributes: u8 {
/// Generate WillDetach sequence on bus.
const WILL_DETACH = 0b0000_1000;
/// Device can communicate during manifestation phase.
const MANIFESTATION_TOLERANT = 0b0000_0100;
/// Capable of upload.
const CAN_UPLOAD = 0b0000_0010;
/// Capable of download.
const CAN_DOWNLOAD = 0b0000_0001;
}
}
#[cfg(not(feature = "defmt"))]
bitflags::bitflags! {
/// Attributes supported by the DFU controller.
pub struct DfuAttributes: u8 {
/// Generate WillDetach sequence on bus.
const WILL_DETACH = 0b0000_1000;
/// Device can communicate during manifestation phase.
const MANIFESTATION_TOLERANT = 0b0000_0100;
/// Capable of upload.
const CAN_UPLOAD = 0b0000_0010;
/// Capable of download.
const CAN_DOWNLOAD = 0b0000_0001;
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[allow(unused)]
pub(crate) enum State {
AppIdle = 0,
AppDetach = 1,
DfuIdle = 2,
DlSync = 3,
DlBusy = 4,
Download = 5,
ManifestSync = 6,
Manifest = 7,
ManifestWaitReset = 8,
UploadIdle = 9,
Error = 10,
}
/// DFU status codes indicating the result of the most recent request.
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
#[allow(unused)]
pub enum Status {
/// No error.
Ok = 0x00,
/// File is not targeted for use by this device.
ErrTarget = 0x01,
/// File is for this device but fails some vendor-specific verification test.
ErrFile = 0x02,
/// Device is unable to write memory.
ErrWrite = 0x03,
/// Memory erase function failed.
ErrErase = 0x04,
/// Memory erase check failed.
ErrCheckErased = 0x05,
/// Program memory function failed.
ErrProg = 0x06,
/// Programmed memory failed verification.
ErrVerify = 0x07,
/// Cannot program memory due to received address that is out of range.
ErrAddress = 0x08,
/// Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet.
ErrNotDone = 0x09,
/// Device's firmware is corrupt. It cannot return to run-time (non-DFU) operations.
ErrFirmware = 0x0A,
/// iString indicates a vendor-specific error.
ErrVendor = 0x0B,
/// Device detected unexpected USB reset signaling.
ErrUsbr = 0x0C,
/// Device detected unexpected power on reset.
ErrPor = 0x0D,
/// Something went wrong, but the device does not know what.
ErrUnknown = 0x0E,
/// Device stalled an unexpected request.
ErrStalledPkt = 0x0F,
}
#[derive(Copy, Clone, PartialEq, Eq)]
#[repr(u8)]
pub(crate) enum Request {
Detach = 0,
Dnload = 1,
Upload = 2,
GetStatus = 3,
ClrStatus = 4,
GetState = 5,
Abort = 6,
}
impl TryFrom<u8> for Request {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Request::Detach),
1 => Ok(Request::Dnload),
2 => Ok(Request::Upload),
3 => Ok(Request::GetStatus),
4 => Ok(Request::ClrStatus),
5 => Ok(Request::GetState),
6 => Ok(Request::Abort),
_ => Err(()),
}
}
}

View File

@ -0,0 +1,212 @@
use embassy_usb_driver::Driver;
use super::consts::{
APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU, DfuAttributes, Request, State, Status,
USB_CLASS_APPN_SPEC,
};
use crate::control::{InResponse, OutResponse, Recipient, Request as ControlRequest, RequestType};
use crate::{Builder, FunctionBuilder};
/// Handler trait for DFU bootloader mode.
///
/// Implement this trait to handle firmware download operations.
pub trait Handler {
/// Called when a firmware download starts.
///
/// This is called when the first DFU_DNLOAD request is received.
fn start(&mut self);
/// Called to write a chunk of firmware data.
///
/// Returns `Ok(())` on success, or a `Status` error on failure.
fn write(&mut self, data: &[u8]) -> Result<(), Status>;
/// Called when the firmware download is complete.
///
/// This is called when a zero-length DFU_DNLOAD is received, indicating
/// the end of the firmware data. This is where you would typically
/// mark the firmware as ready to boot.
///
/// Returns `Ok(())` on success, or a `Status` error on failure.
fn finish(&mut self) -> Result<(), Status>;
/// Called at the end of the DFU procedure.
///
/// This is typically where you would perform a system reset to boot
/// the new firmware after a successful download.
fn system_reset(&mut self);
}
/// Internal state for USB DFU
pub struct DfuState<H: Handler> {
handler: H,
attrs: DfuAttributes,
state: State,
status: Status,
next_block_num: usize,
}
impl<'d, H: Handler> DfuState<H> {
/// Create a new DFU instance to handle DFU transfers.
pub fn new(handler: H, attrs: DfuAttributes) -> Self {
Self {
handler,
attrs,
state: State::DfuIdle,
status: Status::Ok,
next_block_num: 0,
}
}
fn reset_state(&mut self) {
self.next_block_num = 0;
self.state = State::DfuIdle;
self.status = Status::Ok;
}
}
impl<H: Handler> crate::Handler for DfuState<H> {
fn reset(&mut self) {
if matches!(self.state, State::ManifestSync | State::ManifestWaitReset) {
self.handler.system_reset();
}
}
fn control_out(&mut self, req: ControlRequest, data: &[u8]) -> Option<OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
match Request::try_from(req.request) {
Ok(Request::Abort) => {
info!("Abort requested");
self.reset_state();
Some(OutResponse::Accepted)
}
Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
if req.value as usize != self.next_block_num {
error!("expected next block num {}, got {}", self.next_block_num, req.value);
self.state = State::Error;
self.status = Status::ErrUnknown;
return Some(OutResponse::Rejected);
}
if req.value == 0 {
self.state = State::Download;
self.handler.start();
}
if req.length == 0 {
match self.handler.finish() {
Ok(_) => {
self.status = Status::Ok;
self.state = State::ManifestSync;
}
Err(e) => {
self.state = State::Error;
self.status = e;
}
}
} else {
if self.state != State::Download {
error!("Unexpected DNLOAD while chip is waiting for a GETSTATUS");
self.status = Status::ErrUnknown;
self.state = State::Error;
return Some(OutResponse::Rejected);
}
match self.handler.write(data) {
Ok(_) => {
self.status = Status::Ok;
self.state = State::DlSync;
self.next_block_num += 1;
}
Err(e) => {
self.state = State::Error;
self.status = e;
}
}
}
Some(OutResponse::Accepted)
}
Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode
Ok(Request::ClrStatus) => {
info!("Clear status requested");
self.reset_state();
Some(OutResponse::Accepted)
}
_ => {
debug!("Unknown OUT request {:?}", req);
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;
}
match Request::try_from(req.request) {
Ok(Request::GetStatus) => {
//TODO: Configurable poll timeout, ability to add string for Vendor error
buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
match self.state {
State::DlSync => self.state = State::Download,
State::ManifestSync => self.state = State::ManifestWaitReset,
_ => {}
}
Some(InResponse::Accepted(&buf[0..6]))
}
Ok(Request::GetState) => {
buf[0] = self.state as u8;
Some(InResponse::Accepted(&buf[0..1]))
}
Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
//TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload.
Some(InResponse::Rejected)
}
_ => {
debug!("Unknown IN request {:?}", req);
None
}
}
}
}
/// An implementation of the USB DFU 1.1 protocol
///
/// 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 handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user.
///
/// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition.
/// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware.
pub fn usb_dfu<'d, D: Driver<'d>, H: Handler>(
builder: &mut Builder<'d, D>,
state: &'d mut DfuState<H>,
max_write_size: usize,
func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>),
) {
let mut func = builder.function(0x00, 0x00, 0x00);
// Here we give users the opportunity to add their own function level MSOS headers for instance.
// This is useful when DFU functionality is part of a composite USB device.
func_modifier(&mut func);
let mut iface = func.interface();
let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None);
alt.descriptor(
DESC_DFU_FUNCTIONAL,
&[
state.attrs.bits(),
0xc4,
0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code
(max_write_size & 0xff) as u8,
((max_write_size & 0xff00) >> 8) as u8,
0x10,
0x01, // DFU 1.1
],
);
drop(func);
builder.handler(state);
}

View File

@ -0,0 +1,12 @@
//! USB Device Firmware Upgrade (DFU) class implementation.
//!
//! This module provides USB DFU 1.1 protocol support, split into two modes:
//! - `app_mode`: Runtime mode for applications to support detach requests
//! - `dfu_mode`: Bootloader mode for handling firmware downloads
pub mod consts;
/// DFU runtime mode (application side).
pub mod app_mode;
/// DFU bootloader mode (firmware download).
pub mod dfu_mode;

View File

@ -2,6 +2,7 @@
pub mod cdc_acm;
pub mod cdc_ncm;
pub mod cmsis_dap_v2;
pub mod dfu;
pub mod hid;
pub mod midi;
pub mod uac1;

View File

@ -19,6 +19,7 @@ defmt = { version = "1.0.1", optional = true }
defmt-rtt = { version = "1.0.0", optional = true }
panic-reset = { version = "0.1.1" }
embedded-hal = { version = "0.2.6" }
embedded-storage = "0.3.1"
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"

View File

@ -14,8 +14,7 @@ use embassy_stm32::{bind_interrupts, peripherals};
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::Duration;
use embassy_usb::{Builder, msos};
use embassy_usb_dfu::consts::DfuAttributes;
use embassy_usb_dfu::{Control, ResetImmediate, usb_dfu};
use embassy_usb_dfu::application::{DfuAttributes, DfuState, Handler, usb_dfu};
use panic_reset as _;
bind_interrupts!(struct Irqs {
@ -27,6 +26,17 @@ bind_interrupts!(struct Irqs {
// N.B. update to a custom GUID for your own device!
const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"];
struct DfuHandler<'d, FLASH: embedded_storage::nor_flash::NorFlash> {
firmware_state: BlockingFirmwareState<'d, FLASH>,
}
impl<FLASH: embedded_storage::nor_flash::NorFlash> Handler for DfuHandler<'_, FLASH> {
fn enter_dfu(&mut self) {
self.firmware_state.mark_dfu().expect("Failed to mark DFU mode");
cortex_m::peripheral::SCB::sys_reset();
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut config = embassy_stm32::Config::default();
@ -49,7 +59,10 @@ async fn main(_spawner: Spawner) {
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 64];
let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
let handler = DfuHandler { firmware_state };
let mut state = DfuState::new(handler, DfuAttributes::CAN_DOWNLOAD, Duration::from_millis(2500));
let mut builder = Builder::new(
driver,
config,
@ -72,7 +85,7 @@ async fn main(_spawner: Spawner) {
msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS),
));
usb_dfu(&mut builder, &mut state, Duration::from_millis(2500), |func| {
usb_dfu(&mut builder, &mut state, |func| {
// You likely don't have to add these function level headers if your USB device is not composite
// (i.e. if your device does not expose another interface in addition to DFU)
func.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));

View File

@ -19,6 +19,7 @@ defmt = { version = "1.0.1", optional = true }
defmt-rtt = { version = "1.0.0", optional = true }
panic-reset = { version = "0.1.1" }
embedded-hal = { version = "0.2.6" }
embedded-storage = "0.3.1"
cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] }
cortex-m-rt = "0.7.0"

View File

@ -13,8 +13,7 @@ use embassy_stm32::{bind_interrupts, peripherals};
use embassy_sync::blocking_mutex::Mutex;
use embassy_time::Duration;
use embassy_usb::{Builder, msos};
use embassy_usb_dfu::consts::DfuAttributes;
use embassy_usb_dfu::{Control, ResetImmediate, usb_dfu};
use embassy_usb_dfu::application::{DfuAttributes, DfuState, Handler, usb_dfu};
use panic_reset as _;
bind_interrupts!(struct Irqs {
@ -26,6 +25,17 @@ bind_interrupts!(struct Irqs {
// N.B. update to a custom GUID for your own device!
const DEVICE_INTERFACE_GUIDS: &[&str] = &["{EAA9A5DC-30BA-44BC-9232-606CDC875321}"];
struct DfuHandler<'d, FLASH: embedded_storage::nor_flash::NorFlash> {
firmware_state: BlockingFirmwareState<'d, FLASH>,
}
impl<FLASH: embedded_storage::nor_flash::NorFlash> Handler for DfuHandler<'_, FLASH> {
fn enter_dfu(&mut self) {
self.firmware_state.mark_dfu().expect("Failed to mark DFU mode");
cortex_m::peripheral::SCB::sys_reset();
}
}
#[embassy_executor::main]
async fn main(_spawner: Spawner) {
let mut config = embassy_stm32::Config::default();
@ -76,7 +86,10 @@ async fn main(_spawner: Spawner) {
let mut config_descriptor = [0; 256];
let mut bos_descriptor = [0; 256];
let mut control_buf = [0; 64];
let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
let handler = DfuHandler { firmware_state };
let mut state = DfuState::new(handler, DfuAttributes::CAN_DOWNLOAD, Duration::from_millis(2500));
let mut builder = Builder::new(
driver,
config,
@ -99,7 +112,7 @@ async fn main(_spawner: Spawner) {
msos::PropertyData::RegMultiSz(DEVICE_INTERFACE_GUIDS),
));
usb_dfu(&mut builder, &mut state, Duration::from_millis(1000), |func| {
usb_dfu(&mut builder, &mut state, |func| {
// You likely don't have to add these function level headers if your USB device is not composite
// (i.e. if your device does not expose another interface in addition to DFU)
func.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", ""));

View File

@ -14,7 +14,7 @@ use embassy_stm32::{bind_interrupts, peripherals, usb};
use embassy_sync::blocking_mutex::Mutex;
use embassy_usb::{Builder, msos};
use embassy_usb_dfu::consts::DfuAttributes;
use embassy_usb_dfu::{Control, ResetImmediate, usb_dfu};
use embassy_usb_dfu::{ResetImmediate, new_state, usb_dfu};
bind_interrupts!(struct Irqs {
USB_LP => usb::InterruptHandler<peripherals::USB>;
@ -65,10 +65,10 @@ fn main() -> ! {
let mut control_buf = [0; 4096];
#[cfg(not(feature = "verify"))]
let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
let mut state = new_state(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
#[cfg(feature = "verify")]
let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY);
let mut state = new_state(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY);
let mut builder = Builder::new(
driver,

View File

@ -13,7 +13,7 @@ use embassy_stm32::{Config, bind_interrupts, peripherals, usb};
use embassy_sync::blocking_mutex::Mutex;
use embassy_usb::{Builder, msos};
use embassy_usb_dfu::consts::DfuAttributes;
use embassy_usb_dfu::{Control, ResetImmediate, usb_dfu};
use embassy_usb_dfu::{ResetImmediate, new_state, usb_dfu};
bind_interrupts!(struct Irqs {
USB_OTG_HS => usb::InterruptHandler<peripherals::USB_OTG_HS>;
@ -94,10 +94,10 @@ fn main() -> ! {
let mut control_buf = [0; 4096];
#[cfg(not(feature = "verify"))]
let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
let mut state = new_state(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate);
#[cfg(feature = "verify")]
let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY);
let mut state = new_state(updater, DfuAttributes::CAN_DOWNLOAD, ResetImmediate, PUBLIC_SIGNING_KEY);
let mut builder = Builder::new(
driver,