dfu_core/
lib.rs

1//! Sans IO core library (traits and tools) for DFU.
2#![no_std]
3#![warn(missing_docs)]
4#![allow(clippy::type_complexity)]
5#![cfg_attr(docsrs, feature(doc_cfg))]
6
7#[cfg(any(feature = "std", test))]
8#[macro_use]
9extern crate std;
10
11/// Generic asynchronous implementation.
12#[cfg(feature = "async")]
13#[cfg_attr(docsrs, doc(cfg(feature = "async")))]
14pub mod asynchronous;
15/// Commands to detach the device.
16pub mod detach;
17/// Commands to download a firmware into the device.
18pub mod download;
19/// Functional descriptor.
20pub mod functional_descriptor;
21/// Commands to get the status of the device.
22pub mod get_status;
23/// Memory layout.
24pub mod memory_layout;
25/// Generic synchronous implementation.
26#[cfg(any(feature = "std", test))]
27#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
28pub mod sync;
29
30use core::convert::TryFrom;
31
32use displaydoc::Display;
33use functional_descriptor::FunctionalDescriptor;
34#[cfg(any(feature = "std", test))]
35use thiserror::Error;
36
37#[derive(Debug, Display)]
38#[cfg_attr(any(feature = "std", test), derive(Error))]
39#[allow(missing_docs)]
40#[non_exhaustive]
41pub enum Error {
42    /// The size of the data being transferred exceeds the DFU capabilities.
43    OutOfCapabilities,
44    /// The device is in an invalid state (got: {got:?}, expected: {expected:?}).
45    InvalidState { got: State, expected: State },
46    /// Buffer size exceeds the maximum allowed.
47    BufferTooBig { got: usize, expected: usize },
48    /// Maximum transfer size exceeded.
49    MaximumTransferSizeExceeded,
50    /// Erasing limit reached.
51    EraseLimitReached,
52    /// Not enough space on device.
53    NoSpaceLeft,
54    /// Unrecognized status code: {0}
55    UnrecognizedStatusCode(u8),
56    /// Unrecognized state code: {0}
57    UnrecognizedStateCode(u8),
58    /// Device response is too short (got: {got:?}, expected: {expected:?}).
59    ResponseTooShort { got: usize, expected: usize },
60    /// Device status is in error: {0}
61    StatusError(Status),
62    /// Device state is in error: {0}
63    StateError(State),
64    /// Unknown DFU protocol
65    UnknownProtocol,
66    /// Failed to parse dfuse interface string
67    InvalidInterfaceString,
68    /// Failed to parse dfuse address from interface string
69    #[cfg(any(feature = "std", test))]
70    MemoryLayout(memory_layout::Error),
71    /// Failed to parse dfuse address from interface string
72    InvalidAddress,
73}
74
75/// Trait to implement lower level communication with a USB device.
76pub trait DfuIo {
77    /// Return type after calling [`Self::read_control`].
78    type Read;
79    /// Return type after calling [`Self::write_control`].
80    type Write;
81    /// Return type after calling [`Self::usb_reset`].
82    type Reset;
83    /// Error type.
84    type Error: From<Error>;
85    /// Dfuse Memory layout type
86    type MemoryLayout: AsRef<memory_layout::mem>;
87
88    /// Read data using control transfer.
89    fn read_control(
90        &self,
91        request_type: u8,
92        request: u8,
93        value: u16,
94        buffer: &mut [u8],
95    ) -> Result<Self::Read, Self::Error>;
96
97    /// Write data using control transfer.
98    fn write_control(
99        &self,
100        request_type: u8,
101        request: u8,
102        value: u16,
103        buffer: &[u8],
104    ) -> Result<Self::Write, Self::Error>;
105
106    /// Triggers a USB reset.
107    fn usb_reset(&self) -> Result<Self::Reset, Self::Error>;
108
109    /// Returns the protocol of the device
110    fn protocol(&self) -> &DfuProtocol<Self::MemoryLayout>;
111
112    /// Returns the functional descriptor of the device.
113    fn functional_descriptor(&self) -> &functional_descriptor::FunctionalDescriptor;
114}
115
116/// The DFU protocol variant in use
117pub enum DfuProtocol<M> {
118    /// DFU 1.1
119    Dfu,
120    /// STM DFU extensions aka DfuSe
121    Dfuse {
122        /// Start memory address
123        address: u32,
124        /// Memory layout for flash
125        memory_layout: M,
126    },
127}
128
129#[cfg(any(feature = "std", test))]
130impl DfuProtocol<memory_layout::MemoryLayout> {
131    /// Create a DFU Protocol object from the interface string and DFU version
132    pub fn new(interface_string: &str, version: (u8, u8)) -> Result<Self, Error> {
133        match version {
134            (0x1, 0x10) => Ok(DfuProtocol::Dfu),
135            (0x1, 0x1a) => {
136                let (rest, memory_layout) = interface_string
137                    .rsplit_once('/')
138                    .ok_or(Error::InvalidInterfaceString)?;
139                let memory_layout = memory_layout::MemoryLayout::try_from(memory_layout)
140                    .map_err(Error::MemoryLayout)?;
141                let (_rest, address) =
142                    rest.rsplit_once('/').ok_or(Error::InvalidInterfaceString)?;
143                let address = address
144                    .strip_prefix("0x")
145                    .and_then(|s| u32::from_str_radix(s, 16).ok())
146                    .ok_or(Error::InvalidAddress)?;
147                Ok(DfuProtocol::Dfuse {
148                    address,
149                    memory_layout,
150                })
151            }
152            _ => Err(Error::UnknownProtocol),
153        }
154    }
155}
156
157/// Use this struct to create state machines to make operations on the device.
158pub struct DfuSansIo {
159    descriptor: FunctionalDescriptor,
160    override_address: Option<u32>,
161}
162
163impl DfuSansIo {
164    /// Create an instance of [`DfuSansIo`].
165    pub fn new(descriptor: FunctionalDescriptor) -> Self {
166        Self {
167            descriptor,
168            override_address: None,
169        }
170    }
171
172    /// Create a state machine to download the firmware into the device.
173    pub fn download<'a, Layout>(
174        &'a self,
175        protocol: &'a DfuProtocol<Layout>,
176        length: u32,
177    ) -> Result<
178        get_status::GetStatus<get_status::ClearStatus<get_status::GetStatus<download::Start<'a>>>>,
179        Error,
180    >
181    where
182        Layout: AsRef<memory_layout::mem>,
183    {
184        let (protocol, end_pos) = match protocol {
185            DfuProtocol::Dfu => (download::ProtocolData::Dfu, length),
186            DfuProtocol::Dfuse {
187                address,
188                memory_layout,
189                ..
190            } => {
191                let address = self.override_address.unwrap_or(*address);
192                (
193                    download::ProtocolData::Dfuse(download::DfuseProtocolData {
194                        address,
195                        erased_pos: address,
196                        address_set: false,
197                        memory_layout: memory_layout.as_ref(),
198                    }),
199                    address.checked_add(length).ok_or(Error::NoSpaceLeft)?,
200                )
201            }
202        };
203
204        Ok(get_status::GetStatus {
205            chained_command: get_status::ClearStatus {
206                chained_command: get_status::GetStatus {
207                    chained_command: download::Start {
208                        descriptor: &self.descriptor,
209                        protocol,
210                        end_pos,
211                    },
212                },
213            },
214        })
215    }
216
217    /// Send a Detach request to the device
218    pub fn detach(&self) -> UsbWriteControl<[u8; 0]> {
219        const REQUEST_TYPE: u8 = 0b00100001;
220        const DFU_DETACH: u8 = 0;
221        UsbWriteControl::new(REQUEST_TYPE, DFU_DETACH, 1000, [])
222    }
223
224    /// Set the address onto which to download the firmware.
225    ///
226    /// This address is only used if the device uses the DfuSe protocol.
227    pub fn set_address(&mut self, address: u32) {
228        self.override_address = Some(address);
229    }
230}
231
232/// DFU Status.
233///
234/// Note: not the same as state!
235#[derive(Debug, Clone, Copy, Eq, PartialEq, Display)]
236pub enum Status {
237    /// No error condition is present.
238    Ok,
239    /// File is not targeted for use by this device.
240    ErrTarget,
241    /// File is for this device but fails some vendor-specific verification test.
242    ErrFile,
243    /// Device is unable to write memory.
244    ErrWrite,
245    /// Memory erase function failed.
246    ErrErase,
247    /// Memory erase check failed.
248    ErrCheckErased,
249    /// Program memory function failed.
250    ErrProg,
251    /// Programmed memory failed verification.
252    ErrVerify,
253    /// Cannot program memory due to received address that is out of range.
254    ErrAddress,
255    /// Received DFU_DNLOAD with wLength = 0, but device does not think it has all of the data yet.
256    ErrNotdone,
257    /// Device's firmware is corrupt. It cannot return to run-time (non-DFU) operations.
258    ErrFirmware,
259    /// iString indicates a vendor-specific error.
260    ErrVendor,
261    /// Device detected unexpected USB reset signaling.
262    ErrUsbr,
263    /// Device detected unexpected power on reset.
264    ErrPor,
265    /// Something went wrong, but the device does not know what it was.
266    ErrUnknown,
267    /// Device stalled an unexpected request.
268    ErrStalledpkt,
269    /// Other ({0}).
270    Other(u8),
271}
272
273impl From<u8> for Status {
274    fn from(state: u8) -> Self {
275        match state {
276            0x00 => Status::Ok,
277            0x01 => Status::ErrTarget,
278            0x02 => Status::ErrFile,
279            0x03 => Status::ErrWrite,
280            0x04 => Status::ErrErase,
281            0x05 => Status::ErrCheckErased,
282            0x06 => Status::ErrProg,
283            0x07 => Status::ErrVerify,
284            0x08 => Status::ErrAddress,
285            0x09 => Status::ErrNotdone,
286            0x0a => Status::ErrFirmware,
287            0x0b => Status::ErrVendor,
288            0x0c => Status::ErrUsbr,
289            0x0d => Status::ErrPor,
290            0x0e => Status::ErrUnknown,
291            0x0f => Status::ErrStalledpkt,
292            other => Status::Other(other),
293        }
294    }
295}
296
297impl From<Status> for u8 {
298    fn from(state: Status) -> Self {
299        match state {
300            Status::Ok => 0x00,
301            Status::ErrTarget => 0x01,
302            Status::ErrFile => 0x02,
303            Status::ErrWrite => 0x03,
304            Status::ErrErase => 0x04,
305            Status::ErrCheckErased => 0x05,
306            Status::ErrProg => 0x06,
307            Status::ErrVerify => 0x07,
308            Status::ErrAddress => 0x08,
309            Status::ErrNotdone => 0x09,
310            Status::ErrFirmware => 0x0a,
311            Status::ErrVendor => 0x0b,
312            Status::ErrUsbr => 0x0c,
313            Status::ErrPor => 0x0d,
314            Status::ErrUnknown => 0x0e,
315            Status::ErrStalledpkt => 0x0f,
316            Status::Other(other) => other,
317        }
318    }
319}
320
321/// DFU State.
322///
323/// Note: not the same as status!
324#[derive(Debug, Clone, Copy, Eq, PartialEq, Display)]
325pub enum State {
326    /// Device is running its normal application.
327    AppIdle,
328    /// Device is running its normal application, has received the DFU_DETACH request, and is waiting for a USB reset.
329    AppDetach,
330    /// Device is operating in the DFU mode and is waiting for requests.
331    DfuIdle,
332    /// Device has received a block and is waiting for the host to solicit the status via DFU_GETSTATUS.
333    DfuDnloadSync,
334    /// Device is programming a control-write block into its nonvolatile memories.
335    DfuDnbusy,
336    /// Device is processing a download operation.  Expecting DFU_DNLOAD requests.
337    DfuDnloadIdle,
338    /// Device has received the final block of firmware from the host and is waiting for receipt of DFU_GETSTATUS to begin the Manifestation phase; or device has completed the Manifestation phase and is waiting for receipt of DFU_GETSTATUS.  (Devices that can enter this state after the Manifestation phase set bmAttributes bit bitManifestationTolerant to 1.)
339    DfuManifestSync,
340    /// Device is in the Manifestation phase.  (Not all devices will be able to respond to DFU_GETSTATUS when in this state.)
341    DfuManifest,
342    /// Device has programmed its memories and is waiting for a USB reset or a power on reset.  (Devices that must enter this state clear bitManifestationTolerant to 0.)
343    DfuManifestWaitReset,
344    /// The device is processing an upload operation.  Expecting DFU_UPLOAD requests.
345    DfuUploadIdle,
346    /// An error has occurred. Awaiting the DFU_CLRSTATUS request.
347    DfuError,
348    /// Other ({0}).
349    Other(u8),
350}
351
352impl From<u8> for State {
353    fn from(state: u8) -> Self {
354        match state {
355            0 => State::AppIdle,
356            1 => State::AppDetach,
357            2 => State::DfuIdle,
358            3 => State::DfuDnloadSync,
359            4 => State::DfuDnbusy,
360            5 => State::DfuDnloadIdle,
361            6 => State::DfuManifestSync,
362            7 => State::DfuManifest,
363            8 => State::DfuManifestWaitReset,
364            9 => State::DfuUploadIdle,
365            10 => State::DfuError,
366            other => State::Other(other),
367        }
368    }
369}
370
371impl From<State> for u8 {
372    fn from(state: State) -> Self {
373        match state {
374            State::AppIdle => 0,
375            State::AppDetach => 1,
376            State::DfuIdle => 2,
377            State::DfuDnloadSync => 3,
378            State::DfuDnbusy => 4,
379            State::DfuDnloadIdle => 5,
380            State::DfuManifestSync => 6,
381            State::DfuManifest => 7,
382            State::DfuManifestWaitReset => 8,
383            State::DfuUploadIdle => 9,
384            State::DfuError => 10,
385            State::Other(other) => other,
386        }
387    }
388}
389
390impl State {
391    // Not all possible state are, according to the spec, possible in the GetStatus result.. As
392    // that's defined as the state the device will be as a result of the request, which may trigger
393    // state transitions. Ofcourse some devices get this wrong... So this does a reasonable
394    // converstion to what should have been the result...
395    fn for_status(self) -> Self {
396        match self {
397            State::DfuManifestSync => State::DfuManifest,
398            State::DfuDnloadSync => State::DfuDnbusy,
399            _ => self,
400        }
401    }
402}
403
404/// A trait for commands that be chained into another.
405pub trait ChainedCommand {
406    /// Type of the argument to pass with the command for chaining.
407    type Arg;
408    /// Type of the command after being chained.
409    type Into;
410
411    /// Chain this command into another.
412    fn chain(self, arg: Self::Arg) -> Self::Into;
413}
414
415/// Usb write request
416#[must_use]
417pub struct UsbWriteControl<D> {
418    request_type: u8,
419    request: u8,
420    value: u16,
421    buffer: D,
422}
423
424impl<D> UsbWriteControl<D>
425where
426    D: AsRef<[u8]>,
427{
428    fn new(request_type: u8, request: u8, value: u16, buffer: D) -> Self {
429        Self {
430            request_type,
431            request,
432            value,
433            buffer,
434        }
435    }
436
437    /// Execute usb write using io
438    pub fn execute<IO: DfuIo>(&self, io: &IO) -> Result<IO::Write, IO::Error> {
439        io.write_control(
440            self.request_type,
441            self.request,
442            self.value,
443            self.buffer.as_ref(),
444        )
445    }
446}
447
448/// Usb read request
449#[must_use]
450pub struct UsbReadControl<'a> {
451    request_type: u8,
452    request: u8,
453    value: u16,
454    buffer: &'a mut [u8],
455}
456
457impl<'a> UsbReadControl<'a> {
458    fn new(request_type: u8, request: u8, value: u16, buffer: &'a mut [u8]) -> Self {
459        Self {
460            request_type,
461            request,
462            value,
463            buffer,
464        }
465    }
466
467    /// Execute usb write using io
468    pub fn execute<IO: DfuIo>(&mut self, io: &IO) -> Result<IO::Read, IO::Error> {
469        io.read_control(self.request_type, self.request, self.value, self.buffer)
470    }
471}
472
473#[cfg(test)]
474mod tests {
475    use super::*;
476
477    // ensure DfuIo can be made into an object
478    const _: [&dyn DfuIo<Read = (), Write = (), Reset = (), MemoryLayout = (), Error = Error>; 0] =
479        [];
480}