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
#![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.
//!
//! To use this class user must provide a callback that will perform the transition to DFU mode,
//! which is highly device-specific.
//!
//! # 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. `enter` 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.
//!
//! ```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 enter(&mut self) {
//!         unsafe { MAGIC.as_mut_ptr().write(MAGIC_JUMP_BOOTLOADER); }
//!         cortex_m::peripheral::SCB::sys_reset();
//!     }
//! }
//! ```

/// DFU runtime class
pub mod class;

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