Skip to main content

dfu_rs/
lib.rs

1// Copyright (C) 2025 Piers Finlayson <piers@piers.rocks>
2//
3// MIT License
4
5//! Implements DFU operations for USB devices.
6//!
7//! Based on the [`nusb`](https://docs.rs/nusb/latest/nusb/) native Rust stack
8//! for USB communication, this crate provides a simple interface for
9//! discovering DFU-capable devices and performing DFU operations.
10//!
11//! It is designed for use in host applications that need to update firmware
12//! on embedded devices via USB DFU.
13//!
14//! It is intended to work on Windows, Linux, and macOS.
15//!
16//! On Windows, WinUSB must be installed for the target device.  This can be
17//! done via [Zadig](https://zadig.akeo.ie/), implementing a WCID descriptor
18//! in your device, or using [`wdi-rs`](https://docs.rs/wdi-rs/latest/wdi_rs/)
19//! to install the WinUSB driver programmatically (requires elevation).
20//!
21//! # Features
22//!
23//! - Upload (read) data from DFU devices
24//! - Download (write) data to DFU devices
25//! - Erase flash memory (page-wise and mass erase)
26//! - Device discovery with filtering by DFU type (e.g. internal flash)
27//! - Error handling with detailed DFU and USB errors
28//! - Async API
29//! - Customizable USB timeout
30//!
31//! # Usage
32//!
33//! ```no_run
34//! use dfu_rs::{DEFAULT_USB_TIMEOUT, DfuType, search_for_dfu};
35//!
36//! # async fn example() -> Result<(), dfu_rs::Error> {
37//! if let Some(device) = search_for_dfu(DEFAULT_USB_TIMEOUT, None).await?.first() {
38//!     // Read the first 16KB from the device
39//!     let data = device.upload(0x08000000, 16 * 1024).await?;
40//!     println!("Uploaded data: {:X?}", &data[..16]); // Print first 16 bytes
41//! }
42//! # Ok(())
43//! # }
44//! ```
45//!
46//! See [`Device`] for more examples.
47
48use async_io::Timer;
49#[allow(unused_imports)]
50use log::{debug, error, info, trace, warn};
51use nusb::transfer::{ControlIn, ControlOut, ControlType, Recipient, TransferError};
52use nusb::{Device as NusbDevice, DeviceInfo as NusbDeviceInfo, Error as NusbError, Interface};
53use std::time::Duration;
54
55// Timeout
56pub const DEFAULT_USB_TIMEOUT: Duration = Duration::from_secs(30);
57
58// USB class/subclass codes for DFU
59const USB_CLASS_APPLICATION_SPECIFIC: u8 = 0xFE;
60const USB_SUBCLASS_DFU: u8 = 0x01;
61
62// DFU block size
63const DFU_BLOCK_SIZE: usize = 2048;
64
65// STM32 DFU commands (vendor-specific)
66const STM32_DFU_CMD_SET_ADDRESS: u8 = 0x21;
67const STM32_DFU_CMD_ERASE: u8 = 0x41;
68#[allow(dead_code)]
69const STM32_DFU_CMD_READ_UNPROTECT: u8 = 0x92;
70
71// Language ID for string descriptors
72const LANGUAGE_ID: u16 = 0x0409; // English (United States)
73
74// DFU request types
75#[derive(Debug, Clone, PartialEq)]
76#[repr(u8)]
77#[allow(dead_code)]
78enum Request {
79    Detach = 0,
80    Download = 1,
81    Upload = 2,
82    GetStatus = 3,
83    ClearStatus = 4,
84    GetState = 5,
85    Abort = 6,
86}
87
88impl From<Request> for u8 {
89    fn from(val: Request) -> Self {
90        val as u8
91    }
92}
93
94impl Request {
95    // Returns expected length of the request's data phase where known
96    const fn fixed_length(&self) -> usize {
97        match self {
98            Request::Detach => 0,
99            Request::Download => 0,
100            Request::Upload => 0,
101            Request::GetStatus => 6,
102            Request::ClearStatus => 0,
103            Request::GetState => 1,
104            Request::Abort => 0,
105        }
106    }
107}
108
109/// DFU Status codes
110#[derive(Debug, Clone, PartialEq)]
111#[repr(u8)]
112pub enum Status {
113    Ok = 0,
114    ErrTarget = 1,
115    ErrFile = 2,
116    ErrWrite = 3,
117    ErrErase = 4,
118    ErrCheckErased = 5,
119    ErrProg = 6,
120    ErrVerify = 7,
121    ErrAddress = 8,
122    ErrNotDone = 9,
123    ErrFirmware = 10,
124    ErrVendor = 11,
125    ErrUsbReset = 12,
126    ErrPowerOnReset = 13,
127    ErrUnknown = 14,
128    ErrStalledPkt = 15,
129}
130
131impl std::fmt::Display for Status {
132    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
133        match self {
134            Status::Ok => write!(f, "OK"),
135            Status::ErrTarget => write!(f, "Error: Target"),
136            Status::ErrFile => write!(f, "Error: File"),
137            Status::ErrWrite => write!(f, "Error: Write"),
138            Status::ErrErase => write!(f, "Error: Erase"),
139            Status::ErrCheckErased => write!(f, "Error: Check Erased"),
140            Status::ErrProg => write!(f, "Error: Program"),
141            Status::ErrVerify => write!(f, "Error: Verify"),
142            Status::ErrAddress => write!(f, "Error: Address"),
143            Status::ErrNotDone => write!(f, "Error: Not Done"),
144            Status::ErrFirmware => write!(f, "Error: Firmware"),
145            Status::ErrVendor => write!(f, "Error: Vendor"),
146            Status::ErrUsbReset => write!(f, "Error: USB Reset"),
147            Status::ErrPowerOnReset => write!(f, "Error: Power On Reset"),
148            Status::ErrUnknown => write!(f, "Error: Unknown"),
149            Status::ErrStalledPkt => write!(f, "Error: Stalled Packet"),
150        }
151    }
152}
153
154/// DFU Device State codes
155#[derive(Debug, Clone, PartialEq)]
156#[repr(u8)]
157pub enum State {
158    AppIdle = 0,
159    AppDetach = 1,
160    DfuIdle = 2,
161    DfuDnloadSync = 3,
162    DfuDnloadBusy = 4,
163    DfuDnloadIdle = 5,
164    DfuManifestSync = 6,
165    DfuManifest = 7,
166    DfuManifestWaitReset = 8,
167    DfuUploadIdle = 9,
168    DfuError = 10,
169}
170
171impl std::fmt::Display for State {
172    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
173        match self {
174            State::AppIdle => write!(f, "App Idle"),
175            State::AppDetach => write!(f, "App Detach"),
176            State::DfuIdle => write!(f, "DFU Idle"),
177            State::DfuDnloadSync => write!(f, "DFU Download Sync"),
178            State::DfuDnloadBusy => write!(f, "DFU Download Busy"),
179            State::DfuDnloadIdle => write!(f, "DFU Download Idle"),
180            State::DfuManifestSync => write!(f, "DFU Manifest Sync"),
181            State::DfuManifest => write!(f, "DFU Manifest"),
182            State::DfuManifestWaitReset => write!(f, "DFU Manifest Wait Reset"),
183            State::DfuUploadIdle => write!(f, "DFU Upload Idle"),
184            State::DfuError => write!(f, "DFU Error"),
185        }
186    }
187}
188
189// Status returned by the DFU device
190struct DeviceStatus {
191    status: Status,
192    state: State,
193    poll_time: u32,
194    string: u8,
195}
196
197#[allow(dead_code)]
198impl DeviceStatus {
199    fn status(&self) -> Status {
200        self.status.clone()
201    }
202
203    fn state(&self) -> State {
204        self.state.clone()
205    }
206
207    fn poll_time(&self) -> u32 {
208        self.poll_time
209    }
210
211    fn string_index(&self) -> u8 {
212        self.string
213    }
214
215    fn is_error(&self) -> bool {
216        self.is_status_error() || self.is_state_error()
217    }
218
219    fn is_status_error(&self) -> bool {
220        self.status != Status::Ok
221    }
222
223    fn is_state_error(&self) -> bool {
224        self.state == State::DfuError
225    }
226
227    fn is_state_dfu_idle(&self) -> bool {
228        self.state == State::DfuIdle
229    }
230
231    fn is_download_busy(&self) -> bool {
232        self.state == State::DfuDnloadBusy
233    }
234
235    fn is_download_manifest(&self) -> bool {
236        self.state == State::DfuManifest || self.state == State::DfuManifestSync
237    }
238
239    fn from_packet(data: &[u8]) -> Result<Self, Error> {
240        if data.len() < Request::GetStatus.fixed_length() {
241            warn!("Invalid DFU device status packet length: {}", data.len());
242            return Err(Error::DfuInvalidDeviceStatus);
243        }
244
245        let status = match data[0] {
246            0 => Status::Ok,
247            1 => Status::ErrTarget,
248            2 => Status::ErrFile,
249            3 => Status::ErrWrite,
250            4 => Status::ErrErase,
251            5 => Status::ErrCheckErased,
252            6 => Status::ErrProg,
253            7 => Status::ErrVerify,
254            8 => Status::ErrAddress,
255            9 => Status::ErrNotDone,
256            10 => Status::ErrFirmware,
257            11 => Status::ErrVendor,
258            12 => Status::ErrUsbReset,
259            13 => Status::ErrPowerOnReset,
260            14 => Status::ErrUnknown,
261            15 => Status::ErrStalledPkt,
262            _ => {
263                warn!("Unknown DFU status code: {}", data[0]);
264                return Err(Error::DfuInvalidDeviceStatus);
265            }
266        };
267
268        let state = match data[4] {
269            0 => State::AppIdle,
270            1 => State::AppDetach,
271            2 => State::DfuIdle,
272            3 => State::DfuDnloadSync,
273            4 => State::DfuDnloadBusy,
274            5 => State::DfuDnloadIdle,
275            6 => State::DfuManifestSync,
276            7 => State::DfuManifest,
277            8 => State::DfuManifestWaitReset,
278            9 => State::DfuUploadIdle,
279            10 => State::DfuError,
280            _ => {
281                warn!("Unknown DFU state code: {}", data[4]);
282                return Err(Error::DfuInvalidDeviceStatus);
283            }
284        };
285
286        let poll_time = u32::from_le_bytes([data[1], data[2], data[3], 0]);
287        let string = data[5];
288
289        trace!(
290            "Device Status: status={}, state={}, poll_time={}ms, string={}",
291            status, state, poll_time, string
292        );
293
294        Ok(DeviceStatus {
295            status,
296            state,
297            poll_time,
298            string,
299        })
300    }
301}
302
303/// DFU Type enumeration - each USB DFU device can represent different memory
304/// regions, represented by this object
305#[derive(Debug, Clone, PartialEq)]
306pub enum DfuType {
307    InternalFlash,
308    OptionBytes,
309    SystemMemory,
310    Unknown(String),
311}
312
313impl std::fmt::Display for DfuType {
314    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
315        match self {
316            DfuType::InternalFlash => write!(f, "Internal Flash"),
317            DfuType::OptionBytes => write!(f, "Option Bytes"),
318            DfuType::SystemMemory => write!(f, "System Memory"),
319            DfuType::Unknown(desc) => write!(f, "Unknown ({})", desc),
320        }
321    }
322}
323
324/// USB device information
325#[derive(Debug, Clone, PartialEq)]
326pub struct DeviceInfo {
327    /// USB device vendor ID
328    pub vid: u16,
329    /// USB device product ID
330    pub pid: u16,
331    /// USB bus ID
332    pub bus: String,
333    /// USB device address
334    pub address: u8,
335    /// DFU information
336    pub dfu: DfuInfo,
337}
338
339impl std::fmt::Display for DeviceInfo {
340    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
341        write!(f, "{:0>4X}:{:0>4X}", self.vid, self.pid)
342    }
343}
344
345impl DeviceInfo {
346    pub fn from_nusb(info: &NusbDeviceInfo, dfu: DfuInfo) -> Self {
347        let vid = info.vendor_id();
348        let pid = info.product_id();
349        let bus = info.bus_id().to_string();
350        let address = info.device_address();
351
352        DeviceInfo {
353            vid,
354            pid,
355            bus,
356            address,
357            dfu,
358        }
359    }
360
361    pub fn dfu_type(&self) -> &DfuType {
362        &self.dfu.dfu_type
363    }
364
365    pub fn is_dfu_type(&self, dfu_type: &DfuType) -> bool {
366        self.dfu.dfu_type == *dfu_type
367    }
368
369    pub fn interface(&self) -> u8 {
370        self.dfu.interface
371    }
372
373    pub fn vid(&self) -> u16 {
374        self.vid
375    }
376
377    pub fn pid(&self) -> u16 {
378        self.pid
379    }
380}
381
382/// Information about a DFU interface
383#[derive(Debug, Clone, PartialEq)]
384pub struct DfuInfo {
385    /// USB interface number
386    pub interface: u8,
387    /// USB alternate setting
388    pub alt: u8,
389    /// Device description string
390    pub desc: String,
391    /// DFU Type, decoded via USB description string
392    pub dfu_type: DfuType,
393}
394
395impl std::fmt::Display for DfuInfo {
396    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
397        write!(
398            f,
399            "Interface {}, Alt {}, Type: {}, Desc: {}",
400            self.interface, self.alt, self.dfu_type, self.desc
401        )
402    }
403}
404
405/// Error type
406///
407/// Many of the errors are wrappers around [`nusb::Error`] and
408/// [`nusb::transfer::TransferError`], and are often
409/// somewhat esoteric.  [`Error::usb_stack_error()`] can be used to retrieve
410/// the underlying USB stack error for further analysis, or test whether the
411/// failure was a USB stack one.
412#[derive(Debug, Clone)]
413pub enum Error {
414    /// DFU Device not found
415    DeviceNotFound,
416    /// DFU status error returned by device
417    DfuStatus {
418        status: Status,
419        state: State,
420    },
421    /// Invalid DFU device status response
422    DfuInvalidDeviceStatus,
423    /// DFU set address pointer failed
424    DfuSetAddressFailed(Status, State),
425    UsbContext(NusbError),
426    UsbDeviceEnumeration(NusbError),
427    UsbDeviceOpen(NusbError),
428    UsbKernelDriverDetach(NusbError),
429    UsbClaimInterface(NusbError),
430    UsbSetAltSetting(NusbError),
431    UsbControlTransfer(TransferError),
432}
433
434impl std::fmt::Display for Error {
435    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
436        match self {
437            Error::DeviceNotFound => write!(f, "DFU Device Not Found"),
438            Error::DfuStatus { status, state } => write!(
439                f,
440                "DFU Status Error: status code {}, state {}",
441                status, state
442            ),
443            Error::DfuInvalidDeviceStatus => write!(f, "DFU Invalid Device Status"),
444            Error::DfuSetAddressFailed(status, state) => write!(
445                f,
446                "DFU Set Address Failed: status code {}, state {}",
447                status, state
448            ),
449            Error::UsbContext(e) => write!(f, "USB Context Error: {}", e),
450            Error::UsbDeviceEnumeration(e) => write!(f, "USB Device Enumeration Error: {}", e),
451            Error::UsbDeviceOpen(e) => write!(f, "Device Open Error: {}", e),
452            Error::UsbKernelDriverDetach(e) => write!(f, "Kernel Driver Detach Error: {}", e),
453            Error::UsbClaimInterface(e) => write!(f, "Claim Interface Error: {}", e),
454            Error::UsbSetAltSetting(e) => write!(f, "Set Alternate Setting Error: {}", e),
455            Error::UsbControlTransfer(e) => write!(f, "Control Transfer Error: {}", e),
456        }
457    }
458}
459
460#[derive(Debug, Clone)]
461pub enum UsbStackError {
462    Nusb(NusbError),
463    Transfer(TransferError),
464}
465
466impl Error {
467    /// Returns the underlying USB stack error if applicable.
468    pub fn usb_stack_error(&self) -> Option<UsbStackError> {
469        match self {
470            Error::UsbContext(e) => Some(UsbStackError::Nusb(e.clone())),
471            Error::UsbDeviceEnumeration(e) => Some(UsbStackError::Nusb(e.clone())),
472            Error::UsbDeviceOpen(e) => Some(UsbStackError::Nusb(e.clone())),
473            Error::UsbKernelDriverDetach(e) => Some(UsbStackError::Nusb(e.clone())),
474            Error::UsbClaimInterface(e) => Some(UsbStackError::Nusb(e.clone())),
475            Error::UsbSetAltSetting(e) => Some(UsbStackError::Nusb(e.clone())),
476            Error::UsbControlTransfer(e) => Some(UsbStackError::Transfer(*e)),
477            _ => None,
478        }
479    }
480}
481
482// Handle object to abstract away Windows + Unix like OS differences
483#[derive(Debug)]
484#[allow(dead_code)]
485struct Handle {
486    device: NusbDevice,
487    interface: Interface,
488}
489
490/// DFU Device representation
491///
492/// Used to search for DFU-capable devices, hold their information, and perform
493/// DFU operations.
494///
495/// Create a DFU device by calling [`search_for_dfu()`], which returns a list
496/// of found DFU devices.
497///
498/// Example:
499/// ```no_run
500/// use dfu_rs::{DEFAULT_USB_TIMEOUT, DfuType, search_for_dfu};
501///
502/// # async fn example() -> Result<(), dfu_rs::Error> {
503/// // Search for all DFU devices
504/// let devices = search_for_dfu(DEFAULT_USB_TIMEOUT, None).await?;
505/// for device in devices {
506///     println!("Found DFU Device: {}", device);
507/// }
508///
509/// // Search for only Internal Flash DFU devices
510/// let flash_devices =
511///     search_for_dfu(DEFAULT_USB_TIMEOUT, Some(DfuType::InternalFlash)).await?;
512/// for device in flash_devices {
513///     println!("Found Flash DFU Device: {}", device);
514/// }
515///
516/// // Retrieve the first 16KB from the first found DFU device
517/// if let Some(device) = search_for_dfu(DEFAULT_USB_TIMEOUT, None).await?.first() {
518///     let data = device.upload(0x08000000, 16 * 1024).await?;
519///     println!("Uploaded data: {:X?}", &data[..16]); // Print first 16 bytes
520/// }
521/// # Ok(())
522/// # }
523/// ```
524#[derive(Debug, Clone)]
525pub struct Device {
526    info: DeviceInfo,
527    nusb_info: NusbDeviceInfo,
528    timeout: Duration,
529}
530
531impl PartialEq for Device {
532    fn eq(&self, other: &Self) -> bool {
533        self.info == other.info
534    }
535}
536
537impl std::fmt::Display for Device {
538    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
539        write!(
540            f,
541            "{} ({:04X}:{:04X})",
542            self.nusb_info.product_string().unwrap_or("Unknown Device"),
543            self.nusb_info.vendor_id(),
544            self.nusb_info.product_id(),
545        )
546    }
547}
548
549impl Device {
550    /// Creates a new DFU Device instance from the provided DeviceInfo.
551    ///
552    /// Users should primarily use [`search_for_dfu()`] to find and create DFU
553    /// devices.  However, this constructor is provided for cases where
554    /// [`search_for_dfu()`] doesn't return the desired device, or is
555    /// considered inefficient or otherwise insufficient.
556    ///
557    /// Arguments:
558    /// - `nusb_info`: The DeviceInfo object from nusb
559    /// - `dfu_info`: Informaton about the DFU interface of the device
560    ///
561    /// Returns:
562    /// - `Device`: The created DFU Device instance.
563    pub fn from_nusb(nusb_info: NusbDeviceInfo, dfu_info: DfuInfo) -> Self {
564        let info = DeviceInfo::from_nusb(&nusb_info, dfu_info);
565
566        Device {
567            info,
568            nusb_info,
569            timeout: DEFAULT_USB_TIMEOUT,
570        }
571    }
572
573    pub async fn from_device_info(info: DeviceInfo) -> Result<Self, Error> {
574        // Get USB devices from nusb
575        let devices = nusb::list_devices()
576            .await
577            .map_err(Error::UsbDeviceEnumeration)?;
578
579        // Find the matching device
580        let nusb_info = devices
581            .into_iter()
582            .find(|d| {
583                d.vendor_id() == info.vid
584                    && d.product_id() == info.pid
585                    && d.bus_id() == info.bus
586                    && d.device_address() == info.address
587            })
588            .ok_or(Error::DeviceNotFound)?;
589
590        Ok(Device {
591            info,
592            nusb_info,
593            timeout: DEFAULT_USB_TIMEOUT,
594        })
595    }
596
597    /// Sets the USB timeout duration for operations.
598    pub fn set_timeout(&mut self, timeout: Duration) {
599        debug!("Setting USB timeout to {:?}", timeout);
600        self.timeout = timeout;
601    }
602
603    /// Returns the device information.
604    pub fn info(&self) -> &DeviceInfo {
605        &self.info
606    }
607
608    /// Upload (retrieve) data from the device via DFU.
609    ///
610    /// Arguments:
611    /// - `address`: The starting address to read from.
612    /// - `length`: The length of data to read (in bytes).
613    ///
614    /// Returns:
615    /// - `Ok(())`: The upload was successful, and `buf` is filled
616    /// - `Err(Error)`: An error occurred during the upload process.
617    pub async fn upload(&self, address: u32, length: usize) -> Result<Vec<u8>, Error> {
618        trace!(
619            "Starting DFU upload from address 0x{:08X} for {} bytes",
620            address, length
621        );
622        let mut bytes = vec![0u8; length];
623
624        // Open and setup device
625        let handle = self.open().await?;
626        trace!("DFU device opened successfully");
627
628        // Set address pointer
629        self.set_address(&handle, address).await?;
630        trace!("DFU address set successfully");
631
632        // Abort to return to dfuIDLE before upload
633        self.abort(&handle, false).await?;
634        trace!("DFU aborted to enter dfuIDLE state");
635
636        // Calculate blocks needed
637        let total_blocks = length.div_ceil(DFU_BLOCK_SIZE);
638
639        // Read each block
640        for block in 0..total_blocks {
641            let block_data = self.read_block(&handle, block).await?;
642            let offset = block * DFU_BLOCK_SIZE;
643            let end = (offset + block_data.len()).min(length);
644            bytes[offset..end].copy_from_slice(&block_data);
645        }
646        trace!("DFU upload completed successfully");
647
648        // Final abort
649        self.abort(&handle, true).await?;
650        trace!("DFU session aborted successfully");
651
652        Ok(bytes)
653    }
654
655    /// Erases flash memory by page/sector
656    ///
657    /// Arguments:
658    /// - `address`: Starting address to erase from
659    /// - `length`: Number of bytes to erase (rounded up to page boundary)
660    /// - `page_size`: Size of each page/sector in bytes (device-specific)
661    ///
662    /// STM32F4 devices have variable sector sizes. Common values:
663    /// - STM32F0/F1: 1024-2048 bytes
664    /// - STM32F4: 16384 bytes for sectors 0-3, then 65536, then 131072
665    ///
666    /// For STM32F4 with mixed sector sizes, use the smallest sector size
667    /// and this will erase more than requested (safe but potentially slower).
668    /// Alternatively, call erase_page() directly for each specific sector.
669    pub async fn erase(&self, address: u32, length: usize, page_size: usize) -> Result<(), Error> {
670        trace!(
671            "Starting DFU erase at address 0x{:08X} for {} bytes with page size {}",
672            address, length, page_size
673        );
674
675        let handle = self.open().await?;
676        trace!("DFU device opened successfully");
677
678        // Calculate number of pages to erase
679        let total_pages = length.div_ceil(page_size);
680
681        // Erase each page
682        for page in 0..total_pages {
683            let page_address = address + (page * page_size) as u32;
684            trace!("Erasing page {} at address 0x{:08X}", page, page_address);
685            self.erase_page(&handle, page_address).await?;
686        }
687
688        trace!("DFU erase completed successfully");
689        Ok(())
690    }
691
692    /// Erases the entire flash memory
693    ///
694    /// More efficient than page-by-page erase when erasing the complete flash.
695    /// Uses the STM32 mass erase command (0x41 with 0xFF parameter).
696    pub async fn mass_erase(&self) -> Result<(), Error> {
697        trace!("Starting DFU mass erase");
698
699        let handle = self.open().await?;
700        trace!("DFU device opened successfully");
701
702        // Mass erase command: Just hte command byte
703        let cmd = vec![STM32_DFU_CMD_ERASE];
704        self.control_out(&handle, Request::Download, 0, &cmd)
705            .await?;
706
707        // Check we get a busy response
708        self.get_status_check_download_busy(&handle).await?;
709
710        // Now try again
711        self.get_status(&handle).await?;
712
713        trace!("DFU mass erase completed successfully");
714        Ok(())
715    }
716
717    /// Downloads (writes) data to flash memory
718    ///
719    /// Arguments:
720    /// - `address`: Starting address to write to
721    /// - `data`: Slice of u32 words to write
722    ///
723    /// Note: Flash must be erased before writing. Use erase() or mass_erase() first.
724    /// Data is written in 2KB blocks using the DFU download protocol.
725    pub async fn download(&self, address: u32, data: &[u8]) -> Result<(), Error> {
726        trace!(
727            "Starting DFU download to address 0x{:08X} for {} bytes",
728            address,
729            data.len()
730        );
731
732        let handle = self.open().await?;
733        trace!("DFU device opened successfully");
734
735        // Set address pointer
736        self.set_address(&handle, address).await?;
737        trace!("DFU address set successfully");
738
739        // Calculate blocks needed
740        let total_blocks = data.len().div_ceil(DFU_BLOCK_SIZE);
741
742        // Write each block
743        for block in 0..total_blocks {
744            let start = block * DFU_BLOCK_SIZE;
745            let end = (start + DFU_BLOCK_SIZE).min(data.len());
746
747            trace!("Writing block {} of {}", block + 1, total_blocks);
748            self.write_block(&handle, block, &data[start..end]).await?;
749        }
750        trace!("DFU download completed successfully");
751
752        // Send zero-length download to complete the transfer
753        self.control_out(&handle, Request::Download, 0, &[]).await?;
754
755        // Final status check to trigger manifest
756        self.get_status(&handle).await?;
757
758        trace!("DFU download session completed successfully");
759        Ok(())
760    }
761}
762
763// Private helper methods
764impl Device {
765    fn is_dfu_type(&self, dfu_type: &DfuType) -> bool {
766        self.info.is_dfu_type(dfu_type)
767    }
768
769    fn interface(&self) -> u8 {
770        self.info.interface()
771    }
772
773    async fn open(&self) -> Result<Handle, Error> {
774        trace!("Opening DFU device: {:?}", self.info);
775        let nusb_device = self.nusb_info.open().await.map_err(Error::UsbDeviceOpen)?;
776
777        // Check there is an active configuration
778        let _ = nusb_device
779            .active_configuration()
780            .map_err(|e| Error::UsbDeviceOpen(e.into()))?;
781
782        // Claim interface
783        let interface = self.info.interface();
784        let interface = nusb_device
785            .detach_and_claim_interface(interface)
786            .await
787            .map_err(Error::UsbClaimInterface)?;
788
789        // Create the "handle"
790        let handle = Handle {
791            device: nusb_device,
792            interface,
793        };
794
795        // Initialize the DFU state machine
796        let needs_clear = match self.get_status(&handle).await {
797            Ok(status) => status.is_state_error(),
798            Err(_) => true,
799        };
800
801        if needs_clear {
802            self.clear_status(&handle).await?;
803            self.get_status(&handle).await?;
804        }
805
806        // Ensure device is in dfuIDLE state for subsequent operations
807        self.abort(&handle, false).await?;
808
809        Ok(handle)
810    }
811
812    #[cfg(not(target_os = "windows"))]
813    async fn control_out(
814        &self,
815        handle: &Handle,
816        request: Request,
817        value: u16,
818        data: &[u8],
819    ) -> Result<(), Error> {
820        trace!("Sending DFU control out: {:?}", request);
821        handle
822            .device
823            .control_out(
824                ControlOut {
825                    control_type: ControlType::Class,
826                    recipient: Recipient::Interface,
827                    request: request.into(),
828                    value,
829                    index: self.interface() as u16,
830                    data,
831                },
832                self.timeout,
833            )
834            .await
835            .map_err(Error::UsbControlTransfer)
836    }
837
838    #[cfg(target_os = "windows")]
839    async fn control_out(
840        &self,
841        handle: &Handle,
842        request: Request,
843        value: u16,
844        data: &[u8],
845    ) -> Result<(), Error> {
846        trace!("Sending DFU control out: {:?}", request);
847        handle
848            .interface
849            .control_out(
850                ControlOut {
851                    control_type: ControlType::Class,
852                    recipient: Recipient::Interface,
853                    request: request.into(),
854                    value,
855                    index: self.interface() as u16,
856                    data,
857                },
858                self.timeout,
859            )
860            .await
861            .map_err(Error::UsbControlTransfer)
862    }
863
864    #[cfg(not(target_os = "windows"))]
865    async fn control_in(
866        &self,
867        handle: &Handle,
868        request: Request,
869        value: u16,
870        length: u16,
871    ) -> Result<Vec<u8>, Error> {
872        trace!("Sending DFU control in: {:?}", request);
873        handle
874            .device
875            .control_in(
876                ControlIn {
877                    control_type: ControlType::Class,
878                    recipient: Recipient::Interface,
879                    request: request.into(),
880                    value,
881                    index: self.interface() as u16,
882                    length,
883                },
884                self.timeout,
885            )
886            .await
887            .map_err(Error::UsbControlTransfer)
888    }
889
890    #[cfg(target_os = "windows")]
891    async fn control_in(
892        &self,
893        handle: &Handle,
894        request: Request,
895        value: u16,
896        length: u16,
897    ) -> Result<Vec<u8>, Error> {
898        trace!("Sending DFU control in: {:?}", request);
899        handle
900            .interface
901            .control_in(
902                ControlIn {
903                    control_type: ControlType::Class,
904                    recipient: Recipient::Interface,
905                    request: request.into(),
906                    value,
907                    index: self.interface() as u16,
908                    length,
909                },
910                self.timeout,
911            )
912            .await
913            .map_err(Error::UsbControlTransfer)
914    }
915
916    async fn clear_status(&self, handle: &Handle) -> Result<(), Error> {
917        trace!("Clearing DFU status");
918        self.control_out(handle, Request::ClearStatus, 0, &[]).await
919    }
920
921    async fn set_address(&self, handle: &Handle, address: u32) -> Result<(), Error> {
922        trace!("Setting DFU address to 0x{:08X}", address);
923        trace!("DFU info {:?}", self.info);
924
925        // Command: 0x21 followed by address in little-endian
926        let mut cmd = vec![STM32_DFU_CMD_SET_ADDRESS];
927        cmd.extend_from_slice(&address.to_le_bytes());
928
929        self.control_out(handle, Request::Download, 0, &cmd).await?;
930
931        // First get status after write address should return download busy
932        self.get_status_check_download_busy(handle).await?;
933        self.get_status(handle).await?;
934
935        Ok(())
936    }
937
938    async fn erase_page(&self, handle: &Handle, address: u32) -> Result<(), Error> {
939        trace!("Erasing page at address 0x{:08X}", address);
940
941        // Command: 0x41 followed by address in little-endian
942        let mut cmd = vec![STM32_DFU_CMD_ERASE];
943        cmd.extend_from_slice(&address.to_le_bytes());
944
945        self.control_out(handle, Request::Download, 0, &cmd).await?;
946
947        // Wait for erase to complete - can take significant time
948        self.get_status_check_download_busy(handle).await?;
949        self.get_status(handle).await?;
950
951        Ok(())
952    }
953
954    async fn abort(&self, handle: &Handle, get_status: bool) -> Result<(), Error> {
955        trace!("Sending DFU abort");
956        self.control_out(handle, Request::Abort, 0, &[]).await?;
957
958        if get_status {
959            self.get_status(handle).await?;
960        }
961
962        Ok(())
963    }
964
965    async fn read_block(&self, handle: &Handle, block: usize) -> Result<Vec<u8>, Error> {
966        trace!("Reading DFU block {}", block);
967
968        let data = self
969            .control_in(
970                handle,
971                Request::Upload,
972                (2 + block) as u16,
973                DFU_BLOCK_SIZE as u16,
974            )
975            .await?;
976
977        self.get_status(handle).await?;
978        self.get_status(handle).await?;
979
980        Ok(data)
981    }
982
983    async fn write_block(&self, handle: &Handle, block: usize, data: &[u8]) -> Result<(), Error> {
984        trace!("Writing DFU block {} ({} bytes)", block, data.len());
985
986        // Prepare 2KB block, padding with 0xFF if needed
987        let mut block_data = vec![0xFF; DFU_BLOCK_SIZE];
988        block_data[..data.len()].copy_from_slice(data);
989
990        self.control_out(handle, Request::Download, (2 + block) as u16, &block_data)
991            .await?;
992
993        // Wait for write to complete
994        self.get_status_check_download_busy(handle).await?;
995        self.get_status(handle).await?;
996
997        Ok(())
998    }
999
1000    // Returns Err(Error) if device is not in download busy state
1001    async fn get_status_check_download_busy(&self, handle: &Handle) -> Result<(), Error> {
1002        let status = self.get_status_error_flag(handle, true).await?;
1003        if !status.is_download_busy() {
1004            return Err(Error::DfuStatus {
1005                status: status.status(),
1006                state: status.state(),
1007            });
1008        }
1009        Ok(())
1010    }
1011
1012    // Returns Err(Error) if any error status/state is reported
1013    async fn get_status(&self, handle: &Handle) -> Result<DeviceStatus, Error> {
1014        self.get_status_error_flag(handle, false).await
1015    }
1016
1017    async fn get_status_error_flag(
1018        &self,
1019        handle: &Handle,
1020        error_ok: bool,
1021    ) -> Result<DeviceStatus, Error> {
1022        trace!("Getting DFU status");
1023        let data = self
1024            .control_in(
1025                handle,
1026                Request::GetStatus,
1027                0,
1028                Request::GetStatus.fixed_length() as u16,
1029            )
1030            .await?;
1031
1032        let status = DeviceStatus::from_packet(&data)?;
1033
1034        // Wait for poll time
1035        trace!("Waiting for DFU poll time: {} ms", status.poll_time());
1036        Timer::after(Duration::from_millis(status.poll_time() as u64)).await;
1037
1038        if !error_ok && status.is_error() {
1039            return Err(Error::DfuStatus {
1040                status: status.status(),
1041                state: status.state(),
1042            });
1043        }
1044
1045        Ok(status)
1046    }
1047}
1048
1049// Parses the DFU type from the USB interface description string
1050fn parse_dfu_type(desc: &str) -> DfuType {
1051    // Look for @Region Name / pattern
1052    if let Some(at_pos) = desc.find('@')
1053        && let Some(slash_pos) = desc[at_pos..].find('/')
1054    {
1055        let region = desc[at_pos + 1..at_pos + slash_pos].trim();
1056
1057        return match region {
1058            s if s.contains("Internal Flash") => DfuType::InternalFlash,
1059            s if s.contains("Option Bytes") => DfuType::OptionBytes,
1060            s if s.contains("System Memory") || s.contains("Bootloader") => DfuType::SystemMemory,
1061            _ => DfuType::Unknown(region.to_string()),
1062        };
1063    }
1064
1065    DfuType::Unknown(desc.to_string())
1066}
1067
1068// Checks a single USB device for DFU interfaces, returning any found
1069async fn check_device_for_dfu(
1070    timeout: Duration,
1071    device_info: &NusbDeviceInfo,
1072) -> Option<Vec<Device>> {
1073    // First of all check if this device has any interfaces with a DFU class/subclass
1074    let mut dfu_device = false;
1075    for interface in device_info.interfaces() {
1076        trace!("Checking {device_info:?} interface {interface:?} for DFU class/subclass");
1077        let class = interface.class();
1078        let subclass = interface.subclass();
1079        if class == USB_CLASS_APPLICATION_SPECIFIC && subclass == USB_SUBCLASS_DFU {
1080            trace!("Found DFU interface");
1081            dfu_device = true;
1082            break;
1083        }
1084    }
1085
1086    if !dfu_device {
1087        return None;
1088    }
1089
1090    // Open the device
1091    let vid = device_info.vendor_id();
1092    let pid = device_info.product_id();
1093    let device = match device_info.open().await {
1094        Ok(dev) => dev,
1095        Err(e) => {
1096            warn!("Failed to open USB device {vid:04X}:{pid:04X} for DFU interface check: {e}");
1097            return None;
1098        }
1099    };
1100
1101    // Get the active configuration
1102    let config = match device.active_configuration() {
1103        Ok(cfg) => cfg,
1104        Err(e) => {
1105            warn!("Failed to get active configuration for USB device {vid:04X}:{pid:04X}: {e}");
1106            return None;
1107        }
1108    };
1109
1110    // Iterate through all interfaces and alt settings of this config
1111    let mut results = Vec::new();
1112    for interface in config.interface_alt_settings() {
1113        let string_index = interface.string_index();
1114        if let Some(index) = string_index {
1115            // Read the interface string;
1116            let desc_str = match device
1117                .get_string_descriptor(index, LANGUAGE_ID, timeout)
1118                .await
1119            {
1120                Ok(s) => s,
1121                Err(e) => {
1122                    warn!(
1123                        "Failed to read interface string for USB device {vid:04X}:{pid:04X}: {e}"
1124                    );
1125                    "Unknown".to_string();
1126                    return None;
1127                }
1128            };
1129
1130            let dfu_type = parse_dfu_type(&desc_str);
1131            let dfu_info = DfuInfo {
1132                interface: interface.interface_number(),
1133                alt: interface.alternate_setting(),
1134                desc: desc_str,
1135                dfu_type,
1136            };
1137
1138            let device = Device::from_nusb(device_info.clone(), dfu_info);
1139            trace!("Found DFU-capable device: {device}");
1140            results.push(device);
1141        }
1142    }
1143
1144    Some(results)
1145}
1146
1147/// Enumerates the USB bus and searches for DFU-capable devices.
1148///
1149/// Arguments:
1150/// - `filter`: Optional filter to only return devices of a specific DFU type.
1151///   (such as flash)
1152///
1153/// Returns:
1154/// - `Ok(Vec<DeviceInfo>)`: A vector of found DFU devices.
1155/// - `Err(Error)`: An error occurred during USB enumeration.
1156pub async fn search_for_dfu(
1157    timeout: Duration,
1158    filter: Option<DfuType>,
1159) -> Result<Vec<Device>, Error> {
1160    // Get USB devices from nusb
1161    let devices = nusb::list_devices()
1162        .await
1163        .map_err(Error::UsbDeviceEnumeration)?;
1164
1165    // Build the DFU devices list, checking each device for DFU capability
1166    let mut dfu_devices = Vec::new();
1167    for device in devices {
1168        if let Some(info) = check_device_for_dfu(timeout, &device).await {
1169            dfu_devices.extend(info);
1170        }
1171    }
1172
1173    // If there's an optional filter, apply it now
1174    let filtered_dfu_devices = if let Some(filter) = &filter {
1175        dfu_devices
1176            .iter()
1177            .filter(|device| {
1178                trace!("Checking device {} for DFU type {:?}", device, filter);
1179                let is_match = device.is_dfu_type(filter);
1180                if is_match {
1181                    trace!("Device {} matches DFU type {:?}", device, filter);
1182                }
1183                is_match
1184            })
1185            .cloned()
1186            .collect()
1187    } else {
1188        dfu_devices
1189    };
1190
1191    Ok(filtered_dfu_devices)
1192}