dfu_core/
download.rs

1use functional_descriptor::FunctionalDescriptor;
2
3use super::*;
4
5const REQUEST_TYPE: u8 = 0b00100001;
6const DFU_DNLOAD: u8 = 1;
7
8/// Starting point to download a firmware into a device.
9#[must_use]
10pub struct Start<'dfu> {
11    pub(crate) descriptor: &'dfu FunctionalDescriptor,
12    pub(crate) end_pos: u32,
13    pub(crate) protocol: ProtocolData<'dfu>,
14}
15
16impl<'dfu> ChainedCommand for Start<'dfu> {
17    type Arg = get_status::GetStatusMessage;
18    type Into = Result<DownloadLoop<'dfu>, Error>;
19
20    fn chain(
21        self,
22        get_status::GetStatusMessage {
23            status: _,
24            poll_timeout: _,
25            state,
26            index: _,
27        }: Self::Arg,
28    ) -> Self::Into {
29        log::trace!("Starting download process");
30        // TODO startup can be in AppIdle in which case the Detach-Attach process needs to be done
31        if state == State::DfuIdle {
32            let (block_num, copied_pos) = match self.protocol {
33                ProtocolData::Dfu => (0, 0),
34                ProtocolData::Dfuse(d) => (2, d.address),
35            };
36            Ok(DownloadLoop {
37                descriptor: self.descriptor,
38                end_pos: self.end_pos,
39                copied_pos,
40                protocol: self.protocol,
41                block_num,
42                eof: false,
43            })
44        } else {
45            Err(Error::InvalidState {
46                got: state,
47                expected: State::DfuIdle,
48            })
49        }
50    }
51}
52
53#[derive(Debug, Copy, Clone)]
54pub(crate) struct DfuseProtocolData<'dfu> {
55    pub address: u32,
56    pub erased_pos: u32,
57    pub address_set: bool,
58    pub memory_layout: &'dfu memory_layout::mem,
59}
60
61#[derive(Debug, Copy, Clone)]
62pub(crate) enum ProtocolData<'dfu> {
63    Dfu,
64    Dfuse(DfuseProtocolData<'dfu>),
65}
66
67/// Download loop.
68#[must_use]
69pub struct DownloadLoop<'dfu> {
70    descriptor: &'dfu FunctionalDescriptor,
71    protocol: ProtocolData<'dfu>,
72    end_pos: u32,
73    copied_pos: u32,
74    block_num: u16,
75    eof: bool,
76}
77
78impl<'dfu> DownloadLoop<'dfu> {
79    /// Get the next step in the download loop.
80    pub fn next(self) -> Step<'dfu> {
81        if self.eof {
82            log::trace!("Download loop ended");
83
84            // If the device won't detach itself, it expects to be reset by the host as there is
85            // nothing more that can be done. Otherwise it is expected to detach by itself
86            log::trace!("Device will detach? {}", self.descriptor.will_detach);
87            return if !self.descriptor.manifestation_tolerant && !self.descriptor.will_detach {
88                Step::UsbReset
89            } else {
90                Step::Break
91            };
92        }
93
94        match self.protocol {
95            ProtocolData::Dfuse(d) if d.erased_pos < self.end_pos => {
96                log::trace!("Download loop: erase page");
97                log::trace!("Erased position: {}", d.erased_pos);
98                log::trace!("End position: {}", self.end_pos);
99                Step::Erase(ErasePage {
100                    descriptor: self.descriptor,
101                    end_pos: self.end_pos,
102                    copied_pos: self.copied_pos,
103                    protocol: d,
104                    block_num: self.block_num,
105                })
106            }
107            ProtocolData::Dfuse(d) if !d.address_set => {
108                log::trace!("Download loop: set address");
109                Step::SetAddress(SetAddress {
110                    descriptor: self.descriptor,
111                    end_pos: self.end_pos,
112                    copied_pos: self.copied_pos,
113                    protocol: d,
114                    block_num: self.block_num,
115                })
116            }
117            _ => {
118                log::trace!("Download loop: download chunk");
119                Step::DownloadChunk(DownloadChunk {
120                    descriptor: self.descriptor,
121                    end_pos: self.end_pos,
122                    copied_pos: self.copied_pos,
123                    block_num: self.block_num,
124                    protocol: self.protocol,
125                })
126            }
127        }
128    }
129}
130
131/// Download step in the loop.
132#[allow(missing_docs)]
133pub enum Step<'dfu> {
134    Break,
135    UsbReset,
136    Erase(ErasePage<'dfu>),
137    SetAddress(SetAddress<'dfu>),
138    DownloadChunk(DownloadChunk<'dfu>),
139}
140
141/// Erase a memory page.
142#[must_use]
143pub struct ErasePage<'dfu> {
144    descriptor: &'dfu FunctionalDescriptor,
145    end_pos: u32,
146    copied_pos: u32,
147    protocol: DfuseProtocolData<'dfu>,
148    block_num: u16,
149}
150
151impl<'dfu> ErasePage<'dfu> {
152    /// Erase a memory page.
153    pub fn erase(
154        self,
155    ) -> Result<
156        (
157            get_status::WaitState<DownloadLoop<'dfu>>,
158            UsbWriteControl<[u8; 5]>,
159        ),
160        crate::Error,
161    > {
162        let (&page_size, rest_memory_layout) = self
163            .protocol
164            .memory_layout
165            .split_first()
166            .ok_or(Error::NoSpaceLeft)?;
167        log::trace!("Rest of memory layout: {:?}", rest_memory_layout);
168        log::trace!("Page size: {:?}", page_size);
169
170        let next_protocol = ProtocolData::Dfuse(DfuseProtocolData {
171            erased_pos: self
172                .protocol
173                .erased_pos
174                .checked_add(page_size)
175                .ok_or(Error::EraseLimitReached)?,
176            memory_layout: rest_memory_layout,
177            ..self.protocol
178        });
179        let next = get_status::WaitState::new(
180            State::DfuDnbusy,
181            State::DfuDnloadIdle,
182            DownloadLoop {
183                descriptor: self.descriptor,
184                protocol: next_protocol,
185                end_pos: self.end_pos,
186                copied_pos: self.copied_pos,
187                block_num: self.block_num,
188                eof: false,
189            },
190        );
191
192        let control = UsbWriteControl {
193            request_type: REQUEST_TYPE,
194            request: DFU_DNLOAD,
195            value: 0,
196            buffer: <[u8; 5]>::from(DownloadCommandErase(self.protocol.erased_pos)),
197        };
198
199        Ok((next, control))
200    }
201}
202
203/// Set the address for download.
204#[must_use]
205pub struct SetAddress<'dfu> {
206    descriptor: &'dfu FunctionalDescriptor,
207    end_pos: u32,
208    copied_pos: u32,
209    protocol: DfuseProtocolData<'dfu>,
210    block_num: u16,
211}
212
213impl<'dfu> SetAddress<'dfu> {
214    /// Set the address for download.
215    pub fn set_address(
216        self,
217    ) -> (
218        get_status::WaitState<DownloadLoop<'dfu>>,
219        UsbWriteControl<[u8; 5]>,
220    ) {
221        let next_protocol = ProtocolData::Dfuse(DfuseProtocolData {
222            address_set: true,
223            ..self.protocol
224        });
225
226        let next = get_status::WaitState::new(
227            State::DfuDnbusy,
228            State::DfuDnloadIdle,
229            DownloadLoop {
230                descriptor: self.descriptor,
231                end_pos: self.end_pos,
232                copied_pos: self.copied_pos,
233                protocol: next_protocol,
234                block_num: self.block_num,
235                eof: false,
236            },
237        );
238        let control = UsbWriteControl::new(
239            REQUEST_TYPE,
240            DFU_DNLOAD,
241            0,
242            <[u8; 5]>::from(DownloadCommandSetAddress(self.copied_pos)),
243        );
244
245        (next, control)
246    }
247}
248
249/// Download a chunk of data into the device.
250#[must_use]
251pub struct DownloadChunk<'dfu> {
252    descriptor: &'dfu FunctionalDescriptor,
253    end_pos: u32,
254    copied_pos: u32,
255    block_num: u16,
256    protocol: ProtocolData<'dfu>,
257}
258
259impl<'dfu> DownloadChunk<'dfu> {
260    /// Download a chunk of data into the device.
261    pub fn download<'data>(
262        self,
263        bytes: &'data [u8],
264    ) -> Result<
265        (
266            get_status::WaitState<DownloadLoop<'dfu>>,
267            UsbWriteControl<&'data [u8]>,
268        ),
269        crate::Error,
270    > {
271        let transfer_size = self.descriptor.transfer_size as u32;
272        log::trace!("Transfer size: {}", transfer_size);
273        let len = u32::try_from(bytes.len())
274            .map_err(|_| Error::BufferTooBig {
275                got: bytes.len(),
276                expected: u32::MAX as usize,
277            })?
278            .min(transfer_size);
279        log::trace!("Chunk length: {}", len);
280        log::trace!("Copied position: {}", self.copied_pos);
281        log::trace!("Block number: {}", self.block_num);
282
283        let (intermediate, next_state) = if bytes.is_empty() {
284            if self.descriptor.manifestation_tolerant {
285                (State::DfuManifest, State::DfuIdle)
286            } else {
287                (State::DfuManifest, State::DfuManifest)
288            }
289        } else {
290            (State::DfuDnbusy, State::DfuDnloadIdle)
291        };
292
293        let next = get_status::WaitState::new(
294            intermediate,
295            next_state,
296            DownloadLoop {
297                descriptor: self.descriptor,
298                end_pos: self.end_pos,
299                copied_pos: self
300                    .copied_pos
301                    .checked_add(len)
302                    .ok_or(Error::MaximumTransferSizeExceeded)?,
303                protocol: self.protocol,
304                block_num: self.block_num.wrapping_add(1),
305                eof: bytes.is_empty(),
306            },
307        );
308        let control = UsbWriteControl {
309            request_type: REQUEST_TYPE,
310            request: DFU_DNLOAD,
311            value: self.block_num,
312            buffer: &bytes[..len as usize],
313        };
314
315        Ok((next, control))
316    }
317}
318
319/// Command to erase.
320#[derive(Debug, Clone, Copy)]
321pub struct DownloadCommandErase(u32);
322
323impl From<DownloadCommandErase> for [u8; 5] {
324    fn from(command: DownloadCommandErase) -> Self {
325        let mut buffer = [0; 5];
326        buffer[0] = 0x41;
327        buffer[1..].copy_from_slice(&command.0.to_le_bytes());
328        buffer
329    }
330}
331
332/// Command to set address to download.
333#[derive(Debug, Clone, Copy)]
334pub struct DownloadCommandSetAddress(u32);
335
336impl From<DownloadCommandSetAddress> for [u8; 5] {
337    fn from(command: DownloadCommandSetAddress) -> Self {
338        let mut buffer = [0; 5];
339        buffer[0] = 0x21;
340        buffer[1..].copy_from_slice(&command.0.to_le_bytes());
341        buffer
342    }
343}