diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index 1ea1a74fe..538d4305b 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -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 { - dfu_marker: MARK, - attrs: DfuAttributes, - state: State, - timeout: Option, - detach_start: Option, - reset: RST, -} - -impl Control { - /// 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 Handler for Control { - 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 { - 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> { - 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, - 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; diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs deleted file mode 100644 index 8701dabc4..000000000 --- a/embassy-usb-dfu/src/consts.rs +++ /dev/null @@ -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, - /// 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)] -/// 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 for Request { - type Error = (); - - fn try_from(value: u8) -> Result { - 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(()), - } - } -} diff --git a/embassy-usb-dfu/src/dfu.rs b/embassy-usb-dfu/src/dfu.rs index 2ed4511ce..f552510f1 100644 --- a/embassy-usb-dfu/src/dfu.rs +++ b/embassy-usb-dfu/src/dfu.rs @@ -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, 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 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 { - 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> { - 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>; + +/// 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); } diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index e9f4278b6..b2ef786ff 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -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; diff --git a/embassy-usb/Cargo.toml b/embassy-usb/Cargo.toml index 6c3ddc1db..d413f170a 100644 --- a/embassy-usb/Cargo.toml +++ b/embassy-usb/Cargo.toml @@ -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 } diff --git a/embassy-usb/src/class/dfu/app_mode.rs b/embassy-usb/src/class/dfu/app_mode.rs new file mode 100644 index 000000000..bb26caa19 --- /dev/null +++ b/embassy-usb/src/class/dfu/app_mode.rs @@ -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 { + handler: H, + state: State, + attrs: DfuAttributes, + detach_start: Option, + timeout: Duration, +} + +impl DfuState { + /// 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 crate::Handler for DfuState { + 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 { + 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> { + 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, + 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); +} diff --git a/embassy-usb/src/class/dfu/consts.rs b/embassy-usb/src/class/dfu/consts.rs new file mode 100644 index 000000000..b9f8c91d3 --- /dev/null +++ b/embassy-usb/src/class/dfu/consts.rs @@ -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 for Request { + type Error = (); + + fn try_from(value: u8) -> Result { + 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(()), + } + } +} diff --git a/embassy-usb/src/class/dfu/dfu_mode.rs b/embassy-usb/src/class/dfu/dfu_mode.rs new file mode 100644 index 000000000..249ae14b9 --- /dev/null +++ b/embassy-usb/src/class/dfu/dfu_mode.rs @@ -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 { + handler: H, + attrs: DfuAttributes, + state: State, + status: Status, + next_block_num: usize, +} + +impl<'d, H: Handler> DfuState { + /// 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 crate::Handler for DfuState { + 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 { + 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> { + 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, + 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); +} diff --git a/embassy-usb/src/class/dfu/mod.rs b/embassy-usb/src/class/dfu/mod.rs new file mode 100644 index 000000000..76a46a96e --- /dev/null +++ b/embassy-usb/src/class/dfu/mod.rs @@ -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; diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index c01707971..bdf86745c 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -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; diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index d1fc3563b..98860c890 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -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" diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs index 1ae28bf3a..f7a594fed 100644 --- a/examples/boot/application/stm32wb-dfu/src/main.rs +++ b/examples/boot/application/stm32wb-dfu/src/main.rs @@ -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 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", "")); diff --git a/examples/boot/application/stm32wba-dfu/Cargo.toml b/examples/boot/application/stm32wba-dfu/Cargo.toml index 06d0b0d58..a95b1a746 100644 --- a/examples/boot/application/stm32wba-dfu/Cargo.toml +++ b/examples/boot/application/stm32wba-dfu/Cargo.toml @@ -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" diff --git a/examples/boot/application/stm32wba-dfu/src/main.rs b/examples/boot/application/stm32wba-dfu/src/main.rs index 8adb2e7c0..5ac64b874 100644 --- a/examples/boot/application/stm32wba-dfu/src/main.rs +++ b/examples/boot/application/stm32wba-dfu/src/main.rs @@ -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 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", "")); diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs index 9ee82846d..79aacdeb1 100644 --- a/examples/boot/bootloader/stm32wb-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -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; @@ -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, diff --git a/examples/boot/bootloader/stm32wba-dfu/src/main.rs b/examples/boot/bootloader/stm32wba-dfu/src/main.rs index b33a75d95..c3d828e70 100644 --- a/examples/boot/bootloader/stm32wba-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wba-dfu/src/main.rs @@ -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; @@ -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,