This commit is contained in:
Kaitlyn Kenwell 2023-12-13 14:53:49 -05:00 committed by Dario Nieuwenhuis
parent 0b26b2d360
commit 6513d03fdf
4 changed files with 73 additions and 64 deletions

View File

@ -1,10 +1,14 @@
use embassy_boot::BlockingFirmwareUpdater; use embassy_boot::BlockingFirmwareUpdater;
use embassy_time::{Instant, Duration}; use embassy_time::{Duration, Instant};
use embassy_usb::{Handler, control::{RequestType, Recipient, OutResponse, InResponse}, Builder, driver::Driver}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
use embassy_usb::driver::Driver;
use embassy_usb::{Builder, Handler};
use embedded_storage::nor_flash::NorFlash; use embedded_storage::nor_flash::NorFlash;
use crate::consts::{DfuAttributes, Request, Status, State, USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT}; use crate::consts::{
DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT,
USB_CLASS_APPN_SPEC,
};
/// Internal state for the DFU class /// Internal state for the DFU class
pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> { pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> {
@ -17,7 +21,13 @@ pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> {
impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> { impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> {
pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self {
Control { updater, attrs, state: State::AppIdle, detach_start: None, timeout: None } Control {
updater,
attrs,
state: State::AppIdle,
detach_start: None,
timeout: None,
}
} }
} }
@ -27,7 +37,11 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> {
let delta = Instant::now() - start; let delta = Instant::now() - start;
let timeout = self.timeout.unwrap(); let timeout = self.timeout.unwrap();
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
defmt::info!("Received RESET with delta = {}, timeout = {}", delta.as_millis(), timeout.as_millis()); defmt::info!(
"Received RESET with delta = {}, timeout = {}",
delta.as_millis(),
timeout.as_millis()
);
if delta < timeout { if delta < timeout {
self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader"); self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader");
cortex_m::asm::dsb(); cortex_m::asm::dsb();
@ -36,7 +50,11 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> {
} }
} }
fn control_out(&mut self, req: embassy_usb::control::Request, _: &[u8]) -> Option<embassy_usb::control::OutResponse> { fn control_out(
&mut self,
req: embassy_usb::control::Request,
_: &[u8],
) -> Option<embassy_usb::control::OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None; return None;
} }
@ -53,13 +71,15 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> {
self.state = State::AppDetach; self.state = State::AppDetach;
Some(OutResponse::Accepted) Some(OutResponse::Accepted)
} }
_ => { _ => None,
None
}
} }
} }
fn control_in<'a>(&'a mut self, req: embassy_usb::control::Request, buf: &'a mut [u8]) -> Option<embassy_usb::control::InResponse<'a>> { fn control_in<'a>(
&'a mut self,
req: embassy_usb::control::Request,
buf: &'a mut [u8],
) -> Option<embassy_usb::control::InResponse<'a>> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None; return None;
} }
@ -72,31 +92,30 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> {
buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
Some(InResponse::Accepted(buf)) Some(InResponse::Accepted(buf))
} }
_ => None _ => None,
} }
} }
} }
/// An implementation of the USB DFU 1.1 runtime protocol /// An implementation of the USB DFU 1.1 runtime protocol
/// ///
/// 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. /// 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.
/// The handler is responsive to DFU GetStatus and Detach commands. /// The handler is responsive to DFU GetStatus and Detach commands.
/// ///
/// 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 /// 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. /// it should expose a DFU device, and a software reset will be issued.
/// ///
/// 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. /// 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.
pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>(builder: &mut Builder<'d, D>, handler: &'d mut Control<'d, DFU, STATE>, timeout: Duration) { pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>(
builder: &mut Builder<'d, D>,
handler: &'d mut Control<'d, DFU, STATE>,
timeout: Duration,
) {
#[cfg(feature = "defmt")] #[cfg(feature = "defmt")]
defmt::info!("Application USB DFU initializing"); defmt::info!("Application USB DFU initializing");
let mut func = builder.function(0x00, 0x00, 0x00); let mut func = builder.function(0x00, 0x00, 0x00);
let mut iface = func.interface(); let mut iface = func.interface();
let mut alt = iface.alt_setting( let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None);
USB_CLASS_APPN_SPEC,
APPN_SPEC_SUBCLASS_DFU,
DFU_PROTOCOL_RT,
None,
);
let timeout = timeout.as_millis() as u16; let timeout = timeout.as_millis() as u16;
alt.descriptor( alt.descriptor(
DESC_DFU_FUNCTIONAL, DESC_DFU_FUNCTIONAL,
@ -104,11 +123,13 @@ pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>(builder: &mut
handler.attrs.bits(), handler.attrs.bits(),
(timeout & 0xff) as u8, (timeout & 0xff) as u8,
((timeout >> 8) & 0xff) as u8, ((timeout >> 8) & 0xff) as u8,
0x40, 0x00, // 64B control buffer size for application side 0x40,
0x10, 0x01, // DFU 1.1 0x00, // 64B control buffer size for application side
0x10,
0x01, // DFU 1.1
], ],
); );
drop(func); drop(func);
builder.handler(handler); builder.handler(handler);
} }

