From 976a7ae22aa222213861c12d515115aac87bd2e0 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Wed, 13 Dec 2023 14:40:49 -0500 Subject: [PATCH 01/13] Add embassy-usb-dfu --- embassy-boot/boot/src/boot_loader.rs | 4 +- .../boot/src/firmware_updater/asynch.rs | 13 +- .../boot/src/firmware_updater/blocking.rs | 17 +- embassy-boot/boot/src/lib.rs | 3 + embassy-boot/stm32/src/lib.rs | 9 +- embassy-usb-dfu/Cargo.toml | 31 +++ embassy-usb-dfu/src/application.rs | 114 ++++++++++ embassy-usb-dfu/src/bootloader.rs | 196 ++++++++++++++++++ embassy-usb-dfu/src/consts.rs | 96 +++++++++ embassy-usb-dfu/src/lib.rs | 16 ++ 10 files changed, 492 insertions(+), 7 deletions(-) create mode 100644 embassy-usb-dfu/Cargo.toml create mode 100644 embassy-usb-dfu/src/application.rs create mode 100644 embassy-usb-dfu/src/bootloader.rs create mode 100644 embassy-usb-dfu/src/consts.rs create mode 100644 embassy-usb-dfu/src/lib.rs diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index a8c19197b..c0deca22b 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; +use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] @@ -384,6 +384,8 @@ impl BootLoader FirmwareUpdater<'d, DFU, STATE> { self.state.mark_updated().await } + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted().await?; + self.state.mark_dfu().await + } + /// Mark firmware boot successful and stop rollback on reset. pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { self.state.mark_booted().await @@ -247,6 +253,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { self.set_magic(SWAP_MAGIC).await } + /// Mark to trigger USB DFU on next boot. + pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC).await + } + /// Mark firmware boot successful and stop rollback on reset. pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { self.set_magic(BOOT_MAGIC).await diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index 76e4264a0..b2a633d1e 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -6,7 +6,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage::nor_flash::NorFlash; use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_MAGIC}; /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to /// 'mess up' the internal bootloader state @@ -168,6 +168,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> self.state.mark_updated() } + /// Mark to trigger USB DFU device on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.state.verify_booted()?; + self.state.mark_dfu() + } + /// Mark firmware boot successful and stop rollback on reset. pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { self.state.mark_booted() @@ -226,7 +232,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { // Make sure we are running a booted firmware to avoid reverting to a bad state. fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { - if self.get_state()? == State::Boot { + if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { Ok(()) } else { Err(FirmwareUpdaterError::BadState) @@ -243,6 +249,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { Ok(State::Swap) + } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { + Ok(State::DfuDetach) } else { Ok(State::Boot) } @@ -253,6 +261,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { self.set_magic(SWAP_MAGIC) } + /// Mark to trigger USB DFU on next boot. + pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { + self.set_magic(DFU_DETACH_MAGIC) + } + /// Mark firmware boot successful and stop rollback on reset. pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { self.set_magic(BOOT_MAGIC) diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 9e70a4dca..451992945 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -23,6 +23,7 @@ pub use firmware_updater::{ pub(crate) const BOOT_MAGIC: u8 = 0xD0; pub(crate) const SWAP_MAGIC: u8 = 0xF0; +pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; /// The state of the bootloader after running prepare. #[derive(PartialEq, Eq, Debug)] @@ -32,6 +33,8 @@ pub enum State { Boot, /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, + /// Application has received a DFU_DETACH request over USB, and is rebooting into the bootloader to apply a DFU. + DfuDetach, } /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index c418cb262..4b4091ac9 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs @@ -10,7 +10,10 @@ pub use embassy_boot::{ use embedded_storage::nor_flash::NorFlash; /// A bootloader for STM32 devices. -pub struct BootLoader; +pub struct BootLoader { + /// The reported state of the bootloader after preparing for boot + pub state: State, +} impl BootLoader { /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware @@ -19,8 +22,8 @@ impl BootLoader { ) -> Self { let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); let mut boot = embassy_boot::BootLoader::new(config); - boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); - Self + let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); + Self { state } } /// Boots the application. diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml new file mode 100644 index 000000000..62398afbc --- /dev/null +++ b/embassy-usb-dfu/Cargo.toml @@ -0,0 +1,31 @@ +[package] +edition = "2021" +name = "embassy-usb-dfu" +version = "0.1.0" +description = "An implementation of the USB DFU 1.1 protocol, using embassy-boot" +license = "MIT OR Apache-2.0" +repository = "https://github.com/embassy-rs/embassy" +categories = [ + "embedded", + "no-std", + "asynchronous" +] + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +bitflags = "2.4.1" +cortex-m = { version = "0.7.7", features = ["inline-asm"] } +defmt = { version = "0.3.5", optional = true } +embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" } +embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } +embassy-futures = { version = "0.1.1", path = "../embassy-futures" } +embassy-sync = { version = "0.5.0", path = "../embassy-sync" } +embassy-time = { version = "0.2.0", path = "../embassy-time" } +embassy-usb = { version = "0.1.0", path = "../embassy-usb", default-features = false } +embedded-storage = { version = "0.3.1" } + +[features] +bootloader = [] +application = [] +defmt = ["dep:defmt"] diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs new file mode 100644 index 000000000..5a52a9fed --- /dev/null +++ b/embassy-usb-dfu/src/application.rs @@ -0,0 +1,114 @@ + +use embassy_boot::BlockingFirmwareUpdater; +use embassy_time::{Instant, Duration}; +use embassy_usb::{Handler, control::{RequestType, Recipient, OutResponse, InResponse}, Builder, driver::Driver}; +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}; + +/// Internal state for the DFU class +pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> { + updater: BlockingFirmwareUpdater<'d, DFU, STATE>, + attrs: DfuAttributes, + state: State, + timeout: Option, + detach_start: Option, +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> { + pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { + Control { updater, attrs, state: State::AppIdle, detach_start: None, timeout: None } + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { + fn reset(&mut self) { + if let Some(start) = self.detach_start { + let delta = Instant::now() - start; + let timeout = self.timeout.unwrap(); + #[cfg(feature = "defmt")] + defmt::info!("Received RESET with delta = {}, timeout = {}", delta.as_millis(), timeout.as_millis()); + if delta < timeout { + self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader"); + cortex_m::asm::dsb(); + cortex_m::peripheral::SCB::sys_reset(); + } + } + } + + fn control_out(&mut self, req: embassy_usb::control::Request, _: &[u8]) -> Option { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + return None; + } + + #[cfg(feature = "defmt")] + defmt::info!("Received request {}", req); + + match Request::try_from(req.request) { + Ok(Request::Detach) => { + #[cfg(feature = "defmt")] + defmt::info!("Received DETACH, awaiting USB reset"); + self.detach_start = Some(Instant::now()); + self.timeout = Some(Duration::from_millis(req.value as u64)); + self.state = State::AppDetach; + Some(OutResponse::Accepted) + } + _ => { + None + } + } + } + + fn control_in<'a>(&'a mut self, req: embassy_usb::control::Request, buf: &'a mut [u8]) -> Option> { + if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { + return None; + } + + #[cfg(feature = "defmt")] + defmt::info!("Received request {}", req); + + 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)) + } + _ => None + } + } +} + +/// 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. +/// 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 +/// 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. +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")] + defmt::info!("Application USB DFU initializing"); + let mut func = builder.function(0x00, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = iface.alt_setting( + USB_CLASS_APPN_SPEC, + APPN_SPEC_SUBCLASS_DFU, + DFU_PROTOCOL_RT, + None, + ); + let timeout = timeout.as_millis() as u16; + alt.descriptor( + DESC_DFU_FUNCTIONAL, + &[ + handler.attrs.bits(), + (timeout & 0xff) as u8, + ((timeout >> 8) & 0xff) as u8, + 0x40, 0x00, // 64B control buffer size for application side + 0x10, 0x01, // DFU 1.1 + ], + ); + + drop(func); + builder.handler(handler); +} \ No newline at end of file diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs new file mode 100644 index 000000000..7bcb0b258 --- /dev/null +++ b/embassy-usb-dfu/src/bootloader.rs @@ -0,0 +1,196 @@ +use embassy_boot::BlockingFirmwareUpdater; +use embassy_usb::{ + control::{InResponse, OutResponse, Recipient, RequestType}, + driver::Driver, + Builder, Handler, +}; +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}; + +/// Internal state for USB DFU +pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> { + updater: BlockingFirmwareUpdater<'d, DFU, STATE>, + attrs: DfuAttributes, + state: State, + status: Status, + offset: usize, +} + +impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, BLOCK_SIZE> { + pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { + Self { + updater, + attrs, + state: State::DfuIdle, + status: Status::Ok, + offset: 0, + } + } + + fn reset_state(&mut self) { + self.offset = 0; + self.state = State::DfuIdle; + self.status = Status::Ok; + } +} + +impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Control<'d, DFU, STATE, BLOCK_SIZE> { + fn control_out( + &mut self, + req: embassy_usb::control::Request, + data: &[u8], + ) -> Option { + 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; + } + + let mut buf = [0; BLOCK_SIZE]; + buf[..data.len()].copy_from_slice(data); + + 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, + NorFlashErrorKind::OutOfBounds => { + self.status = Status::ErrAddress + } + _ => self.status = Status::ErrUnknown, + }, + embassy_boot::FirmwareUpdaterError::Signature(_) => { + self.status = Status::ErrVerify + } + embassy_boot::FirmwareUpdaterError::BadState => { + self.status = Status::ErrUnknown + } + } + } + } + } 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); + } + match self.updater.write_firmware(self.offset, &buf[..]) { + 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, + NorFlashErrorKind::OutOfBounds => { + self.status = Status::ErrAddress + } + _ => self.status = Status::ErrUnknown, + }, + embassy_boot::FirmwareUpdaterError::Signature(_) => { + self.status = Status::ErrVerify + } + embassy_boot::FirmwareUpdaterError::BadState => { + self.status = Status::ErrUnknown + } + } + } + } + } + + 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> { + 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 => cortex_m::peripheral::SCB::sys_reset(), + _ => {} + } + + 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 +/// +/// 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. +/// +/// 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, const BLOCK_SIZE: usize>( + builder: &mut Builder<'d, D>, + handler: &'d mut Control<'d, DFU, STATE, BLOCK_SIZE>, +) { + let mut func = builder.function(0x00, 0x00, 0x00); + let mut iface = func.interface(); + let mut alt = iface.alt_setting( + USB_CLASS_APPN_SPEC, + APPN_SPEC_SUBCLASS_DFU, + DFU_PROTOCOL_DFU, + None, + ); + alt.descriptor( + DESC_DFU_FUNCTIONAL, + &[ + handler.attrs.bits(), + 0xc4, 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code + (BLOCK_SIZE & 0xff) as u8, + ((BLOCK_SIZE & 0xff00) >> 8) as u8, + 0x10, 0x01, // DFU 1.1 + ], + ); + + drop(func); + builder.handler(handler); +} diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs new file mode 100644 index 000000000..b083af9de --- /dev/null +++ b/embassy-usb-dfu/src/consts.rs @@ -0,0 +1,96 @@ + +pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE; +pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01; +#[allow(unused)] +pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02; +#[allow(unused)] +pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01; +pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21; + +#[cfg(feature = "defmt")] +defmt::bitflags! { + pub struct DfuAttributes: u8 { + const WILL_DETACH = 0b0000_1000; + const MANIFESTATION_TOLERANT = 0b0000_0100; + const CAN_UPLOAD = 0b0000_0010; + const CAN_DOWNLOAD = 0b0000_0001; + } +} + +#[cfg(not(feature = "defmt"))] +bitflags::bitflags! { + pub struct DfuAttributes: u8 { + const WILL_DETACH = 0b0000_1000; + const MANIFESTATION_TOLERANT = 0b0000_0100; + const CAN_UPLOAD = 0b0000_0010; + const CAN_DOWNLOAD = 0b0000_0001; + } +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +#[allow(unused)] +pub enum State { + AppIdle = 0, + AppDetach = 1, + DfuIdle = 2, + DlSync = 3, + DlBusy = 4, + Download = 5, + ManifestSync = 6, + Manifest = 7, + ManifestWaitReset = 8, + UploadIdle = 9, + Error = 10, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +#[allow(unused)] +pub enum Status { + Ok = 0x00, + ErrTarget = 0x01, + ErrFile = 0x02, + ErrWrite = 0x03, + ErrErase = 0x04, + ErrCheckErased = 0x05, + ErrProg = 0x06, + ErrVerify = 0x07, + ErrAddress = 0x08, + ErrNotDone = 0x09, + ErrFirmware = 0x0A, + ErrVendor = 0x0B, + ErrUsbr = 0x0C, + ErrPor = 0x0D, + ErrUnknown = 0x0E, + ErrStalledPkt = 0x0F, +} + +#[derive(Copy, Clone, PartialEq, Eq)] +#[repr(u8)] +pub enum Request { + Detach = 0, + Dnload = 1, + Upload = 2, + GetStatus = 3, + ClrStatus = 4, + GetState = 5, + Abort = 6, +} + +impl TryFrom for Request { + type Error = (); + + fn try_from(value: u8) -> Result { + match value { + 0 => Ok(Request::Detach), + 1 => Ok(Request::Dnload), + 2 => Ok(Request::Upload), + 3 => Ok(Request::GetStatus), + 4 => Ok(Request::ClrStatus), + 5 => Ok(Request::GetState), + 6 => Ok(Request::Abort), + _ => Err(()), + } + } +} diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs new file mode 100644 index 000000000..dcdb11b2a --- /dev/null +++ b/embassy-usb-dfu/src/lib.rs @@ -0,0 +1,16 @@ +#![no_std] + +pub mod consts; + +#[cfg(feature = "bootloader")] +mod bootloader; +#[cfg(feature = "bootloader")] +pub use self::bootloader::*; + +#[cfg(feature = "application")] +mod application; +#[cfg(feature = "application")] +pub use self::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"); From c2942f2727739d8972ad211721b1bb1804fb7b4a Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Wed, 13 Dec 2023 14:53:49 -0500 Subject: [PATCH 02/13] fmt --- embassy-usb-dfu/src/application.rs | 75 +++++++++++++++++++----------- embassy-usb-dfu/src/bootloader.rs | 56 +++++++++------------- embassy-usb-dfu/src/consts.rs | 1 - embassy-usb-dfu/src/lib.rs | 5 +- 4 files changed, 73 insertions(+), 64 deletions(-) diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index 5a52a9fed..2e7bda121 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -1,10 +1,14 @@ - use embassy_boot::BlockingFirmwareUpdater; -use embassy_time::{Instant, Duration}; -use embassy_usb::{Handler, control::{RequestType, Recipient, OutResponse, InResponse}, Builder, driver::Driver}; +use embassy_time::{Duration, Instant}; +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 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 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> { 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 timeout = self.timeout.unwrap(); #[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 { self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader"); 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 { + fn control_out( + &mut self, + req: embassy_usb::control::Request, + _: &[u8], + ) -> Option { if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { return None; } @@ -53,13 +71,15 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { self.state = State::AppDetach; Some(OutResponse::Accepted) } - _ => { - None - } + _ => None, } } - fn control_in<'a>(&'a mut self, req: embassy_usb::control::Request, buf: &'a mut [u8]) -> Option> { + fn control_in<'a>( + &'a mut self, + req: embassy_usb::control::Request, + buf: &'a mut [u8], + ) -> Option> { if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { 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]); Some(InResponse::Accepted(buf)) } - _ => None + _ => None, } } } /// 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. -/// 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 /// 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. -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")] defmt::info!("Application USB DFU initializing"); let mut func = builder.function(0x00, 0x00, 0x00); let mut iface = func.interface(); - let mut alt = iface.alt_setting( - USB_CLASS_APPN_SPEC, - APPN_SPEC_SUBCLASS_DFU, - DFU_PROTOCOL_RT, - None, - ); + let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None); let timeout = timeout.as_millis() as u16; alt.descriptor( DESC_DFU_FUNCTIONAL, @@ -104,11 +123,13 @@ pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>(builder: &mut handler.attrs.bits(), (timeout & 0xff) as u8, ((timeout >> 8) & 0xff) as u8, - 0x40, 0x00, // 64B control buffer size for application side - 0x10, 0x01, // DFU 1.1 + 0x40, + 0x00, // 64B control buffer size for application side + 0x10, + 0x01, // DFU 1.1 ], ); drop(func); - builder.handler(handler); -} \ No newline at end of file + builder.handler(handler); +} diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs index 7bcb0b258..215058932 100644 --- a/embassy-usb-dfu/src/bootloader.rs +++ b/embassy-usb-dfu/src/bootloader.rs @@ -1,12 +1,13 @@ use embassy_boot::BlockingFirmwareUpdater; -use embassy_usb::{ - control::{InResponse, OutResponse, Recipient, RequestType}, - driver::Driver, - Builder, Handler, -}; -use embedded_storage::nor_flash::{NorFlashErrorKind, NorFlash}; +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}; -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 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 { embassy_boot::FirmwareUpdaterError::Flash(e) => match e { NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, - NorFlashErrorKind::OutOfBounds => { - self.status = Status::ErrAddress - } + NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress, _ => self.status = Status::ErrUnknown, }, - embassy_boot::FirmwareUpdaterError::Signature(_) => { - self.status = Status::ErrVerify - } - embassy_boot::FirmwareUpdaterError::BadState => { - self.status = Status::ErrUnknown - } + embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify, + 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 { embassy_boot::FirmwareUpdaterError::Flash(e) => match e { NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, - NorFlashErrorKind::OutOfBounds => { - self.status = Status::ErrAddress - } + NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress, _ => self.status = Status::ErrUnknown, }, - embassy_boot::FirmwareUpdaterError::Signature(_) => { - self.status = Status::ErrVerify - } - embassy_boot::FirmwareUpdaterError::BadState => { - self.status = Status::ErrUnknown - } + embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify, + 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 -/// +/// /// 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. -/// +/// /// 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, 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 iface = func.interface(); - let mut alt = iface.alt_setting( - USB_CLASS_APPN_SPEC, - APPN_SPEC_SUBCLASS_DFU, - DFU_PROTOCOL_DFU, - None, - ); + let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None); alt.descriptor( DESC_DFU_FUNCTIONAL, &[ 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 & 0xff00) >> 8) as u8, - 0x10, 0x01, // DFU 1.1 + 0x10, + 0x01, // DFU 1.1 ], ); diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs index b083af9de..b359a107e 100644 --- a/embassy-usb-dfu/src/consts.rs +++ b/embassy-usb-dfu/src/consts.rs @@ -1,4 +1,3 @@ - pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE; pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01; #[allow(unused)] diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index dcdb11b2a..81ef041f8 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -12,5 +12,8 @@ mod application; #[cfg(feature = "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"); From 702d2a1a193985c9b8da6fc5e532ac55b5485464 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Wed, 13 Dec 2023 16:08:20 -0500 Subject: [PATCH 03/13] Formatting fixes, add example using stm32wb55 --- .../boot/src/firmware_updater/asynch.rs | 2 +- .../boot/src/firmware_updater/blocking.rs | 2 +- examples/boot-usb-dfu/.cargo/config.toml | 9 ++ .../application/stm32wb/.cargo/config.toml | 9 ++ .../application/stm32wb/Cargo.toml | 32 +++++++ .../application/stm32wb/README.md | 29 ++++++ .../boot-usb-dfu/application/stm32wb/build.rs | 37 ++++++++ .../boot-usb-dfu/application/stm32wb/memory.x | 15 +++ .../application/stm32wb/src/main.rs | 64 +++++++++++++ .../bootloader/stm32wb/Cargo.toml | 63 ++++++++++++ .../boot-usb-dfu/bootloader/stm32wb/README.md | 11 +++ .../boot-usb-dfu/bootloader/stm32wb/build.rs | 27 ++++++ .../boot-usb-dfu/bootloader/stm32wb/memory.x | 18 ++++ .../bootloader/stm32wb/src/main.rs | 95 +++++++++++++++++++ 14 files changed, 411 insertions(+), 2 deletions(-) create mode 100644 examples/boot-usb-dfu/.cargo/config.toml create mode 100644 examples/boot-usb-dfu/application/stm32wb/.cargo/config.toml create mode 100644 examples/boot-usb-dfu/application/stm32wb/Cargo.toml create mode 100644 examples/boot-usb-dfu/application/stm32wb/README.md create mode 100644 examples/boot-usb-dfu/application/stm32wb/build.rs create mode 100644 examples/boot-usb-dfu/application/stm32wb/memory.x create mode 100644 examples/boot-usb-dfu/application/stm32wb/src/main.rs create mode 100644 examples/boot-usb-dfu/bootloader/stm32wb/Cargo.toml create mode 100644 examples/boot-usb-dfu/bootloader/stm32wb/README.md create mode 100644 examples/boot-usb-dfu/bootloader/stm32wb/build.rs create mode 100644 examples/boot-usb-dfu/bootloader/stm32wb/memory.x create mode 100644 examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index 0a3cbc136..82e99965b 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -6,7 +6,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage_async::nor_flash::NorFlash; use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_MAGIC}; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to /// 'mess up' the internal bootloader state diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index b2a633d1e..ae4179ba3 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -6,7 +6,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embedded_storage::nor_flash::NorFlash; use super::FirmwareUpdaterConfig; -use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_MAGIC}; +use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to /// 'mess up' the internal bootloader state diff --git a/examples/boot-usb-dfu/.cargo/config.toml b/examples/boot-usb-dfu/.cargo/config.toml new file mode 100644 index 000000000..de3a814f7 --- /dev/null +++ b/examples/boot-usb-dfu/.cargo/config.toml @@ -0,0 +1,9 @@ +[unstable] +build-std = ["core"] +build-std-features = ["panic_immediate_abort"] + +[build] +target = "thumbv7em-none-eabi" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/boot-usb-dfu/application/stm32wb/.cargo/config.toml b/examples/boot-usb-dfu/application/stm32wb/.cargo/config.toml new file mode 100644 index 000000000..4f8094ff2 --- /dev/null +++ b/examples/boot-usb-dfu/application/stm32wb/.cargo/config.toml @@ -0,0 +1,9 @@ +[target.'cfg(all(target_arch = "arm", target_os = "none"))'] +# replace your chip as listed in `probe-rs chip list` +runner = "probe-rs run --chip STM32WLE5JCIx" + +[build] +target = "thumbv7em-none-eabihf" + +[env] +DEFMT_LOG = "trace" diff --git a/examples/boot-usb-dfu/application/stm32wb/Cargo.toml b/examples/boot-usb-dfu/application/stm32wb/Cargo.toml new file mode 100644 index 000000000..0a41c0648 --- /dev/null +++ b/examples/boot-usb-dfu/application/stm32wb/Cargo.toml @@ -0,0 +1,32 @@ +[package] +edition = "2021" +name = "embassy-boot-stm32wl-examples" +version = "0.1.0" +license = "MIT OR Apache-2.0" + +[dependencies] +embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } +embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } +embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } +embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } +embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } +embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } +embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb" } +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application"] } + +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } +panic-reset = { version = "0.1.1" } +embedded-hal = { version = "0.2.6" } + +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +cortex-m-rt = "0.7.0" + +[features] +defmt = [ + "dep:defmt", + "embassy-stm32/defmt", + "embassy-boot-stm32/defmt", + "embassy-sync/defmt", +] +skip-include = [] diff --git a/examples/boot-usb-dfu/application/stm32wb/README.md b/examples/boot-usb-dfu/application/stm32wb/README.md new file mode 100644 index 000000000..c8dce0387 --- /dev/null +++ b/examples/boot-usb-dfu/application/stm32wb/README.md @@ -0,0 +1,29 @@ +# Examples using bootloader + +Example for STM32WL demonstrating the bootloader. The example consists of application binaries, 'a' +which allows you to press a button to start the DFU process, and 'b' which is the updated +application. + + +## Prerequisites + +* `cargo-binutils` +* `cargo-flash` +* `embassy-boot-stm32` + +## Usage + +``` +# Flash bootloader +cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4 --chip STM32WLE5JCIx +# Build 'b' +cargo build --release --bin b +# Generate binary for 'b' +cargo objcopy --release --bin b -- -O binary b.bin +``` + +# Flash `a` (which includes b.bin) + +``` +cargo flash --release --bin a --chip STM32WLE5JCIx +``` diff --git a/examples/boot-usb-dfu/application/stm32wb/build.rs b/examples/boot-usb-dfu/application/stm32wb/build.rs new file mode 100644 index 000000000..e1da69328 --- /dev/null +++ b/examples/boot-usb-dfu/application/stm32wb/build.rs @@ -0,0 +1,37 @@ +//! This build script copies the `memory.x` file from the crate root into +//! a directory where the linker can always find it at build time. +//! For many projects this is optional, as the linker always searches the +//! project root directory -- wherever `Cargo.toml` is. However, if you +//! are using a workspace or have a more complicated build setup, this +//! build script becomes required. Additionally, by requesting that +//! Cargo re-run the build script whenever `memory.x` is changed, +//! updating `memory.x` ensures a rebuild of the application with the +//! new memory settings. + +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/examples/boot-usb-dfu/application/stm32wb/memory.x b/examples/boot-usb-dfu/application/stm32wb/memory.x new file mode 100644 index 000000000..f51875766 --- /dev/null +++ b/examples/boot-usb-dfu/application/stm32wb/memory.x @@ -0,0 +1,15 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + FLASH : ORIGIN = 0x08008000, LENGTH = 32K + DFU : ORIGIN = 0x08010000, LENGTH = 36K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); diff --git a/examples/boot-usb-dfu/application/stm32wb/src/main.rs b/examples/boot-usb-dfu/application/stm32wb/src/main.rs new file mode 100644 index 000000000..f03003ffe --- /dev/null +++ b/examples/boot-usb-dfu/application/stm32wb/src/main.rs @@ -0,0 +1,64 @@ +#![no_std] +#![no_main] +#![feature(type_alias_impl_trait)] + +use core::cell::RefCell; + +#[cfg(feature = "defmt-rtt")] +use defmt_rtt::*; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_executor::Spawner; +use embassy_stm32::flash::{Flash, WRITE_SIZE}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32::usb::{self, Driver}; +use embassy_stm32::{bind_interrupts, peripherals}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_time::Duration; +use embassy_usb::Builder; +use embassy_usb_dfu::consts::DfuAttributes; +use embassy_usb_dfu::{usb_dfu, Control}; +use panic_reset as _; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; +}); + +#[embassy_executor::main] +async fn main(_spawner: Spawner) { + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + let flash = Flash::new_blocking(p.FLASH); + let flash = Mutex::new(RefCell::new(flash)); + + let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut magic = AlignedBuffer([0; WRITE_SIZE]); + let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0); + updater.mark_booted().expect("Failed to mark booted"); + + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-DFU Runtime example"); + config.serial_number = Some("1235678"); + + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 64]; + let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + usb_dfu::<_, _, _>(&mut builder, &mut state, Duration::from_millis(2500)); + + let mut dev = builder.build(); + dev.run().await +} diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/Cargo.toml b/examples/boot-usb-dfu/bootloader/stm32wb/Cargo.toml new file mode 100644 index 000000000..774a8223d --- /dev/null +++ b/examples/boot-usb-dfu/bootloader/stm32wb/Cargo.toml @@ -0,0 +1,63 @@ +[package] +edition = "2021" +name = "stm32-bootloader-example" +version = "0.1.0" +description = "Example bootloader for STM32 chips" +license = "MIT OR Apache-2.0" + +[dependencies] +defmt = { version = "0.3", optional = true } +defmt-rtt = { version = "0.4", optional = true } + +embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] } +embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } +cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } +embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } +cortex-m-rt = { version = "0.7" } +embedded-storage = "0.3.1" +embedded-storage-async = "0.4.0" +cfg-if = "1.0.0" +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["bootloader"] } +embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb", default-features = false } +embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" } + +[features] +defmt = [ + "dep:defmt", + "embassy-boot-stm32/defmt", + "embassy-stm32/defmt", + "embassy-usb/defmt", + "embassy-usb-dfu/defmt" +] +debug = ["defmt-rtt", "defmt"] + +[profile.dev] +debug = 2 +debug-assertions = true +incremental = false +opt-level = 'z' +overflow-checks = true + +[profile.release] +codegen-units = 1 +debug = 2 +debug-assertions = false +incremental = false +lto = 'fat' +opt-level = 'z' +overflow-checks = false + +# do not optimize proc-macro crates = faster builds from scratch +[profile.dev.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false + +[profile.release.build-override] +codegen-units = 8 +debug = false +debug-assertions = false +opt-level = 0 +overflow-checks = false diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/README.md b/examples/boot-usb-dfu/bootloader/stm32wb/README.md new file mode 100644 index 000000000..a82b730b9 --- /dev/null +++ b/examples/boot-usb-dfu/bootloader/stm32wb/README.md @@ -0,0 +1,11 @@ +# Bootloader for STM32 + +The bootloader uses `embassy-boot` to interact with the flash. + +# Usage + +Flash the bootloader + +``` +cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx +``` diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/build.rs b/examples/boot-usb-dfu/bootloader/stm32wb/build.rs new file mode 100644 index 000000000..fd605991f --- /dev/null +++ b/examples/boot-usb-dfu/bootloader/stm32wb/build.rs @@ -0,0 +1,27 @@ +use std::env; +use std::fs::File; +use std::io::Write; +use std::path::PathBuf; + +fn main() { + // Put `memory.x` in our output directory and ensure it's + // on the linker search path. + let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); + File::create(out.join("memory.x")) + .unwrap() + .write_all(include_bytes!("memory.x")) + .unwrap(); + println!("cargo:rustc-link-search={}", out.display()); + + // By default, Cargo will re-run a build script whenever + // any file in the project changes. By specifying `memory.x` + // here, we ensure the build script is only re-run when + // `memory.x` is changed. + println!("cargo:rerun-if-changed=memory.x"); + + println!("cargo:rustc-link-arg-bins=--nmagic"); + println!("cargo:rustc-link-arg-bins=-Tlink.x"); + if env::var("CARGO_FEATURE_DEFMT").is_ok() { + println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); + } +} diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/memory.x b/examples/boot-usb-dfu/bootloader/stm32wb/memory.x new file mode 100644 index 000000000..b6f185ef7 --- /dev/null +++ b/examples/boot-usb-dfu/bootloader/stm32wb/memory.x @@ -0,0 +1,18 @@ +MEMORY +{ + /* NOTE 1 K = 1 KiBi = 1024 bytes */ + FLASH : ORIGIN = 0x08000000, LENGTH = 24K + BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K + ACTIVE : ORIGIN = 0x08008000, LENGTH = 32K + DFU : ORIGIN = 0x08010000, LENGTH = 36K + RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K +} + +__bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); +__bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); + +__bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); +__bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); + +__bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH); +__bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH); diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs b/examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs new file mode 100644 index 000000000..00a535d35 --- /dev/null +++ b/examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs @@ -0,0 +1,95 @@ +#![no_std] +#![no_main] + +use core::cell::RefCell; + +use cortex_m_rt::{entry, exception}; +#[cfg(feature = "defmt")] +use defmt_rtt as _; +use embassy_boot_stm32::*; +use embassy_stm32::flash::{Flash, BANK1_REGION, WRITE_SIZE}; +use embassy_stm32::rcc::WPAN_DEFAULT; +use embassy_stm32::usb::Driver; +use embassy_stm32::{bind_interrupts, peripherals, usb}; +use embassy_sync::blocking_mutex::Mutex; +use embassy_usb::Builder; +use embassy_usb_dfu::consts::DfuAttributes; +use embassy_usb_dfu::{usb_dfu, Control}; + +bind_interrupts!(struct Irqs { + USB_LP => usb::InterruptHandler; +}); + +#[entry] +fn main() -> ! { + let mut config = embassy_stm32::Config::default(); + config.rcc = WPAN_DEFAULT; + let p = embassy_stm32::init(config); + + // Uncomment this if you are debugging the bootloader with debugger/RTT attached, + // as it prevents a hard fault when accessing flash 'too early' after boot. + /* + for i in 0..10000000 { + cortex_m::asm::nop(); + } + */ + + let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); + let flash = Mutex::new(RefCell::new(layout.bank1_region)); + + let config = BootLoaderConfig::from_linkerfile_blocking(&flash); + let active_offset = config.active.offset(); + let bl = BootLoader::prepare::<_, _, _, 2048>(config); + if bl.state == State::DfuDetach { + let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); + let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); + config.manufacturer = Some("Embassy"); + config.product = Some("USB-DFU Bootloader example"); + config.serial_number = Some("1235678"); + + let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); + let mut buffer = AlignedBuffer([0; WRITE_SIZE]); + let updater = BlockingFirmwareUpdater::new(fw_config, &mut buffer.0[..]); + + let mut device_descriptor = [0; 256]; + let mut config_descriptor = [0; 256]; + let mut bos_descriptor = [0; 256]; + let mut control_buf = [0; 4096]; + let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); + let mut builder = Builder::new( + driver, + config, + &mut device_descriptor, + &mut config_descriptor, + &mut bos_descriptor, + &mut [], + &mut control_buf, + ); + + usb_dfu::<_, _, _, 4096>(&mut builder, &mut state); + + let mut dev = builder.build(); + embassy_futures::block_on(dev.run()); + } + + unsafe { bl.load(BANK1_REGION.base + active_offset) } +} + +#[no_mangle] +#[cfg_attr(target_os = "none", link_section = ".HardFault.user")] +unsafe extern "C" fn HardFault() { + cortex_m::peripheral::SCB::sys_reset(); +} + +#[exception] +unsafe fn DefaultHandler(_: i16) -> ! { + const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; + let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; + + panic!("DefaultHandler #{:?}", irqn); +} + +#[panic_handler] +fn panic(_info: &core::panic::PanicInfo) -> ! { + cortex_m::asm::udf(); +} From b60b3f4eb8f22ecda1c30d63213010f1b6b47686 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Wed, 13 Dec 2023 16:19:59 -0500 Subject: [PATCH 04/13] Last fmt hopefully --- embassy-boot/boot/src/boot_loader.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 340962a7a..b3970d867 100644 --- a/embassy-boot/boot/src/boot_loader.rs +++ b/embassy-boot/boot/src/boot_loader.rs @@ -5,7 +5,7 @@ use embassy_sync::blocking_mutex::raw::NoopRawMutex; use embassy_sync::blocking_mutex::Mutex; use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; -use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC, DFU_DETACH_MAGIC}; +use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; /// Errors returned by bootloader #[derive(PartialEq, Eq, Debug)] From e27e00f6280683293f427d0731aa69ca32dbbe60 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 09:36:22 -0500 Subject: [PATCH 05/13] Address reviews --- .../boot/src/firmware_updater/asynch.rs | 10 + .../boot/src/firmware_updater/blocking.rs | 10 + embassy-boot/boot/src/lib.rs | 2 +- embassy-usb-dfu/src/application.rs | 36 ++- embassy-usb-dfu/src/bootloader.rs | 8 +- embassy-usb-dfu/src/fmt.rs | 258 ++++++++++++++++++ embassy-usb-dfu/src/lib.rs | 1 + examples/boot-usb-dfu/.cargo/config.toml | 9 - .../stm32wb-dfu}/.cargo/config.toml | 0 .../application/stm32wb-dfu}/Cargo.toml | 1 + .../application/stm32wb-dfu}/README.md | 0 .../application/stm32wb-dfu}/build.rs | 0 .../application/stm32wb-dfu}/memory.x | 0 .../application/stm32wb-dfu}/src/main.rs | 0 .../bootloader/stm32wb-dfu}/Cargo.toml | 0 .../bootloader/stm32wb-dfu}/README.md | 0 .../bootloader/stm32wb-dfu}/build.rs | 0 .../bootloader/stm32wb-dfu}/memory.x | 0 .../bootloader/stm32wb-dfu}/src/main.rs | 13 +- 19 files changed, 307 insertions(+), 41 deletions(-) create mode 100644 embassy-usb-dfu/src/fmt.rs delete mode 100644 examples/boot-usb-dfu/.cargo/config.toml rename examples/{boot-usb-dfu/application/stm32wb => boot/application/stm32wb-dfu}/.cargo/config.toml (100%) rename examples/{boot-usb-dfu/application/stm32wb => boot/application/stm32wb-dfu}/Cargo.toml (98%) rename examples/{boot-usb-dfu/application/stm32wb => boot/application/stm32wb-dfu}/README.md (100%) rename examples/{boot-usb-dfu/application/stm32wb => boot/application/stm32wb-dfu}/build.rs (100%) rename examples/{boot-usb-dfu/application/stm32wb => boot/application/stm32wb-dfu}/memory.x (100%) rename examples/{boot-usb-dfu/application/stm32wb => boot/application/stm32wb-dfu}/src/main.rs (100%) rename examples/{boot-usb-dfu/bootloader/stm32wb => boot/bootloader/stm32wb-dfu}/Cargo.toml (100%) rename examples/{boot-usb-dfu/bootloader/stm32wb => boot/bootloader/stm32wb-dfu}/README.md (100%) rename examples/{boot-usb-dfu/bootloader/stm32wb => boot/bootloader/stm32wb-dfu}/build.rs (100%) rename examples/{boot-usb-dfu/bootloader/stm32wb => boot/bootloader/stm32wb-dfu}/memory.x (100%) rename examples/{boot-usb-dfu/bootloader/stm32wb => boot/bootloader/stm32wb-dfu}/src/main.rs (91%) diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index 82e99965b..d8d85c3d2 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs @@ -213,6 +213,16 @@ pub struct FirmwareState<'d, STATE> { } impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { + /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + /// Create a firmware state instance with a buffer for magic content and state partition. /// /// # Safety diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index ae4179ba3..4f56f152d 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -219,6 +219,16 @@ pub struct BlockingFirmwareState<'d, STATE> { } impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { + /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. + /// + /// # Safety + /// + /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from + /// and written to. + pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { + Self::new(config.state, aligned) + } + /// Create a firmware state instance with a buffer for magic content and state partition. /// /// # Safety diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 451992945..15b69f69d 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs @@ -33,7 +33,7 @@ pub enum State { Boot, /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. Swap, - /// Application has received a DFU_DETACH request over USB, and is rebooting into the bootloader to apply a DFU. + /// Application has received a request to reboot into DFU mode to apply an update. DfuDetach, } diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index 2e7bda121..0b7b53af8 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -1,4 +1,4 @@ -use embassy_boot::BlockingFirmwareUpdater; +use embassy_boot::BlockingFirmwareState; use embassy_time::{Duration, Instant}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; use embassy_usb::driver::Driver; @@ -11,18 +11,18 @@ use crate::consts::{ }; /// Internal state for the DFU class -pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> { - updater: BlockingFirmwareUpdater<'d, DFU, STATE>, +pub struct Control<'d, STATE: NorFlash> { + firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes, state: State, timeout: Option, detach_start: Option, } -impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> { - pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { +impl<'d, STATE: NorFlash> Control<'d, STATE> { + pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self { Control { - updater, + firmware_state, attrs, state: State::AppIdle, detach_start: None, @@ -31,19 +31,20 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> { } } -impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { +impl<'d, STATE: NorFlash> Handler for Control<'d, STATE> { fn reset(&mut self) { if let Some(start) = self.detach_start { let delta = Instant::now() - start; let timeout = self.timeout.unwrap(); - #[cfg(feature = "defmt")] - defmt::info!( + trace!( "Received RESET with delta = {}, timeout = {}", delta.as_millis(), timeout.as_millis() ); if delta < timeout { - self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader"); + self.firmware_state + .mark_dfu() + .expect("Failed to mark DFU mode in bootloader"); cortex_m::asm::dsb(); cortex_m::peripheral::SCB::sys_reset(); } @@ -59,13 +60,11 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { return None; } - #[cfg(feature = "defmt")] - defmt::info!("Received request {}", req); + trace!("Received request {}", req); match Request::try_from(req.request) { Ok(Request::Detach) => { - #[cfg(feature = "defmt")] - defmt::info!("Received DETACH, awaiting USB reset"); + trace!("Received DETACH, awaiting USB reset"); self.detach_start = Some(Instant::now()); self.timeout = Some(Duration::from_millis(req.value as u64)); self.state = State::AppDetach; @@ -84,8 +83,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { return None; } - #[cfg(feature = "defmt")] - defmt::info!("Received request {}", req); + trace!("Received request {}", req); match Request::try_from(req.request) { Ok(Request::GetStatus) => { @@ -106,13 +104,11 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { /// 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. -pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>( +pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash>( builder: &mut Builder<'d, D>, - handler: &'d mut Control<'d, DFU, STATE>, + handler: &'d mut Control<'d, STATE>, timeout: Duration, ) { - #[cfg(feature = "defmt")] - defmt::info!("Application USB DFU initializing"); let mut func = builder.function(0x00, 0x00, 0x00); let mut iface = func.interface(); let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None); diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs index 215058932..99384d961 100644 --- a/embassy-usb-dfu/src/bootloader.rs +++ b/embassy-usb-dfu/src/bootloader.rs @@ -1,4 +1,4 @@ -use embassy_boot::BlockingFirmwareUpdater; +use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; use embassy_usb::driver::Driver; use embassy_usb::{Builder, Handler}; @@ -56,8 +56,8 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co self.offset = 0; } - let mut buf = [0; BLOCK_SIZE]; - buf[..data.len()].copy_from_slice(data); + let mut buf = AlignedBuffer([0; BLOCK_SIZE]); + buf.as_mut()[..data.len()].copy_from_slice(data); if req.length == 0 { match self.updater.mark_updated() { @@ -85,7 +85,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co self.state = State::Error; return Some(OutResponse::Rejected); } - match self.updater.write_firmware(self.offset, &buf[..]) { + match self.updater.write_firmware(self.offset, buf.as_ref()) { Ok(_) => { self.status = Status::Ok; self.state = State::DlSync; diff --git a/embassy-usb-dfu/src/fmt.rs b/embassy-usb-dfu/src/fmt.rs new file mode 100644 index 000000000..78e583c1c --- /dev/null +++ b/embassy-usb-dfu/src/fmt.rs @@ -0,0 +1,258 @@ +#![macro_use] +#![allow(unused_macros)] + +use core::fmt::{Debug, Display, LowerHex}; + +#[cfg(all(feature = "defmt", feature = "log"))] +compile_error!("You may not enable both `defmt` and `log` features."); + +macro_rules! assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert!($($x)*); + } + }; +} + +macro_rules! assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_eq!($($x)*); + } + }; +} + +macro_rules! assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::assert_ne!($($x)*); + } + }; +} + +macro_rules! debug_assert { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert!($($x)*); + } + }; +} + +macro_rules! debug_assert_eq { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_eq!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_eq!($($x)*); + } + }; +} + +macro_rules! debug_assert_ne { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::debug_assert_ne!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::debug_assert_ne!($($x)*); + } + }; +} + +macro_rules! todo { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::todo!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::todo!($($x)*); + } + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unreachable { + ($($x:tt)*) => { + ::core::unreachable!($($x)*) + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unreachable { + ($($x:tt)*) => { + ::defmt::unreachable!($($x)*) + }; +} + +macro_rules! panic { + ($($x:tt)*) => { + { + #[cfg(not(feature = "defmt"))] + ::core::panic!($($x)*); + #[cfg(feature = "defmt")] + ::defmt::panic!($($x)*); + } + }; +} + +macro_rules! trace { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::trace!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::trace!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! debug { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::debug!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::debug!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! info { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::info!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::info!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! warn { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::warn!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::warn!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +macro_rules! error { + ($s:literal $(, $x:expr)* $(,)?) => { + { + #[cfg(feature = "log")] + ::log::error!($s $(, $x)*); + #[cfg(feature = "defmt")] + ::defmt::error!($s $(, $x)*); + #[cfg(not(any(feature = "log", feature="defmt")))] + let _ = ($( & $x ),*); + } + }; +} + +#[cfg(feature = "defmt")] +macro_rules! unwrap { + ($($x:tt)*) => { + ::defmt::unwrap!($($x)*) + }; +} + +#[cfg(not(feature = "defmt"))] +macro_rules! unwrap { + ($arg:expr) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); + } + } + }; + ($arg:expr, $($msg:expr),+ $(,)? ) => { + match $crate::fmt::Try::into_result($arg) { + ::core::result::Result::Ok(t) => t, + ::core::result::Result::Err(e) => { + ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); + } + } + } +} + +#[derive(Debug, Copy, Clone, Eq, PartialEq)] +pub struct NoneError; + +pub trait Try { + type Ok; + type Error; + fn into_result(self) -> Result; +} + +impl Try for Option { + type Ok = T; + type Error = NoneError; + + #[inline] + fn into_result(self) -> Result { + self.ok_or(NoneError) + } +} + +impl Try for Result { + type Ok = T; + type Error = E; + + #[inline] + fn into_result(self) -> Self { + self + } +} + +#[allow(unused)] +pub(crate) struct Bytes<'a>(pub &'a [u8]); + +impl<'a> Debug for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> Display for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +impl<'a> LowerHex for Bytes<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + write!(f, "{:#02x?}", self.0) + } +} + +#[cfg(feature = "defmt")] +impl<'a> defmt::Format for Bytes<'a> { + fn format(&self, fmt: defmt::Formatter) { + defmt::write!(fmt, "{:02x}", self.0) + } +} diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index 81ef041f8..ae0fbbd4c 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -1,4 +1,5 @@ #![no_std] +mod fmt; pub mod consts; diff --git a/examples/boot-usb-dfu/.cargo/config.toml b/examples/boot-usb-dfu/.cargo/config.toml deleted file mode 100644 index de3a814f7..000000000 --- a/examples/boot-usb-dfu/.cargo/config.toml +++ /dev/null @@ -1,9 +0,0 @@ -[unstable] -build-std = ["core"] -build-std-features = ["panic_immediate_abort"] - -[build] -target = "thumbv7em-none-eabi" - -[env] -DEFMT_LOG = "trace" diff --git a/examples/boot-usb-dfu/application/stm32wb/.cargo/config.toml b/examples/boot/application/stm32wb-dfu/.cargo/config.toml similarity index 100% rename from examples/boot-usb-dfu/application/stm32wb/.cargo/config.toml rename to examples/boot/application/stm32wb-dfu/.cargo/config.toml diff --git a/examples/boot-usb-dfu/application/stm32wb/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml similarity index 98% rename from examples/boot-usb-dfu/application/stm32wb/Cargo.toml rename to examples/boot/application/stm32wb-dfu/Cargo.toml index 0a41c0648..e67224ce6 100644 --- a/examples/boot-usb-dfu/application/stm32wb/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -25,6 +25,7 @@ cortex-m-rt = "0.7.0" [features] defmt = [ "dep:defmt", + "dep:defmt-rtt", "embassy-stm32/defmt", "embassy-boot-stm32/defmt", "embassy-sync/defmt", diff --git a/examples/boot-usb-dfu/application/stm32wb/README.md b/examples/boot/application/stm32wb-dfu/README.md similarity index 100% rename from examples/boot-usb-dfu/application/stm32wb/README.md rename to examples/boot/application/stm32wb-dfu/README.md diff --git a/examples/boot-usb-dfu/application/stm32wb/build.rs b/examples/boot/application/stm32wb-dfu/build.rs similarity index 100% rename from examples/boot-usb-dfu/application/stm32wb/build.rs rename to examples/boot/application/stm32wb-dfu/build.rs diff --git a/examples/boot-usb-dfu/application/stm32wb/memory.x b/examples/boot/application/stm32wb-dfu/memory.x similarity index 100% rename from examples/boot-usb-dfu/application/stm32wb/memory.x rename to examples/boot/application/stm32wb-dfu/memory.x diff --git a/examples/boot-usb-dfu/application/stm32wb/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs similarity index 100% rename from examples/boot-usb-dfu/application/stm32wb/src/main.rs rename to examples/boot/application/stm32wb-dfu/src/main.rs diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml similarity index 100% rename from examples/boot-usb-dfu/bootloader/stm32wb/Cargo.toml rename to examples/boot/bootloader/stm32wb-dfu/Cargo.toml diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/README.md b/examples/boot/bootloader/stm32wb-dfu/README.md similarity index 100% rename from examples/boot-usb-dfu/bootloader/stm32wb/README.md rename to examples/boot/bootloader/stm32wb-dfu/README.md diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/build.rs b/examples/boot/bootloader/stm32wb-dfu/build.rs similarity index 100% rename from examples/boot-usb-dfu/bootloader/stm32wb/build.rs rename to examples/boot/bootloader/stm32wb-dfu/build.rs diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/memory.x b/examples/boot/bootloader/stm32wb-dfu/memory.x similarity index 100% rename from examples/boot-usb-dfu/bootloader/stm32wb/memory.x rename to examples/boot/bootloader/stm32wb-dfu/memory.x diff --git a/examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs similarity index 91% rename from examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs rename to examples/boot/bootloader/stm32wb-dfu/src/main.rs index 00a535d35..a2b884770 100644 --- a/examples/boot-usb-dfu/bootloader/stm32wb/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -26,13 +26,12 @@ fn main() -> ! { config.rcc = WPAN_DEFAULT; let p = embassy_stm32::init(config); - // Uncomment this if you are debugging the bootloader with debugger/RTT attached, - // as it prevents a hard fault when accessing flash 'too early' after boot. - /* - for i in 0..10000000 { - cortex_m::asm::nop(); - } - */ + // Prevent a hard fault when accessing flash 'too early' after boot. + #[cfg(feature = "defmt")] + for _ in 0..10000000 { + cortex_m::asm::nop(); + } + let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); let flash = Mutex::new(RefCell::new(layout.bank1_region)); From c1438fe87bf363b018231bd36e188c72679eb202 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 09:38:02 -0500 Subject: [PATCH 06/13] fmt --- embassy-boot/boot/src/firmware_updater/blocking.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index 4f56f152d..c4c142169 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs @@ -220,9 +220,9 @@ pub struct BlockingFirmwareState<'d, STATE> { impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. - /// + /// /// # Safety - /// + /// /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from /// and written to. pub fn from_config(config: FirmwareUpdaterConfig, aligned: &'d mut [u8]) -> Self { From 9cc5d8ac892d4efbb629ab3bafdf341edd50ca7e Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 09:38:49 -0500 Subject: [PATCH 07/13] fmt --- examples/boot/bootloader/stm32wb-dfu/src/main.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs index a2b884770..db7039e8c 100644 --- a/examples/boot/bootloader/stm32wb-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -31,7 +31,6 @@ fn main() -> ! { for _ in 0..10000000 { cortex_m::asm::nop(); } - let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); let flash = Mutex::new(RefCell::new(layout.bank1_region)); From ef692c514101d6c91059c13af52cdd70dc5cd6e0 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 10:06:36 -0500 Subject: [PATCH 08/13] SCB::sys_reset has a DSB internally, no need to replicate --- embassy-usb-dfu/src/application.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index 0b7b53af8..5ff8f90f8 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -45,7 +45,6 @@ impl<'d, STATE: NorFlash> Handler for Control<'d, STATE> { self.firmware_state .mark_dfu() .expect("Failed to mark DFU mode in bootloader"); - cortex_m::asm::dsb(); cortex_m::peripheral::SCB::sys_reset(); } } From a34abd849f09187edea48713538403ebf44d6576 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 10:30:10 -0500 Subject: [PATCH 09/13] Add examples to ci.sh --- ci.sh | 2 ++ examples/boot/application/stm32wb-dfu/Cargo.toml | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ci.sh b/ci.sh index 8a5e206d2..83c05d1b9 100755 --- a/ci.sh +++ b/ci.sh @@ -173,10 +173,12 @@ cargo batch \ --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \ + --- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wb-dfu \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ + --- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf \ --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index e67224ce6..57d51de02 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -30,4 +30,3 @@ defmt = [ "embassy-boot-stm32/defmt", "embassy-sync/defmt", ] -skip-include = [] From 27d054aa6875d977efc5f5c3554c57fd1245bdb9 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 10:34:22 -0500 Subject: [PATCH 10/13] Adjust toml files, fix application example --- examples/boot/application/stm32wb-dfu/Cargo.toml | 2 +- examples/boot/application/stm32wb-dfu/src/main.rs | 10 +++++----- examples/boot/bootloader/stm32wb-dfu/Cargo.toml | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 57d51de02..0ed0b75e0 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -1,6 +1,6 @@ [package] edition = "2021" -name = "embassy-boot-stm32wl-examples" +name = "embassy-boot-stm32wb-dfu-examples" version = "0.1.0" license = "MIT OR Apache-2.0" diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs index f03003ffe..cdac903b5 100644 --- a/examples/boot/application/stm32wb-dfu/src/main.rs +++ b/examples/boot/application/stm32wb-dfu/src/main.rs @@ -6,7 +6,7 @@ use core::cell::RefCell; #[cfg(feature = "defmt-rtt")] use defmt_rtt::*; -use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareUpdater, FirmwareUpdaterConfig}; +use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareState, FirmwareUpdaterConfig}; use embassy_executor::Spawner; use embassy_stm32::flash::{Flash, WRITE_SIZE}; use embassy_stm32::rcc::WPAN_DEFAULT; @@ -33,8 +33,8 @@ async fn main(_spawner: Spawner) { let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); let mut magic = AlignedBuffer([0; WRITE_SIZE]); - let mut updater = BlockingFirmwareUpdater::new(config, &mut magic.0); - updater.mark_booted().expect("Failed to mark booted"); + let mut firmware_state = BlockingFirmwareState::from_config(config, &mut magic.0); + firmware_state.mark_booted().expect("Failed to mark booted"); let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); @@ -46,7 +46,7 @@ async fn main(_spawner: Spawner) { let mut config_descriptor = [0; 256]; let mut bos_descriptor = [0; 256]; let mut control_buf = [0; 64]; - let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); + let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD); let mut builder = Builder::new( driver, config, @@ -57,7 +57,7 @@ async fn main(_spawner: Spawner) { &mut control_buf, ); - usb_dfu::<_, _, _>(&mut builder, &mut state, Duration::from_millis(2500)); + usb_dfu::<_, _>(&mut builder, &mut state, Duration::from_millis(2500)); let mut dev = builder.build(); dev.run().await diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index 774a8223d..fde9eb57d 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -1,8 +1,8 @@ [package] edition = "2021" -name = "stm32-bootloader-example" +name = "stm32wb-dfu-bootloader-example" version = "0.1.0" -description = "Example bootloader for STM32 chips" +description = "Example USB DFUbootloader for the STM32WB series of chips" license = "MIT OR Apache-2.0" [dependencies] From cbc8ccc51e8e747fab51ac377225495cd24eb447 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 10:56:16 -0500 Subject: [PATCH 11/13] Adjust stm32wb-dfu example memory maps to fix linker errors --- examples/boot/application/stm32wb-dfu/memory.x | 4 ++-- examples/boot/bootloader/stm32wb-dfu/memory.x | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/boot/application/stm32wb-dfu/memory.x b/examples/boot/application/stm32wb-dfu/memory.x index f51875766..ff1b800d2 100644 --- a/examples/boot/application/stm32wb-dfu/memory.x +++ b/examples/boot/application/stm32wb-dfu/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - FLASH : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + FLASH : ORIGIN = 0x08008000, LENGTH = 128K + DFU : ORIGIN = 0x08028000, LENGTH = 132K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K } diff --git a/examples/boot/bootloader/stm32wb-dfu/memory.x b/examples/boot/bootloader/stm32wb-dfu/memory.x index b6f185ef7..858062631 100644 --- a/examples/boot/bootloader/stm32wb-dfu/memory.x +++ b/examples/boot/bootloader/stm32wb-dfu/memory.x @@ -3,8 +3,8 @@ MEMORY /* NOTE 1 K = 1 KiBi = 1024 bytes */ FLASH : ORIGIN = 0x08000000, LENGTH = 24K BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K - ACTIVE : ORIGIN = 0x08008000, LENGTH = 32K - DFU : ORIGIN = 0x08010000, LENGTH = 36K + ACTIVE : ORIGIN = 0x08008000, LENGTH = 128K + DFU : ORIGIN = 0x08028000, LENGTH = 132K RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K } From 9f9f6e75bb3ef6d285ebed88a20ab57fb55f3d07 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 13:29:26 -0500 Subject: [PATCH 12/13] Abstract chip reset logic, add Reset impls for cortex-m and esp32c3 --- embassy-usb-dfu/Cargo.toml | 5 +-- embassy-usb-dfu/src/application.rs | 17 ++++++---- embassy-usb-dfu/src/bootloader.rs | 19 ++++++++---- embassy-usb-dfu/src/lib.rs | 31 +++++++++++++++++++ .../boot/application/stm32wb-dfu/Cargo.toml | 2 +- .../boot/application/stm32wb-dfu/src/main.rs | 4 +-- .../boot/bootloader/stm32wb-dfu/Cargo.toml | 2 +- .../boot/bootloader/stm32wb-dfu/src/main.rs | 4 +-- 8 files changed, 64 insertions(+), 20 deletions(-) diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index 62398afbc..02146c646 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -15,15 +15,16 @@ categories = [ [dependencies] bitflags = "2.4.1" -cortex-m = { version = "0.7.7", features = ["inline-asm"] } +cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } defmt = { version = "0.3.5", optional = true } embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" } -embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } +# embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } embassy-futures = { version = "0.1.1", path = "../embassy-futures" } embassy-sync = { version = "0.5.0", path = "../embassy-sync" } embassy-time = { version = "0.2.0", path = "../embassy-time" } embassy-usb = { version = "0.1.0", path = "../embassy-usb", default-features = false } embedded-storage = { version = "0.3.1" } +esp32c3-hal = { version = "0.13.0", optional = true, default-features = false } [features] bootloader = [] diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs index 5ff8f90f8..75689db26 100644 --- a/embassy-usb-dfu/src/application.rs +++ b/embassy-usb-dfu/src/application.rs @@ -1,3 +1,5 @@ +use core::marker::PhantomData; + use embassy_boot::BlockingFirmwareState; use embassy_time::{Duration, Instant}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; @@ -9,17 +11,19 @@ use crate::consts::{ DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT, USB_CLASS_APPN_SPEC, }; +use crate::Reset; /// Internal state for the DFU class -pub struct Control<'d, STATE: NorFlash> { +pub struct Control<'d, STATE: NorFlash, RST: Reset> { firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes, state: State, timeout: Option, detach_start: Option, + _rst: PhantomData, } -impl<'d, STATE: NorFlash> Control<'d, STATE> { +impl<'d, STATE: NorFlash, RST: Reset> Control<'d, STATE, RST> { pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self { Control { firmware_state, @@ -27,11 +31,12 @@ impl<'d, STATE: NorFlash> Control<'d, STATE> { state: State::AppIdle, detach_start: None, timeout: None, + _rst: PhantomData, } } } -impl<'d, STATE: NorFlash> Handler for Control<'d, STATE> { +impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { fn reset(&mut self) { if let Some(start) = self.detach_start { let delta = Instant::now() - start; @@ -45,7 +50,7 @@ impl<'d, STATE: NorFlash> Handler for Control<'d, STATE> { self.firmware_state .mark_dfu() .expect("Failed to mark DFU mode in bootloader"); - cortex_m::peripheral::SCB::sys_reset(); + RST::sys_reset() } } } @@ -103,9 +108,9 @@ impl<'d, STATE: NorFlash> Handler for Control<'d, STATE> { /// 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. -pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash>( +pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>( builder: &mut Builder<'d, D>, - handler: &'d mut Control<'d, STATE>, + handler: &'d mut Control<'d, STATE, RST>, timeout: Duration, ) { let mut func = builder.function(0x00, 0x00, 0x00); diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs index 99384d961..d41e6280d 100644 --- a/embassy-usb-dfu/src/bootloader.rs +++ b/embassy-usb-dfu/src/bootloader.rs @@ -1,3 +1,5 @@ +use core::marker::PhantomData; + use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater}; use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; use embassy_usb::driver::Driver; @@ -8,17 +10,19 @@ use crate::consts::{ DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU, USB_CLASS_APPN_SPEC, }; +use crate::Reset; /// 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, RST: Reset, const BLOCK_SIZE: usize> { updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes, state: State, status: Status, offset: usize, + _rst: PhantomData, } -impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, BLOCK_SIZE> { +impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> { pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { Self { updater, @@ -26,6 +30,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Control<'d, DF state: State::DfuIdle, status: Status::Ok, offset: 0, + _rst: PhantomData, } } @@ -36,7 +41,9 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Control<'d, DF } } -impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Control<'d, DFU, STATE, BLOCK_SIZE> { +impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler + for Control<'d, DFU, STATE, RST, BLOCK_SIZE> +{ fn control_out( &mut self, req: embassy_usb::control::Request, @@ -131,7 +138,7 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co 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 => cortex_m::peripheral::SCB::sys_reset(), + State::ManifestSync => RST::sys_reset(), _ => {} } @@ -157,9 +164,9 @@ impl<'d, DFU: NorFlash, STATE: NorFlash, const BLOCK_SIZE: usize> Handler for Co /// /// 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, const BLOCK_SIZE: usize>( +pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>( builder: &mut Builder<'d, D>, - handler: &'d mut Control<'d, DFU, STATE, BLOCK_SIZE>, + handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>, ) { let mut func = builder.function(0x00, 0x00, 0x00); let mut iface = func.interface(); diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index ae0fbbd4c..283905de9 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -18,3 +18,34 @@ pub use self::application::*; not(any(feature = "bootloader", feature = "application")) ))] compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features"); + +/// Provides a platform-agnostic interface for initiating a system reset. +/// +/// This crate exposes `ResetImmediate` when compiled with cortex-m or esp32c3 support, which immediately issues a +/// reset request without interfacing with any other peripherals. +/// +/// If alternate behaviour is desired, a custom implementation of Reset can be provided as a type argument to the usb_dfu function. +pub trait Reset { + fn sys_reset() -> !; +} + +#[cfg(feature = "esp32c3-hal")] +pub struct ResetImmediate; + +#[cfg(feature = "esp32c3-hal")] +impl Reset for ResetImmediate { + fn sys_reset() -> ! { + esp32c3_hal::reset::software_reset(); + loop {} + } +} + +#[cfg(feature = "cortex-m")] +pub struct ResetImmediate; + +#[cfg(feature = "cortex-m")] +impl Reset for ResetImmediate { + fn sys_reset() -> ! { + cortex_m::peripheral::SCB::sys_reset() + } +} diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml index 0ed0b75e0..f6beea498 100644 --- a/examples/boot/application/stm32wb-dfu/Cargo.toml +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml @@ -12,7 +12,7 @@ embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", feature embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb" } -embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application"] } +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } defmt = { version = "0.3", optional = true } defmt-rtt = { version = "0.4", optional = true } diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs index cdac903b5..fbecbf23b 100644 --- a/examples/boot/application/stm32wb-dfu/src/main.rs +++ b/examples/boot/application/stm32wb-dfu/src/main.rs @@ -16,7 +16,7 @@ use embassy_sync::blocking_mutex::Mutex; use embassy_time::Duration; use embassy_usb::Builder; use embassy_usb_dfu::consts::DfuAttributes; -use embassy_usb_dfu::{usb_dfu, Control}; +use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; use panic_reset as _; bind_interrupts!(struct Irqs { @@ -57,7 +57,7 @@ async fn main(_spawner: Spawner) { &mut control_buf, ); - usb_dfu::<_, _>(&mut builder, &mut state, Duration::from_millis(2500)); + usb_dfu::<_, _, ResetImmediate>(&mut builder, &mut state, Duration::from_millis(2500)); let mut dev = builder.build(); dev.run().await diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index fde9eb57d..e849eb539 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -17,7 +17,7 @@ cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" embedded-storage-async = "0.4.0" cfg-if = "1.0.0" -embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["bootloader"] } +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["bootloader", "cortex-m"] } embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb", default-features = false } embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" } diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs index db7039e8c..a7ab813b6 100644 --- a/examples/boot/bootloader/stm32wb-dfu/src/main.rs +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs @@ -14,7 +14,7 @@ use embassy_stm32::{bind_interrupts, peripherals, usb}; use embassy_sync::blocking_mutex::Mutex; use embassy_usb::Builder; use embassy_usb_dfu::consts::DfuAttributes; -use embassy_usb_dfu::{usb_dfu, Control}; +use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; bind_interrupts!(struct Irqs { USB_LP => usb::InterruptHandler; @@ -64,7 +64,7 @@ fn main() -> ! { &mut control_buf, ); - usb_dfu::<_, _, _, 4096>(&mut builder, &mut state); + usb_dfu::<_, _, _, ResetImmediate, 4096>(&mut builder, &mut state); let mut dev = builder.build(); embassy_futures::block_on(dev.run()); From 33e8943e5b6e637b82f13c77bd88bb56d55ab515 Mon Sep 17 00:00:00 2001 From: Kaitlyn Kenwell Date: Thu, 14 Dec 2023 14:16:58 -0500 Subject: [PATCH 13/13] Rename bootloader feature to dfu --- embassy-usb-dfu/Cargo.toml | 2 +- embassy-usb-dfu/src/lib.rs | 8 ++++---- examples/boot/bootloader/stm32wb-dfu/Cargo.toml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml index 02146c646..ee110ee87 100644 --- a/embassy-usb-dfu/Cargo.toml +++ b/embassy-usb-dfu/Cargo.toml @@ -27,6 +27,6 @@ embedded-storage = { version = "0.3.1" } esp32c3-hal = { version = "0.13.0", optional = true, default-features = false } [features] -bootloader = [] +dfu = [] application = [] defmt = ["dep:defmt"] diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs index 283905de9..389bb33f2 100644 --- a/embassy-usb-dfu/src/lib.rs +++ b/embassy-usb-dfu/src/lib.rs @@ -3,9 +3,9 @@ mod fmt; pub mod consts; -#[cfg(feature = "bootloader")] +#[cfg(feature = "dfu")] mod bootloader; -#[cfg(feature = "bootloader")] +#[cfg(feature = "dfu")] pub use self::bootloader::*; #[cfg(feature = "application")] @@ -14,8 +14,8 @@ mod application; pub use self::application::*; #[cfg(any( - all(feature = "bootloader", feature = "application"), - not(any(feature = "bootloader", feature = "application")) + all(feature = "dfu", feature = "application"), + not(any(feature = "dfu", feature = "application")) ))] compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features"); diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml index e849eb539..ada073970 100644 --- a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml @@ -17,7 +17,7 @@ cortex-m-rt = { version = "0.7" } embedded-storage = "0.3.1" embedded-storage-async = "0.4.0" cfg-if = "1.0.0" -embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["bootloader", "cortex-m"] } +embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["dfu", "cortex-m"] } embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb", default-features = false } embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" }