1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
#![no_std]
#![deny(missing_docs)]

//! Implementation of the USB DFU run-time class
//!
//! # DFU run-time class
//!
//! DFU stands for Device Firmware Upgrade. DFU defines two USB classes:
//! * DFU mode: used to transfer new firmware and flash the device
//! * Run-time: advertises DFU capability and can be used to change device mode to DFU upgrade
//!
//! This crate implements DFU run-time class according to the USB DFU class specification version
//! 1.1a. It implements _only_ the run-time class, which means that it only implements DFU_DETACH
//! request. This request means that the device should switch to DFU mode in preparation for
//! firmware upgrade. This usually means rebooting to a DFU-capable bootloader which then handles
//! the upgrade.
//!
//! # Usage
//!
//! To use this class user must provide a callback that will perform the transition to DFU mode,
//! which is highly device-specific. Application specific behaviour is defined by implementing
//! [`DfuRuntimeOps`].
//!
//! 1. Define type that will implement [`DfuRuntimeOps`] and hold any additional state.
//! 2. Set `const` settings in [`DfuRuntimeOps`] or use the defaults.
//! 3. Implement [`DfuRuntimeOps::detach`] and optionally [`DfuRuntimeOps::allow`].
//! 4. Call [`DfuRuntimeClass::tick`] regularly.
//!
//! [`DfuRuntimeOps::allow`] is called when DFU_DETACH request is received and can be used
//! to reject it or change the timeout value.
//!
//! Depending on the value of [`DfuRuntimeOps::WILL_DETACH`], [`DfuRuntimeOps::detach`] is called
//! differently. With `WILL_DETACH=false` this class waits until USB reset from host is detected,
//! and then it calls [`DfuRuntimeOps::detach`].
//!
//! With `WILL_DETACH=true`, `timeout` returned from [`DfuRuntimeOps::allow`] is used to wait
//! before a call to [`DfuRuntimeOps::detach`]. This is usually wanted because the device should
//! be able to respond to the detach request with an accept. Otherwise host may see detach errors.
//! Because application often needs to perform some cleanup (disable peripherals, etc.), this
//! timeout mechanism can be used to simplify user logic: in [`DfuRuntimeOps::allow`] return
//! the time needed for application cleanup and start it, and when [`DfuRuntimeOps::detach`] is
//! called the application can simply switch to DFU mode. For more complex logic, [`DfuRuntimeOps`]
//! can store some state during [`DfuRuntimeOps::allow`] and perform the detaching in custom way
//! while ignoring [`DfuRuntimeOps::detach`]
//!
//! # Example
//!
//! Some MCUs may come with an embedded DFU bootloader firmware. This may be used to implement full
//! firmware update via USB with minimal effort - we only need to implement DFU run-time class, and
//! DFU mode is implemented in the embedded bootloader. This is e.g. the case for the STM32F072
//! MCU.
//!
//! On STM32F072, before jumping to the embedded bootloader, we should disable all peripherals,
//! setting them to the reset state. This might be problematic to do in our application, but it is
//! possible to just perform a CPU reset, which will also reset the peripherals and then jump to
//! the embedded bootloader. For this to work, we need a way for the firmware to detect that a
//! reset occured because we wanted to jump to DFU bootloader. This can be done by storing a magic
//! value in the memory and checking it just after reset.
//!
//! The following code could be used to implement the logic described above. `detach` will be called
//! on a DFU_DETACH request, setting the magic value and resetting the MCU. The `jump_bootloader`
//! routine will be executed before any code that initializes RAM (due to the
//! `#[cortex_m_rt::pre_init]` attribute), so the magic value will still be storing the value
//! written before reset. It is then checked to see if we should perform a jump to the embedded
//! bootloader. Make sure to call [`DfuRuntimeClass::tick`] in the main loop.
//!
//! ```no_run
//! use core::mem::MaybeUninit;
//! use cortex_m_rt;
//! use usbd_dfu_rt::DfuRuntimeOps;
//!
//! const MAGIC_JUMP_BOOTLOADER: u32 = 0xdeadbeef;
//! const SYSTEM_MEMORY_BASE: u32 = 0x1fffc800;
//!
//! #[link_section = ".uninit.MAGIC"]
//! static mut MAGIC: MaybeUninit<u32> = MaybeUninit::uninit();
//!
//! #[cortex_m_rt::pre_init]
//! unsafe fn jump_bootloader() {
//!     if MAGIC.assume_init() == MAGIC_JUMP_BOOTLOADER {
//!         // reset the magic value not to jump again
//!         MAGIC.as_mut_ptr().write(0);
//!         // jump to bootloader located in System Memory
//!         cortex_m::asm::bootload(SYSTEM_MEMORY_BASE as *const u32);
//!     }
//! }
//!
//! pub struct DFUBootloader;
//!
//! impl DfuRuntimeOps for DFUBootloader {
//!     fn detach(&mut self) {
//!         unsafe { MAGIC.as_mut_ptr().write(MAGIC_JUMP_BOOTLOADER); }
//!         cortex_m::peripheral::SCB::sys_reset();
//!     }
//! }
//!
//! // Remember to call .tick() regularly!
//! ```

/// DFU runtime class
pub mod class;

#[doc(inline)]
pub use crate::class::{DfuRuntimeClass, DfuRuntimeOps};