View File

@ -1,12 +1,13 @@
use embassy_boot::BlockingFirmwareUpdater; use embassy_boot::BlockingFirmwareUpdater;
use embassy_usb::{ use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
control::{InResponse, OutResponse, Recipient, RequestType}, use embassy_usb::driver::Driver;
driver::Driver, use embassy_usb::{Builder, Handler};
Builder, Handler, use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind};
};
use embedded_storage::nor_flash::{NorFlashErrorKind, NorFlash};
use crate::consts::{DfuAttributes, Request, State, Status, USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, DESC_DFU_FUNCTIONAL}; use crate::consts::{
DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU,
USB_CLASS_APPN_SPEC,
};
/// Internal state for USB DFU /// Internal state for USB DFU
pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> { pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> {
@ -69,17 +70,11 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co
match e { match e {
embassy_boot::FirmwareUpdaterError::Flash(e) => match e { embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
NorFlashErrorKind::OutOfBounds => { NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress,
self.status = Status::ErrAddress
}
_ => self.status = Status::ErrUnknown, _ => self.status = Status::ErrUnknown,
}, },
embassy_boot::FirmwareUpdaterError::Signature(_) => { embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify,
self.status = Status::ErrVerify embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown,
}
embassy_boot::FirmwareUpdaterError::BadState => {
self.status = Status::ErrUnknown
}
} }
} }
} }
@ -101,17 +96,11 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co
match e { match e {
embassy_boot::FirmwareUpdaterError::Flash(e) => match e { embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
NorFlashErrorKind::OutOfBounds => { NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress,
self.status = Status::ErrAddress
}
_ => self.status = Status::ErrUnknown, _ => self.status = Status::ErrUnknown,
}, },
embassy_boot::FirmwareUpdaterError::Signature(_) => { embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify,
self.status = Status::ErrVerify embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown,
}
embassy_boot::FirmwareUpdaterError::BadState => {
self.status = Status::ErrUnknown
}
} }
} }
} }
@ -162,10 +151,10 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co
} }
/// An implementation of the USB DFU 1.1 protocol /// An implementation of the USB DFU 1.1 protocol
/// ///
/// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device /// 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 handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user. /// The handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user.
/// ///
/// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition. /// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition.
/// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware. /// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware.
pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize>( pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize>(
@ -174,20 +163,17 @@ pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, const BLOCK_SI
) { ) {
let mut func = builder.function(0x00, 0x00, 0x00); let mut func = builder.function(0x00, 0x00, 0x00);
let mut iface = func.interface(); let mut iface = func.interface();
let mut alt = iface.alt_setting( let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None);
USB_CLASS_APPN_SPEC,
APPN_SPEC_SUBCLASS_DFU,
DFU_PROTOCOL_DFU,
None,
);
alt.descriptor( alt.descriptor(
DESC_DFU_FUNCTIONAL, DESC_DFU_FUNCTIONAL,
&[ &[
handler.attrs.bits(), handler.attrs.bits(),
0xc4, 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code 0xc4,
0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code
(BLOCK_SIZE & 0xff) as u8, (BLOCK_SIZE & 0xff) as u8,
((BLOCK_SIZE & 0xff00) >> 8) as u8, ((BLOCK_SIZE & 0xff00) >> 8) as u8,
0x10, 0x01, // DFU 1.1 0x10,
0x01, // DFU 1.1
], ],
); );

View File

@ -1,4 +1,3 @@
pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE; pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE;
pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01; pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01;
#[allow(unused)] #[allow(unused)]

View File

@ -12,5 +12,8 @@ mod application;
#[cfg(feature = "application")] #[cfg(feature = "application")]
pub use self::application::*; pub use self::application::*;
#[cfg(any(all(feature = "bootloader", feature = "application"), not(any(feature = "bootloader", feature = "application"))))] #[cfg(any(
all(feature = "bootloader", feature = "application"),
not(any(feature = "bootloader", feature = "application"))
))]
compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features"); compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features");