embassy/embassy-usb-dfu/src/bootloader.rs

190 lines
7.8 KiB
Rust
Raw Normal View History

use core::marker::PhantomData;
2023-12-14 15:36:22 +01:00
use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater};
2023-12-13 20:53:49 +01:00
use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType};
use embassy_usb::driver::Driver;
use embassy_usb::{Builder, Handler};
use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind};
2023-12-13 20:40:49 +01:00
2023-12-13 20:53:49 +01:00
use crate::consts::{
DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU,
USB_CLASS_APPN_SPEC,
};
use crate::Reset;
2023-12-13 20:40:49 +01:00
/// Internal state for USB DFU
pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> {
2023-12-13 20:40:49 +01:00
updater: BlockingFirmwareUpdater<'d, DFU, STATE>,
attrs: DfuAttributes,
state: State,
status: Status,
offset: usize,
_rst: PhantomData<RST>,
2023-12-13 20:40:49 +01:00
}
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> {
2023-12-13 20:40:49 +01:00
pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self {
Self {
updater,
attrs,
state: State::DfuIdle,
status: Status::Ok,
offset: 0,
_rst: PhantomData,
2023-12-13 20:40:49 +01:00
}
}
fn reset_state(&mut self) {
self.offset = 0;
self.state = State::DfuIdle;
self.status = Status::Ok;
}
}
impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler
for Control<'d, DFU, STATE, RST, BLOCK_SIZE>
{
2023-12-13 20:40:49 +01:00
fn control_out(
&mut self,
req: embassy_usb::control::Request,
data: &[u8],
) -> Option<embassy_usb::control::OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
match Request::try_from(req.request) {
Ok(Request::Abort) => {
self.reset_state();
Some(OutResponse::Accepted)
}
Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
if req.value == 0 {
self.state = State::Download;
self.offset = 0;
}
2023-12-14 15:36:22 +01:00
let mut buf = AlignedBuffer([0; BLOCK_SIZE]);
buf.as_mut()[..data.len()].copy_from_slice(data);
2023-12-13 20:40:49 +01:00
if req.length == 0 {
match self.updater.mark_updated() {
Ok(_) => {
self.status = Status::Ok;
self.state = State::ManifestSync;
}
Err(e) => {
self.state = State::Error;
match e {
embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
2023-12-13 20:53:49 +01:00
NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress,
2023-12-13 20:40:49 +01:00
_ => self.status = Status::ErrUnknown,
},
2023-12-13 20:53:49 +01:00
embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify,
embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown,
2023-12-13 20:40:49 +01:00
}
}
}
} else {
if self.state != State::Download {
// Unexpected DNLOAD while chip is waiting for a GETSTATUS
self.status = Status::ErrUnknown;
self.state = State::Error;
return Some(OutResponse::Rejected);
}
2023-12-14 15:36:22 +01:00
match self.updater.write_firmware(self.offset, buf.as_ref()) {
2023-12-13 20:40:49 +01:00
Ok(_) => {
self.status = Status::Ok;
self.state = State::DlSync;
self.offset += data.len();
}
Err(e) => {
self.state = State::Error;
match e {
embassy_boot::FirmwareUpdaterError::Flash(e) => match e {
NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite,
2023-12-13 20:53:49 +01:00
NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress,
2023-12-13 20:40:49 +01:00
_ => self.status = Status::ErrUnknown,
},
2023-12-13 20:53:49 +01:00
embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify,
embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown,
2023-12-13 20:40:49 +01:00
}
}
}
}
Some(OutResponse::Accepted)
}
Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode
Ok(Request::ClrStatus) => {
self.reset_state();
Some(OutResponse::Accepted)
}
_ => None,
}
}
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) {
return None;
}
match Request::try_from(req.request) {
Ok(Request::GetStatus) => {
//TODO: Configurable poll timeout, ability to add string for Vendor error
buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
match self.state {
State::DlSync => self.state = State::Download,
State::ManifestSync => RST::sys_reset(),
2023-12-13 20:40:49 +01:00
_ => {}
}
Some(InResponse::Accepted(&buf[0..6]))
}
Ok(Request::GetState) => {
buf[0] = self.state as u8;
Some(InResponse::Accepted(&buf[0..1]))
}
Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
//TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload.
Some(InResponse::Rejected)
}
_ => None,
}
}
}
/// An implementation of the USB DFU 1.1 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 handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user.
2023-12-13 20:53:49 +01:00
///
2023-12-13 20:40:49 +01:00
/// 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.
pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>(
2023-12-13 20:40:49 +01:00
builder: &mut Builder<'d, D>,
handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>,
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_DFU, None);
2023-12-13 20:40:49 +01:00
alt.descriptor(
DESC_DFU_FUNCTIONAL,
&[
handler.attrs.bits(),
2023-12-13 20:53:49 +01:00
0xc4,
0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code
2023-12-13 20:40:49 +01:00
(BLOCK_SIZE & 0xff) as u8,
((BLOCK_SIZE & 0xff00) >> 8) as u8,
2023-12-13 20:53:49 +01:00
0x10,
0x01, // DFU 1.1
2023-12-13 20:40:49 +01:00
],
);
drop(func);
builder.handler(handler);
}