2023-12-14 15:36:22 +01:00
use embassy_boot ::BlockingFirmwareState ;
2023-12-13 20:53:49 +01:00
use embassy_time ::{ Duration , Instant } ;
use embassy_usb ::control ::{ InResponse , OutResponse , Recipient , RequestType } ;
use embassy_usb ::driver ::Driver ;
use embassy_usb ::{ Builder , Handler } ;
2023-12-13 20:40:49 +01:00
use embedded_storage ::nor_flash ::NorFlash ;
2023-12-13 20:53:49 +01:00
use crate ::consts ::{
DfuAttributes , Request , State , Status , APPN_SPEC_SUBCLASS_DFU , DESC_DFU_FUNCTIONAL , DFU_PROTOCOL_RT ,
USB_CLASS_APPN_SPEC ,
} ;
2023-12-13 20:40:49 +01:00
/// Internal state for the DFU class
2023-12-14 15:36:22 +01:00
pub struct Control < ' d , STATE : NorFlash > {
firmware_state : BlockingFirmwareState < ' d , STATE > ,
2023-12-13 20:40:49 +01:00
attrs : DfuAttributes ,
state : State ,
timeout : Option < Duration > ,
detach_start : Option < Instant > ,
}
2023-12-14 15:36:22 +01:00
impl < ' d , STATE : NorFlash > Control < ' d , STATE > {
pub fn new ( firmware_state : BlockingFirmwareState < ' d , STATE > , attrs : DfuAttributes ) -> Self {
2023-12-13 20:53:49 +01:00
Control {
2023-12-14 15:36:22 +01:00
firmware_state ,
2023-12-13 20:53:49 +01:00
attrs ,
state : State ::AppIdle ,
detach_start : None ,
timeout : None ,
}
2023-12-13 20:40:49 +01:00
}
}
2023-12-14 15:36:22 +01:00
impl < ' d , STATE : NorFlash > Handler for Control < ' d , STATE > {
2023-12-13 20:40:49 +01:00
fn reset ( & mut self ) {
if let Some ( start ) = self . detach_start {
let delta = Instant ::now ( ) - start ;
let timeout = self . timeout . unwrap ( ) ;
2023-12-14 15:36:22 +01:00
trace! (
2023-12-13 20:53:49 +01:00
" Received RESET with delta = {}, timeout = {} " ,
delta . as_millis ( ) ,
timeout . as_millis ( )
) ;
2023-12-13 20:40:49 +01:00
if delta < timeout {
2023-12-14 15:36:22 +01:00
self . firmware_state
. mark_dfu ( )
. expect ( " Failed to mark DFU mode in bootloader " ) ;
2023-12-13 20:40:49 +01:00
cortex_m ::peripheral ::SCB ::sys_reset ( ) ;
}
}
}
2023-12-13 20:53:49 +01:00
fn control_out (
& mut self ,
req : embassy_usb ::control ::Request ,
_ : & [ u8 ] ,
) -> Option < embassy_usb ::control ::OutResponse > {
2023-12-13 20:40:49 +01:00
if ( req . request_type , req . recipient ) ! = ( RequestType ::Class , Recipient ::Interface ) {
return None ;
}
2023-12-14 15:36:22 +01:00
trace! ( " Received request {} " , req ) ;
2023-12-13 20:40:49 +01:00
match Request ::try_from ( req . request ) {
Ok ( Request ::Detach ) = > {
2023-12-14 15:36:22 +01:00
trace! ( " Received DETACH, awaiting USB reset " ) ;
2023-12-13 20:40:49 +01:00
self . detach_start = Some ( Instant ::now ( ) ) ;
self . timeout = Some ( Duration ::from_millis ( req . value as u64 ) ) ;
self . state = State ::AppDetach ;
Some ( OutResponse ::Accepted )
}
2023-12-13 20:53:49 +01:00
_ = > None ,
2023-12-13 20:40:49 +01:00
}
}
2023-12-13 20:53:49 +01:00
fn control_in < ' a > (
& ' a mut self ,
req : embassy_usb ::control ::Request ,
buf : & ' a mut [ u8 ] ,
) -> Option < embassy_usb ::control ::InResponse < ' a > > {
2023-12-13 20:40:49 +01:00
if ( req . request_type , req . recipient ) ! = ( RequestType ::Class , Recipient ::Interface ) {
return None ;
}
2023-12-14 15:36:22 +01:00
trace! ( " Received request {} " , req ) ;
2023-12-13 20:40:49 +01:00
match Request ::try_from ( req . request ) {
Ok ( Request ::GetStatus ) = > {
buf [ 0 .. 6 ] . copy_from_slice ( & [ Status ::Ok as u8 , 0x32 , 0x00 , 0x00 , self . state as u8 , 0x00 ] ) ;
Some ( InResponse ::Accepted ( buf ) )
}
2023-12-13 20:53:49 +01:00
_ = > None ,
2023-12-13 20:40:49 +01:00
}
}
}
/// An implementation of the USB DFU 1.1 runtime protocol
2023-12-13 20:53:49 +01:00
///
2023-12-13 20:40:49 +01:00
/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device. The USB builder can be used as normal once this is complete.
2023-12-13 20:53:49 +01:00
/// The handler is responsive to DFU GetStatus and Detach commands.
///
2023-12-13 20:40:49 +01:00
/// Once a detach command, followed by a USB reset is received by the host, a magic number will be written into the bootloader state partition to indicate that
/// it should expose a DFU device, and a software reset will be issued.
2023-12-13 20:53:49 +01:00
///
2023-12-13 20:40:49 +01:00
/// To apply USB DFU updates, the bootloader must be capable of recognizing the DFU magic and exposing a device to handle the full DFU transaction with the host.
2023-12-14 15:36:22 +01:00
pub fn usb_dfu < ' d , D : Driver < ' d > , STATE : NorFlash > (
2023-12-13 20:53:49 +01:00
builder : & mut Builder < ' d , D > ,
2023-12-14 15:36:22 +01:00
handler : & ' d mut Control < ' d , STATE > ,
2023-12-13 20:53:49 +01:00
timeout : Duration ,
) {
2023-12-13 20:40:49 +01:00
let mut func = builder . function ( 0x00 , 0x00 , 0x00 ) ;
let mut iface = func . interface ( ) ;
2023-12-13 20:53:49 +01:00
let mut alt = iface . alt_setting ( USB_CLASS_APPN_SPEC , APPN_SPEC_SUBCLASS_DFU , DFU_PROTOCOL_RT , None ) ;
2023-12-13 20:40:49 +01:00
let timeout = timeout . as_millis ( ) as u16 ;
alt . descriptor (
DESC_DFU_FUNCTIONAL ,
& [
handler . attrs . bits ( ) ,
( timeout & 0xff ) as u8 ,
( ( timeout > > 8 ) & 0xff ) as u8 ,
2023-12-13 20:53:49 +01:00
0x40 ,
0x00 , // 64B control buffer size for application side
0x10 ,
0x01 , // DFU 1.1
2023-12-13 20:40:49 +01:00
] ,
) ;
drop ( func ) ;
2023-12-13 20:53:49 +01:00
builder . handler ( handler ) ;
}