diff options
| author | Ulf Lilleengen <[email protected]> | 2023-12-14 19:56:04 +0000 |
|---|---|---|
| committer | GitHub <[email protected]> | 2023-12-14 19:56:04 +0000 |
| commit | 5ec2fbe3a2ce3234ed6477fe5923c3d2425b55a7 (patch) | |
| tree | 94b50831f7e5d606643eba7e0c9c1e5c7bf1ec86 | |
| parent | 485765320aaef82adbd4865b25e7171fb8f4041a (diff) | |
| parent | 33e8943e5b6e637b82f13c77bd88bb56d55ab515 (diff) | |
Merge pull request #2284 from Redrield/feature/embassy-usb-dfu
Add embassy-usb-dfu crate, with related modifications to embassy-boot
23 files changed, 1219 insertions, 7 deletions
| @@ -173,10 +173,12 @@ cargo batch \ | |||
| 173 | --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ | 173 | --- build --release --manifest-path examples/boot/application/stm32l1/Cargo.toml --target thumbv7m-none-eabi --features skip-include --out-dir out/examples/boot/stm32l1 \ |
| 174 | --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ | 174 | --- build --release --manifest-path examples/boot/application/stm32l4/Cargo.toml --target thumbv7em-none-eabi --features skip-include --out-dir out/examples/boot/stm32l4 \ |
| 175 | --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \ | 175 | --- build --release --manifest-path examples/boot/application/stm32wl/Cargo.toml --target thumbv7em-none-eabihf --features skip-include --out-dir out/examples/boot/stm32wl \ |
| 176 | --- build --release --manifest-path examples/boot/application/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf --out-dir out/examples/boot/stm32wb-dfu \ | ||
| 176 | --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ | 177 | --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv7em-none-eabi --features embassy-nrf/nrf52840 \ |
| 177 | --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ | 178 | --- build --release --manifest-path examples/boot/bootloader/nrf/Cargo.toml --target thumbv8m.main-none-eabihf --features embassy-nrf/nrf9160-ns \ |
| 178 | --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ | 179 | --- build --release --manifest-path examples/boot/bootloader/rp/Cargo.toml --target thumbv6m-none-eabi \ |
| 179 | --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ | 180 | --- build --release --manifest-path examples/boot/bootloader/stm32/Cargo.toml --target thumbv7em-none-eabi --features embassy-stm32/stm32wl55jc-cm4 \ |
| 181 | --- build --release --manifest-path examples/boot/bootloader/stm32wb-dfu/Cargo.toml --target thumbv7em-none-eabihf \ | ||
| 180 | --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ | 182 | --- build --release --manifest-path examples/wasm/Cargo.toml --target wasm32-unknown-unknown --out-dir out/examples/wasm \ |
| 181 | --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ | 183 | --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7m-none-eabi --features stm32f103c8 --out-dir out/tests/stm32f103c8 \ |
| 182 | --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ | 184 | --- build --release --manifest-path tests/stm32/Cargo.toml --target thumbv7em-none-eabi --features stm32f429zi --out-dir out/tests/stm32f429zi \ |
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs index 65b12dc5f..e568001bc 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; | |||
| 5 | use embassy_sync::blocking_mutex::Mutex; | 5 | use embassy_sync::blocking_mutex::Mutex; |
| 6 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | 6 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; |
| 7 | 7 | ||
| 8 | use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | 8 | use crate::{State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; |
| 9 | 9 | ||
| 10 | /// Errors returned by bootloader | 10 | /// Errors returned by bootloader |
| 11 | #[derive(PartialEq, Eq, Debug)] | 11 | #[derive(PartialEq, Eq, Debug)] |
| @@ -371,6 +371,8 @@ impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, S | |||
| 371 | 371 | ||
| 372 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { | 372 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { |
| 373 | Ok(State::Swap) | 373 | Ok(State::Swap) |
| 374 | } else if !state_word.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 375 | Ok(State::DfuDetach) | ||
| 374 | } else { | 376 | } else { |
| 375 | Ok(State::Boot) | 377 | Ok(State::Boot) |
| 376 | } | 378 | } |
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index ae713bb6f..d8d85c3d2 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; | |||
| 6 | use embedded_storage_async::nor_flash::NorFlash; | 6 | use embedded_storage_async::nor_flash::NorFlash; |
| 7 | 7 | ||
| 8 | use super::FirmwareUpdaterConfig; | 8 | use super::FirmwareUpdaterConfig; |
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; |
| 10 | 10 | ||
| 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to |
| 12 | /// 'mess up' the internal bootloader state | 12 | /// 'mess up' the internal bootloader state |
| @@ -161,6 +161,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<'d, DFU, STATE> { | |||
| 161 | self.state.mark_updated().await | 161 | self.state.mark_updated().await |
| 162 | } | 162 | } |
| 163 | 163 | ||
| 164 | /// Mark to trigger USB DFU on next boot. | ||
| 165 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 166 | self.state.verify_booted().await?; | ||
| 167 | self.state.mark_dfu().await | ||
| 168 | } | ||
| 169 | |||
| 164 | /// Mark firmware boot successful and stop rollback on reset. | 170 | /// Mark firmware boot successful and stop rollback on reset. |
| 165 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 171 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 166 | self.state.mark_booted().await | 172 | self.state.mark_booted().await |
| @@ -207,6 +213,16 @@ pub struct FirmwareState<'d, STATE> { | |||
| 207 | } | 213 | } |
| 208 | 214 | ||
| 209 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | 215 | impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { |
| 216 | /// Create a firmware state instance from a FirmwareUpdaterConfig with a buffer for magic content and state partition. | ||
| 217 | /// | ||
| 218 | /// # Safety | ||
| 219 | /// | ||
| 220 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 221 | /// and written to. | ||
| 222 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 223 | Self::new(config.state, aligned) | ||
| 224 | } | ||
| 225 | |||
| 210 | /// Create a firmware state instance with a buffer for magic content and state partition. | 226 | /// Create a firmware state instance with a buffer for magic content and state partition. |
| 211 | /// | 227 | /// |
| 212 | /// # Safety | 228 | /// # Safety |
| @@ -247,6 +263,11 @@ impl<'d, STATE: NorFlash> FirmwareState<'d, STATE> { | |||
| 247 | self.set_magic(SWAP_MAGIC).await | 263 | self.set_magic(SWAP_MAGIC).await |
| 248 | } | 264 | } |
| 249 | 265 | ||
| 266 | /// Mark to trigger USB DFU on next boot. | ||
| 267 | pub async fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 268 | self.set_magic(DFU_DETACH_MAGIC).await | ||
| 269 | } | ||
| 270 | |||
| 250 | /// Mark firmware boot successful and stop rollback on reset. | 271 | /// Mark firmware boot successful and stop rollback on reset. |
| 251 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 272 | pub async fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 252 | self.set_magic(BOOT_MAGIC).await | 273 | 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..c4c142169 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; | |||
| 6 | use embedded_storage::nor_flash::NorFlash; | 6 | use embedded_storage::nor_flash::NorFlash; |
| 7 | 7 | ||
| 8 | use super::FirmwareUpdaterConfig; | 8 | use super::FirmwareUpdaterConfig; |
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, DFU_DETACH_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; |
| 10 | 10 | ||
| 11 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | 11 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to |
| 12 | /// 'mess up' the internal bootloader state | 12 | /// 'mess up' the internal bootloader state |
| @@ -168,6 +168,12 @@ impl<'d, DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<'d, DFU, STATE> | |||
| 168 | self.state.mark_updated() | 168 | self.state.mark_updated() |
| 169 | } | 169 | } |
| 170 | 170 | ||
| 171 | /// Mark to trigger USB DFU device on next boot. | ||
| 172 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 173 | self.state.verify_booted()?; | ||
| 174 | self.state.mark_dfu() | ||
| 175 | } | ||
| 176 | |||
| 171 | /// Mark firmware boot successful and stop rollback on reset. | 177 | /// Mark firmware boot successful and stop rollback on reset. |
| 172 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 178 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 173 | self.state.mark_booted() | 179 | self.state.mark_booted() |
| @@ -213,6 +219,16 @@ pub struct BlockingFirmwareState<'d, STATE> { | |||
| 213 | } | 219 | } |
| 214 | 220 | ||
| 215 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | 221 | impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { |
| 222 | /// Creates a firmware state instance from a FirmwareUpdaterConfig, with a buffer for magic content and state partition. | ||
| 223 | /// | ||
| 224 | /// # Safety | ||
| 225 | /// | ||
| 226 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 227 | /// and written to. | ||
| 228 | pub fn from_config<DFU: NorFlash>(config: FirmwareUpdaterConfig<DFU, STATE>, aligned: &'d mut [u8]) -> Self { | ||
| 229 | Self::new(config.state, aligned) | ||
| 230 | } | ||
| 231 | |||
| 216 | /// Create a firmware state instance with a buffer for magic content and state partition. | 232 | /// Create a firmware state instance with a buffer for magic content and state partition. |
| 217 | /// | 233 | /// |
| 218 | /// # Safety | 234 | /// # Safety |
| @@ -226,7 +242,7 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 226 | 242 | ||
| 227 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | 243 | // Make sure we are running a booted firmware to avoid reverting to a bad state. |
| 228 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 244 | fn verify_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 229 | if self.get_state()? == State::Boot { | 245 | if self.get_state()? == State::Boot || self.get_state()? == State::DfuDetach { |
| 230 | Ok(()) | 246 | Ok(()) |
| 231 | } else { | 247 | } else { |
| 232 | Err(FirmwareUpdaterError::BadState) | 248 | Err(FirmwareUpdaterError::BadState) |
| @@ -243,6 +259,8 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 243 | 259 | ||
| 244 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { | 260 | if !self.aligned.iter().any(|&b| b != SWAP_MAGIC) { |
| 245 | Ok(State::Swap) | 261 | Ok(State::Swap) |
| 262 | } else if !self.aligned.iter().any(|&b| b != DFU_DETACH_MAGIC) { | ||
| 263 | Ok(State::DfuDetach) | ||
| 246 | } else { | 264 | } else { |
| 247 | Ok(State::Boot) | 265 | Ok(State::Boot) |
| 248 | } | 266 | } |
| @@ -253,6 +271,11 @@ impl<'d, STATE: NorFlash> BlockingFirmwareState<'d, STATE> { | |||
| 253 | self.set_magic(SWAP_MAGIC) | 271 | self.set_magic(SWAP_MAGIC) |
| 254 | } | 272 | } |
| 255 | 273 | ||
| 274 | /// Mark to trigger USB DFU on next boot. | ||
| 275 | pub fn mark_dfu(&mut self) -> Result<(), FirmwareUpdaterError> { | ||
| 276 | self.set_magic(DFU_DETACH_MAGIC) | ||
| 277 | } | ||
| 278 | |||
| 256 | /// Mark firmware boot successful and stop rollback on reset. | 279 | /// Mark firmware boot successful and stop rollback on reset. |
| 257 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { | 280 | pub fn mark_booted(&mut self) -> Result<(), FirmwareUpdaterError> { |
| 258 | self.set_magic(BOOT_MAGIC) | 281 | self.set_magic(BOOT_MAGIC) |
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 9e70a4dca..15b69f69d 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs | |||
| @@ -23,6 +23,7 @@ pub use firmware_updater::{ | |||
| 23 | 23 | ||
| 24 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; | 24 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; |
| 25 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; | 25 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; |
| 26 | pub(crate) const DFU_DETACH_MAGIC: u8 = 0xE0; | ||
| 26 | 27 | ||
| 27 | /// The state of the bootloader after running prepare. | 28 | /// The state of the bootloader after running prepare. |
| 28 | #[derive(PartialEq, Eq, Debug)] | 29 | #[derive(PartialEq, Eq, Debug)] |
| @@ -32,6 +33,8 @@ pub enum State { | |||
| 32 | Boot, | 33 | Boot, |
| 33 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. | 34 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. |
| 34 | Swap, | 35 | Swap, |
| 36 | /// Application has received a request to reboot into DFU mode to apply an update. | ||
| 37 | DfuDetach, | ||
| 35 | } | 38 | } |
| 36 | 39 | ||
| 37 | /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. | 40 | /// 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::{ | |||
| 10 | use embedded_storage::nor_flash::NorFlash; | 10 | use embedded_storage::nor_flash::NorFlash; |
| 11 | 11 | ||
| 12 | /// A bootloader for STM32 devices. | 12 | /// A bootloader for STM32 devices. |
| 13 | pub struct BootLoader; | 13 | pub struct BootLoader { |
| 14 | /// The reported state of the bootloader after preparing for boot | ||
| 15 | pub state: State, | ||
| 16 | } | ||
| 14 | 17 | ||
| 15 | impl BootLoader { | 18 | impl BootLoader { |
| 16 | /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware | 19 | /// Inspect the bootloader state and perform actions required before booting, such as swapping firmware |
| @@ -19,8 +22,8 @@ impl BootLoader { | |||
| 19 | ) -> Self { | 22 | ) -> Self { |
| 20 | let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); | 23 | let mut aligned_buf = AlignedBuffer([0; BUFFER_SIZE]); |
| 21 | let mut boot = embassy_boot::BootLoader::new(config); | 24 | let mut boot = embassy_boot::BootLoader::new(config); |
| 22 | boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); | 25 | let state = boot.prepare_boot(aligned_buf.as_mut()).expect("Boot prepare error"); |
| 23 | Self | 26 | Self { state } |
| 24 | } | 27 | } |
| 25 | 28 | ||
| 26 | /// Boots the application. | 29 | /// Boots the application. |
diff --git a/embassy-usb-dfu/Cargo.toml b/embassy-usb-dfu/Cargo.toml new file mode 100644 index 000000000..ee110ee87 --- /dev/null +++ b/embassy-usb-dfu/Cargo.toml | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-usb-dfu" | ||
| 4 | version = "0.1.0" | ||
| 5 | description = "An implementation of the USB DFU 1.1 protocol, using embassy-boot" | ||
| 6 | license = "MIT OR Apache-2.0" | ||
| 7 | repository = "https://github.com/embassy-rs/embassy" | ||
| 8 | categories = [ | ||
| 9 | "embedded", | ||
| 10 | "no-std", | ||
| 11 | "asynchronous" | ||
| 12 | ] | ||
| 13 | |||
| 14 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||
| 15 | |||
| 16 | [dependencies] | ||
| 17 | bitflags = "2.4.1" | ||
| 18 | cortex-m = { version = "0.7.7", features = ["inline-asm"], optional = true } | ||
| 19 | defmt = { version = "0.3.5", optional = true } | ||
| 20 | embassy-boot = { version = "0.1.1", path = "../embassy-boot/boot" } | ||
| 21 | # embassy-embedded-hal = { version = "0.1.0", path = "../embassy-embedded-hal" } | ||
| 22 | embassy-futures = { version = "0.1.1", path = "../embassy-futures" } | ||
| 23 | embassy-sync = { version = "0.5.0", path = "../embassy-sync" } | ||
| 24 | embassy-time = { version = "0.2.0", path = "../embassy-time" } | ||
| 25 | embassy-usb = { version = "0.1.0", path = "../embassy-usb", default-features = false } | ||
| 26 | embedded-storage = { version = "0.3.1" } | ||
| 27 | esp32c3-hal = { version = "0.13.0", optional = true, default-features = false } | ||
| 28 | |||
| 29 | [features] | ||
| 30 | dfu = [] | ||
| 31 | application = [] | ||
| 32 | 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..75689db26 --- /dev/null +++ b/embassy-usb-dfu/src/application.rs | |||
| @@ -0,0 +1,135 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_boot::BlockingFirmwareState; | ||
| 4 | use embassy_time::{Duration, Instant}; | ||
| 5 | use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; | ||
| 6 | use embassy_usb::driver::Driver; | ||
| 7 | use embassy_usb::{Builder, Handler}; | ||
| 8 | use embedded_storage::nor_flash::NorFlash; | ||
| 9 | |||
| 10 | use crate::consts::{ | ||
| 11 | DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT, | ||
| 12 | USB_CLASS_APPN_SPEC, | ||
| 13 | }; | ||
| 14 | use crate::Reset; | ||
| 15 | |||
| 16 | /// Internal state for the DFU class | ||
| 17 | pub struct Control<'d, STATE: NorFlash, RST: Reset> { | ||
| 18 | firmware_state: BlockingFirmwareState<'d, STATE>, | ||
| 19 | attrs: DfuAttributes, | ||
| 20 | state: State, | ||
| 21 | timeout: Option<Duration>, | ||
| 22 | detach_start: Option<Instant>, | ||
| 23 | _rst: PhantomData<RST>, | ||
| 24 | } | ||
| 25 | |||
| 26 | impl<'d, STATE: NorFlash, RST: Reset> Control<'d, STATE, RST> { | ||
| 27 | pub fn new(firmware_state: BlockingFirmwareState<'d, STATE>, attrs: DfuAttributes) -> Self { | ||
| 28 | Control { | ||
| 29 | firmware_state, | ||
| 30 | attrs, | ||
| 31 | state: State::AppIdle, | ||
| 32 | detach_start: None, | ||
| 33 | timeout: None, | ||
| 34 | _rst: PhantomData, | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | impl<'d, STATE: NorFlash, RST: Reset> Handler for Control<'d, STATE, RST> { | ||
| 40 | fn reset(&mut self) { | ||
| 41 | if let Some(start) = self.detach_start { | ||
| 42 | let delta = Instant::now() - start; | ||
| 43 | let timeout = self.timeout.unwrap(); | ||
| 44 | trace!( | ||
| 45 | "Received RESET with delta = {}, timeout = {}", | ||
| 46 | delta.as_millis(), | ||
| 47 | timeout.as_millis() | ||
| 48 | ); | ||
| 49 | if delta < timeout { | ||
| 50 | self.firmware_state | ||
| 51 | .mark_dfu() | ||
| 52 | .expect("Failed to mark DFU mode in bootloader"); | ||
| 53 | RST::sys_reset() | ||
| 54 | } | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | fn control_out( | ||
| 59 | &mut self, | ||
| 60 | req: embassy_usb::control::Request, | ||
| 61 | _: &[u8], | ||
| 62 | ) -> Option<embassy_usb::control::OutResponse> { | ||
| 63 | if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { | ||
| 64 | return None; | ||
| 65 | } | ||
| 66 | |||
| 67 | trace!("Received request {}", req); | ||
| 68 | |||
| 69 | match Request::try_from(req.request) { | ||
| 70 | Ok(Request::Detach) => { | ||
| 71 | trace!("Received DETACH, awaiting USB reset"); | ||
| 72 | self.detach_start = Some(Instant::now()); | ||
| 73 | self.timeout = Some(Duration::from_millis(req.value as u64)); | ||
| 74 | self.state = State::AppDetach; | ||
| 75 | Some(OutResponse::Accepted) | ||
| 76 | } | ||
| 77 | _ => None, | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | fn control_in<'a>( | ||
| 82 | &'a mut self, | ||
| 83 | req: embassy_usb::control::Request, | ||
| 84 | buf: &'a mut [u8], | ||
| 85 | ) -> Option<embassy_usb::control::InResponse<'a>> { | ||
| 86 | if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { | ||
| 87 | return None; | ||
| 88 | } | ||
| 89 | |||
| 90 | trace!("Received request {}", req); | ||
| 91 | |||
| 92 | match Request::try_from(req.request) { | ||
| 93 | Ok(Request::GetStatus) => { | ||
| 94 | buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); | ||
| 95 | Some(InResponse::Accepted(buf)) | ||
| 96 | } | ||
| 97 | _ => None, | ||
| 98 | } | ||
| 99 | } | ||
| 100 | } | ||
| 101 | |||
| 102 | /// An implementation of the USB DFU 1.1 runtime protocol | ||
| 103 | /// | ||
| 104 | /// 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. | ||
| 105 | /// The handler is responsive to DFU GetStatus and Detach commands. | ||
| 106 | /// | ||
| 107 | /// 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 | ||
| 108 | /// it should expose a DFU device, and a software reset will be issued. | ||
| 109 | /// | ||
| 110 | /// 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. | ||
| 111 | pub fn usb_dfu<'d, D: Driver<'d>, STATE: NorFlash, RST: Reset>( | ||
| 112 | builder: &mut Builder<'d, D>, | ||
| 113 | handler: &'d mut Control<'d, STATE, RST>, | ||
| 114 | timeout: Duration, | ||
| 115 | ) { | ||
| 116 | let mut func = builder.function(0x00, 0x00, 0x00); | ||
| 117 | let mut iface = func.interface(); | ||
| 118 | let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_RT, None); | ||
| 119 | let timeout = timeout.as_millis() as u16; | ||
| 120 | alt.descriptor( | ||
| 121 | DESC_DFU_FUNCTIONAL, | ||
| 122 | &[ | ||
| 123 | handler.attrs.bits(), | ||
| 124 | (timeout & 0xff) as u8, | ||
| 125 | ((timeout >> 8) & 0xff) as u8, | ||
| 126 | 0x40, | ||
| 127 | 0x00, // 64B control buffer size for application side | ||
| 128 | 0x10, | ||
| 129 | 0x01, // DFU 1.1 | ||
| 130 | ], | ||
| 131 | ); | ||
| 132 | |||
| 133 | drop(func); | ||
| 134 | builder.handler(handler); | ||
| 135 | } | ||
diff --git a/embassy-usb-dfu/src/bootloader.rs b/embassy-usb-dfu/src/bootloader.rs new file mode 100644 index 000000000..d41e6280d --- /dev/null +++ b/embassy-usb-dfu/src/bootloader.rs | |||
| @@ -0,0 +1,189 @@ | |||
| 1 | use core::marker::PhantomData; | ||
| 2 | |||
| 3 | use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater}; | ||
| 4 | use embassy_usb::control::{InResponse, OutResponse, Recipient, RequestType}; | ||
| 5 | use embassy_usb::driver::Driver; | ||
| 6 | use embassy_usb::{Builder, Handler}; | ||
| 7 | use embedded_storage::nor_flash::{NorFlash, NorFlashErrorKind}; | ||
| 8 | |||
| 9 | use crate::consts::{ | ||
| 10 | DfuAttributes, Request, State, Status, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU, | ||
| 11 | USB_CLASS_APPN_SPEC, | ||
| 12 | }; | ||
| 13 | use crate::Reset; | ||
| 14 | |||
| 15 | /// Internal state for USB DFU | ||
| 16 | pub struct Control<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> { | ||
| 17 | updater: BlockingFirmwareUpdater<'d, DFU, STATE>, | ||
| 18 | attrs: DfuAttributes, | ||
| 19 | state: State, | ||
| 20 | status: Status, | ||
| 21 | offset: usize, | ||
| 22 | _rst: PhantomData<RST>, | ||
| 23 | } | ||
| 24 | |||
| 25 | impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Control<'d, DFU, STATE, RST, BLOCK_SIZE> { | ||
| 26 | pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { | ||
| 27 | Self { | ||
| 28 | updater, | ||
| 29 | attrs, | ||
| 30 | state: State::DfuIdle, | ||
| 31 | status: Status::Ok, | ||
| 32 | offset: 0, | ||
| 33 | _rst: PhantomData, | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | fn reset_state(&mut self) { | ||
| 38 | self.offset = 0; | ||
| 39 | self.state = State::DfuIdle; | ||
| 40 | self.status = Status::Ok; | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<'d, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize> Handler | ||
| 45 | for Control<'d, DFU, STATE, RST, BLOCK_SIZE> | ||
| 46 | { | ||
| 47 | fn control_out( | ||
| 48 | &mut self, | ||
| 49 | req: embassy_usb::control::Request, | ||
| 50 | data: &[u8], | ||
| 51 | ) -> Option<embassy_usb::control::OutResponse> { | ||
| 52 | if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { | ||
| 53 | return None; | ||
| 54 | } | ||
| 55 | match Request::try_from(req.request) { | ||
| 56 | Ok(Request::Abort) => { | ||
| 57 | self.reset_state(); | ||
| 58 | Some(OutResponse::Accepted) | ||
| 59 | } | ||
| 60 | Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => { | ||
| 61 | if req.value == 0 { | ||
| 62 | self.state = State::Download; | ||
| 63 | self.offset = 0; | ||
| 64 | } | ||
| 65 | |||
| 66 | let mut buf = AlignedBuffer([0; BLOCK_SIZE]); | ||
| 67 | buf.as_mut()[..data.len()].copy_from_slice(data); | ||
| 68 | |||
| 69 | if req.length == 0 { | ||
| 70 | match self.updater.mark_updated() { | ||
| 71 | Ok(_) => { | ||
| 72 | self.status = Status::Ok; | ||
| 73 | self.state = State::ManifestSync; | ||
| 74 | } | ||
| 75 | Err(e) => { | ||
| 76 | self.state = State::Error; | ||
| 77 | match e { | ||
| 78 | embassy_boot::FirmwareUpdaterError::Flash(e) => match e { | ||
| 79 | NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, | ||
| 80 | NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress, | ||
| 81 | _ => self.status = Status::ErrUnknown, | ||
| 82 | }, | ||
| 83 | embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify, | ||
| 84 | embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown, | ||
| 85 | } | ||
| 86 | } | ||
| 87 | } | ||
| 88 | } else { | ||
| 89 | if self.state != State::Download { | ||
| 90 | // Unexpected DNLOAD while chip is waiting for a GETSTATUS | ||
| 91 | self.status = Status::ErrUnknown; | ||
| 92 | self.state = State::Error; | ||
| 93 | return Some(OutResponse::Rejected); | ||
| 94 | } | ||
| 95 | match self.updater.write_firmware(self.offset, buf.as_ref()) { | ||
| 96 | Ok(_) => { | ||
| 97 | self.status = Status::Ok; | ||
| 98 | self.state = State::DlSync; | ||
| 99 | self.offset += data.len(); | ||
| 100 | } | ||
| 101 | Err(e) => { | ||
| 102 | self.state = State::Error; | ||
| 103 | match e { | ||
| 104 | embassy_boot::FirmwareUpdaterError::Flash(e) => match e { | ||
| 105 | NorFlashErrorKind::NotAligned => self.status = Status::ErrWrite, | ||
| 106 | NorFlashErrorKind::OutOfBounds => self.status = Status::ErrAddress, | ||
| 107 | _ => self.status = Status::ErrUnknown, | ||
| 108 | }, | ||
| 109 | embassy_boot::FirmwareUpdaterError::Signature(_) => self.status = Status::ErrVerify, | ||
| 110 | embassy_boot::FirmwareUpdaterError::BadState => self.status = Status::ErrUnknown, | ||
| 111 | } | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | Some(OutResponse::Accepted) | ||
| 117 | } | ||
| 118 | Ok(Request::Detach) => Some(OutResponse::Accepted), // Device is already in DFU mode | ||
| 119 | Ok(Request::ClrStatus) => { | ||
| 120 | self.reset_state(); | ||
| 121 | Some(OutResponse::Accepted) | ||
| 122 | } | ||
| 123 | _ => None, | ||
| 124 | } | ||
| 125 | } | ||
| 126 | |||
| 127 | fn control_in<'a>( | ||
| 128 | &'a mut self, | ||
| 129 | req: embassy_usb::control::Request, | ||
| 130 | buf: &'a mut [u8], | ||
| 131 | ) -> Option<embassy_usb::control::InResponse<'a>> { | ||
| 132 | if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { | ||
| 133 | return None; | ||
| 134 | } | ||
| 135 | match Request::try_from(req.request) { | ||
| 136 | Ok(Request::GetStatus) => { | ||
| 137 | //TODO: Configurable poll timeout, ability to add string for Vendor error | ||
| 138 | buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); | ||
| 139 | match self.state { | ||
| 140 | State::DlSync => self.state = State::Download, | ||
| 141 | State::ManifestSync => RST::sys_reset(), | ||
| 142 | _ => {} | ||
| 143 | } | ||
| 144 | |||
| 145 | Some(InResponse::Accepted(&buf[0..6])) | ||
| 146 | } | ||
| 147 | Ok(Request::GetState) => { | ||
| 148 | buf[0] = self.state as u8; | ||
| 149 | Some(InResponse::Accepted(&buf[0..1])) | ||
| 150 | } | ||
| 151 | Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => { | ||
| 152 | //TODO: FirmwareUpdater does not provide a way of reading the active partition, can't upload. | ||
| 153 | Some(InResponse::Rejected) | ||
| 154 | } | ||
| 155 | _ => None, | ||
| 156 | } | ||
| 157 | } | ||
| 158 | } | ||
| 159 | |||
| 160 | /// An implementation of the USB DFU 1.1 protocol | ||
| 161 | /// | ||
| 162 | /// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device | ||
| 163 | /// The handler is responsive to DFU GetState, GetStatus, Abort, and ClrStatus commands, as well as Download if configured by the user. | ||
| 164 | /// | ||
| 165 | /// Once the host has initiated a DFU download operation, the chunks sent by the host will be written to the DFU partition. | ||
| 166 | /// Once the final sync in the manifestation phase has been received, the handler will trigger a system reset to swap the new firmware. | ||
| 167 | pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash, RST: Reset, const BLOCK_SIZE: usize>( | ||
| 168 | builder: &mut Builder<'d, D>, | ||
| 169 | handler: &'d mut Control<'d, DFU, STATE, RST, BLOCK_SIZE>, | ||
| 170 | ) { | ||
| 171 | let mut func = builder.function(0x00, 0x00, 0x00); | ||
| 172 | let mut iface = func.interface(); | ||
| 173 | let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None); | ||
| 174 | alt.descriptor( | ||
| 175 | DESC_DFU_FUNCTIONAL, | ||
| 176 | &[ | ||
| 177 | handler.attrs.bits(), | ||
| 178 | 0xc4, | ||
| 179 | 0x09, // 2500ms timeout, doesn't affect operation as DETACH not necessary in bootloader code | ||
| 180 | (BLOCK_SIZE & 0xff) as u8, | ||
| 181 | ((BLOCK_SIZE & 0xff00) >> 8) as u8, | ||
| 182 | 0x10, | ||
| 183 | 0x01, // DFU 1.1 | ||
| 184 | ], | ||
| 185 | ); | ||
| 186 | |||
| 187 | drop(func); | ||
| 188 | builder.handler(handler); | ||
| 189 | } | ||
diff --git a/embassy-usb-dfu/src/consts.rs b/embassy-usb-dfu/src/consts.rs new file mode 100644 index 000000000..b359a107e --- /dev/null +++ b/embassy-usb-dfu/src/consts.rs | |||
| @@ -0,0 +1,95 @@ | |||
| 1 | pub(crate) const USB_CLASS_APPN_SPEC: u8 = 0xFE; | ||
| 2 | pub(crate) const APPN_SPEC_SUBCLASS_DFU: u8 = 0x01; | ||
| 3 | #[allow(unused)] | ||
| 4 | pub(crate) const DFU_PROTOCOL_DFU: u8 = 0x02; | ||
| 5 | #[allow(unused)] | ||
| 6 | pub(crate) const DFU_PROTOCOL_RT: u8 = 0x01; | ||
| 7 | pub(crate) const DESC_DFU_FUNCTIONAL: u8 = 0x21; | ||
| 8 | |||
| 9 | #[cfg(feature = "defmt")] | ||
| 10 | defmt::bitflags! { | ||
| 11 | pub struct DfuAttributes: u8 { | ||
| 12 | const WILL_DETACH = 0b0000_1000; | ||
| 13 | const MANIFESTATION_TOLERANT = 0b0000_0100; | ||
| 14 | const CAN_UPLOAD = 0b0000_0010; | ||
| 15 | const CAN_DOWNLOAD = 0b0000_0001; | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | #[cfg(not(feature = "defmt"))] | ||
| 20 | bitflags::bitflags! { | ||
| 21 | pub struct DfuAttributes: u8 { | ||
| 22 | const WILL_DETACH = 0b0000_1000; | ||
| 23 | const MANIFESTATION_TOLERANT = 0b0000_0100; | ||
| 24 | const CAN_UPLOAD = 0b0000_0010; | ||
| 25 | const CAN_DOWNLOAD = 0b0000_0001; | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | #[derive(Copy, Clone, PartialEq, Eq)] | ||
| 30 | #[repr(u8)] | ||
| 31 | #[allow(unused)] | ||
| 32 | pub enum State { | ||
| 33 | AppIdle = 0, | ||
| 34 | AppDetach = 1, | ||
| 35 | DfuIdle = 2, | ||
| 36 | DlSync = 3, | ||
| 37 | DlBusy = 4, | ||
| 38 | Download = 5, | ||
| 39 | ManifestSync = 6, | ||
| 40 | Manifest = 7, | ||
| 41 | ManifestWaitReset = 8, | ||
| 42 | UploadIdle = 9, | ||
| 43 | Error = 10, | ||
| 44 | } | ||
| 45 | |||
| 46 | #[derive(Copy, Clone, PartialEq, Eq)] | ||
| 47 | #[repr(u8)] | ||
| 48 | #[allow(unused)] | ||
| 49 | pub enum Status { | ||
| 50 | Ok = 0x00, | ||
| 51 | ErrTarget = 0x01, | ||
| 52 | ErrFile = 0x02, | ||
| 53 | ErrWrite = 0x03, | ||
| 54 | ErrErase = 0x04, | ||
| 55 | ErrCheckErased = 0x05, | ||
| 56 | ErrProg = 0x06, | ||
| 57 | ErrVerify = 0x07, | ||
| 58 | ErrAddress = 0x08, | ||
| 59 | ErrNotDone = 0x09, | ||
| 60 | ErrFirmware = 0x0A, | ||
| 61 | ErrVendor = 0x0B, | ||
| 62 | ErrUsbr = 0x0C, | ||
| 63 | ErrPor = 0x0D, | ||
| 64 | ErrUnknown = 0x0E, | ||
| 65 | ErrStalledPkt = 0x0F, | ||
| 66 | } | ||
| 67 | |||
| 68 | #[derive(Copy, Clone, PartialEq, Eq)] | ||
| 69 | #[repr(u8)] | ||
| 70 | pub enum Request { | ||
| 71 | Detach = 0, | ||
| 72 | Dnload = 1, | ||
| 73 | Upload = 2, | ||
| 74 | GetStatus = 3, | ||
| 75 | ClrStatus = 4, | ||
| 76 | GetState = 5, | ||
| 77 | Abort = 6, | ||
| 78 | } | ||
| 79 | |||
| 80 | impl TryFrom<u8> for Request { | ||
| 81 | type Error = (); | ||
| 82 | |||
| 83 | fn try_from(value: u8) -> Result<Self, Self::Error> { | ||
| 84 | match value { | ||
| 85 | 0 => Ok(Request::Detach), | ||
| 86 | 1 => Ok(Request::Dnload), | ||
| 87 | 2 => Ok(Request::Upload), | ||
| 88 | 3 => Ok(Request::GetStatus), | ||
| 89 | 4 => Ok(Request::ClrStatus), | ||
| 90 | 5 => Ok(Request::GetState), | ||
| 91 | 6 => Ok(Request::Abort), | ||
| 92 | _ => Err(()), | ||
| 93 | } | ||
| 94 | } | ||
| 95 | } | ||
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 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | use core::fmt::{Debug, Display, LowerHex}; | ||
| 5 | |||
| 6 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 7 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 8 | |||
| 9 | macro_rules! assert { | ||
| 10 | ($($x:tt)*) => { | ||
| 11 | { | ||
| 12 | #[cfg(not(feature = "defmt"))] | ||
| 13 | ::core::assert!($($x)*); | ||
| 14 | #[cfg(feature = "defmt")] | ||
| 15 | ::defmt::assert!($($x)*); | ||
| 16 | } | ||
| 17 | }; | ||
| 18 | } | ||
| 19 | |||
| 20 | macro_rules! assert_eq { | ||
| 21 | ($($x:tt)*) => { | ||
| 22 | { | ||
| 23 | #[cfg(not(feature = "defmt"))] | ||
| 24 | ::core::assert_eq!($($x)*); | ||
| 25 | #[cfg(feature = "defmt")] | ||
| 26 | ::defmt::assert_eq!($($x)*); | ||
| 27 | } | ||
| 28 | }; | ||
| 29 | } | ||
| 30 | |||
| 31 | macro_rules! assert_ne { | ||
| 32 | ($($x:tt)*) => { | ||
| 33 | { | ||
| 34 | #[cfg(not(feature = "defmt"))] | ||
| 35 | ::core::assert_ne!($($x)*); | ||
| 36 | #[cfg(feature = "defmt")] | ||
| 37 | ::defmt::assert_ne!($($x)*); | ||
| 38 | } | ||
| 39 | }; | ||
| 40 | } | ||
| 41 | |||
| 42 | macro_rules! debug_assert { | ||
| 43 | ($($x:tt)*) => { | ||
| 44 | { | ||
| 45 | #[cfg(not(feature = "defmt"))] | ||
| 46 | ::core::debug_assert!($($x)*); | ||
| 47 | #[cfg(feature = "defmt")] | ||
| 48 | ::defmt::debug_assert!($($x)*); | ||
| 49 | } | ||
| 50 | }; | ||
| 51 | } | ||
| 52 | |||
| 53 | macro_rules! debug_assert_eq { | ||
| 54 | ($($x:tt)*) => { | ||
| 55 | { | ||
| 56 | #[cfg(not(feature = "defmt"))] | ||
| 57 | ::core::debug_assert_eq!($($x)*); | ||
| 58 | #[cfg(feature = "defmt")] | ||
| 59 | ::defmt::debug_assert_eq!($($x)*); | ||
| 60 | } | ||
| 61 | }; | ||
| 62 | } | ||
| 63 | |||
| 64 | macro_rules! debug_assert_ne { | ||
| 65 | ($($x:tt)*) => { | ||
| 66 | { | ||
| 67 | #[cfg(not(feature = "defmt"))] | ||
| 68 | ::core::debug_assert_ne!($($x)*); | ||
| 69 | #[cfg(feature = "defmt")] | ||
| 70 | ::defmt::debug_assert_ne!($($x)*); | ||
| 71 | } | ||
| 72 | }; | ||
| 73 | } | ||
| 74 | |||
| 75 | macro_rules! todo { | ||
| 76 | ($($x:tt)*) => { | ||
| 77 | { | ||
| 78 | #[cfg(not(feature = "defmt"))] | ||
| 79 | ::core::todo!($($x)*); | ||
| 80 | #[cfg(feature = "defmt")] | ||
| 81 | ::defmt::todo!($($x)*); | ||
| 82 | } | ||
| 83 | }; | ||
| 84 | } | ||
| 85 | |||
| 86 | #[cfg(not(feature = "defmt"))] | ||
| 87 | macro_rules! unreachable { | ||
| 88 | ($($x:tt)*) => { | ||
| 89 | ::core::unreachable!($($x)*) | ||
| 90 | }; | ||
| 91 | } | ||
| 92 | |||
| 93 | #[cfg(feature = "defmt")] | ||
| 94 | macro_rules! unreachable { | ||
| 95 | ($($x:tt)*) => { | ||
| 96 | ::defmt::unreachable!($($x)*) | ||
| 97 | }; | ||
| 98 | } | ||
| 99 | |||
| 100 | macro_rules! panic { | ||
| 101 | ($($x:tt)*) => { | ||
| 102 | { | ||
| 103 | #[cfg(not(feature = "defmt"))] | ||
| 104 | ::core::panic!($($x)*); | ||
| 105 | #[cfg(feature = "defmt")] | ||
| 106 | ::defmt::panic!($($x)*); | ||
| 107 | } | ||
| 108 | }; | ||
| 109 | } | ||
| 110 | |||
| 111 | macro_rules! trace { | ||
| 112 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 113 | { | ||
| 114 | #[cfg(feature = "log")] | ||
| 115 | ::log::trace!($s $(, $x)*); | ||
| 116 | #[cfg(feature = "defmt")] | ||
| 117 | ::defmt::trace!($s $(, $x)*); | ||
| 118 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 119 | let _ = ($( & $x ),*); | ||
| 120 | } | ||
| 121 | }; | ||
| 122 | } | ||
| 123 | |||
| 124 | macro_rules! debug { | ||
| 125 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 126 | { | ||
| 127 | #[cfg(feature = "log")] | ||
| 128 | ::log::debug!($s $(, $x)*); | ||
| 129 | #[cfg(feature = "defmt")] | ||
| 130 | ::defmt::debug!($s $(, $x)*); | ||
| 131 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 132 | let _ = ($( & $x ),*); | ||
| 133 | } | ||
| 134 | }; | ||
| 135 | } | ||
| 136 | |||
| 137 | macro_rules! info { | ||
| 138 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 139 | { | ||
| 140 | #[cfg(feature = "log")] | ||
| 141 | ::log::info!($s $(, $x)*); | ||
| 142 | #[cfg(feature = "defmt")] | ||
| 143 | ::defmt::info!($s $(, $x)*); | ||
| 144 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 145 | let _ = ($( & $x ),*); | ||
| 146 | } | ||
| 147 | }; | ||
| 148 | } | ||
| 149 | |||
| 150 | macro_rules! warn { | ||
| 151 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 152 | { | ||
| 153 | #[cfg(feature = "log")] | ||
| 154 | ::log::warn!($s $(, $x)*); | ||
| 155 | #[cfg(feature = "defmt")] | ||
| 156 | ::defmt::warn!($s $(, $x)*); | ||
| 157 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 158 | let _ = ($( & $x ),*); | ||
| 159 | } | ||
| 160 | }; | ||
| 161 | } | ||
| 162 | |||
| 163 | macro_rules! error { | ||
| 164 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 165 | { | ||
| 166 | #[cfg(feature = "log")] | ||
| 167 | ::log::error!($s $(, $x)*); | ||
| 168 | #[cfg(feature = "defmt")] | ||
| 169 | ::defmt::error!($s $(, $x)*); | ||
| 170 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 171 | let _ = ($( & $x ),*); | ||
| 172 | } | ||
| 173 | }; | ||
| 174 | } | ||
| 175 | |||
| 176 | #[cfg(feature = "defmt")] | ||
| 177 | macro_rules! unwrap { | ||
| 178 | ($($x:tt)*) => { | ||
| 179 | ::defmt::unwrap!($($x)*) | ||
| 180 | }; | ||
| 181 | } | ||
| 182 | |||
| 183 | #[cfg(not(feature = "defmt"))] | ||
| 184 | macro_rules! unwrap { | ||
| 185 | ($arg:expr) => { | ||
| 186 | match $crate::fmt::Try::into_result($arg) { | ||
| 187 | ::core::result::Result::Ok(t) => t, | ||
| 188 | ::core::result::Result::Err(e) => { | ||
| 189 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 190 | } | ||
| 191 | } | ||
| 192 | }; | ||
| 193 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 194 | match $crate::fmt::Try::into_result($arg) { | ||
| 195 | ::core::result::Result::Ok(t) => t, | ||
| 196 | ::core::result::Result::Err(e) => { | ||
| 197 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | } | ||
| 202 | |||
| 203 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 204 | pub struct NoneError; | ||
| 205 | |||
| 206 | pub trait Try { | ||
| 207 | type Ok; | ||
| 208 | type Error; | ||
| 209 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 210 | } | ||
| 211 | |||
| 212 | impl<T> Try for Option<T> { | ||
| 213 | type Ok = T; | ||
| 214 | type Error = NoneError; | ||
| 215 | |||
| 216 | #[inline] | ||
| 217 | fn into_result(self) -> Result<T, NoneError> { | ||
| 218 | self.ok_or(NoneError) | ||
| 219 | } | ||
| 220 | } | ||
| 221 | |||
| 222 | impl<T, E> Try for Result<T, E> { | ||
| 223 | type Ok = T; | ||
| 224 | type Error = E; | ||
| 225 | |||
| 226 | #[inline] | ||
| 227 | fn into_result(self) -> Self { | ||
| 228 | self | ||
| 229 | } | ||
| 230 | } | ||
| 231 | |||
| 232 | #[allow(unused)] | ||
| 233 | pub(crate) struct Bytes<'a>(pub &'a [u8]); | ||
| 234 | |||
| 235 | impl<'a> Debug for Bytes<'a> { | ||
| 236 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 237 | write!(f, "{:#02x?}", self.0) | ||
| 238 | } | ||
| 239 | } | ||
| 240 | |||
| 241 | impl<'a> Display for Bytes<'a> { | ||
| 242 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 243 | write!(f, "{:#02x?}", self.0) | ||
| 244 | } | ||
| 245 | } | ||
| 246 | |||
| 247 | impl<'a> LowerHex for Bytes<'a> { | ||
| 248 | fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { | ||
| 249 | write!(f, "{:#02x?}", self.0) | ||
| 250 | } | ||
| 251 | } | ||
| 252 | |||
| 253 | #[cfg(feature = "defmt")] | ||
| 254 | impl<'a> defmt::Format for Bytes<'a> { | ||
| 255 | fn format(&self, fmt: defmt::Formatter) { | ||
| 256 | defmt::write!(fmt, "{:02x}", self.0) | ||
| 257 | } | ||
| 258 | } | ||
diff --git a/embassy-usb-dfu/src/lib.rs b/embassy-usb-dfu/src/lib.rs new file mode 100644 index 000000000..389bb33f2 --- /dev/null +++ b/embassy-usb-dfu/src/lib.rs | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | #![no_std] | ||
| 2 | mod fmt; | ||
| 3 | |||
| 4 | pub mod consts; | ||
| 5 | |||
| 6 | #[cfg(feature = "dfu")] | ||
| 7 | mod bootloader; | ||
| 8 | #[cfg(feature = "dfu")] | ||
| 9 | pub use self::bootloader::*; | ||
| 10 | |||
| 11 | #[cfg(feature = "application")] | ||
| 12 | mod application; | ||
| 13 | #[cfg(feature = "application")] | ||
| 14 | pub use self::application::*; | ||
| 15 | |||
| 16 | #[cfg(any( | ||
| 17 | all(feature = "dfu", feature = "application"), | ||
| 18 | not(any(feature = "dfu", feature = "application")) | ||
| 19 | ))] | ||
| 20 | compile_error!("usb-dfu must be compiled with exactly one of `bootloader`, or `application` features"); | ||
| 21 | |||
| 22 | /// Provides a platform-agnostic interface for initiating a system reset. | ||
| 23 | /// | ||
| 24 | /// This crate exposes `ResetImmediate` when compiled with cortex-m or esp32c3 support, which immediately issues a | ||
| 25 | /// reset request without interfacing with any other peripherals. | ||
| 26 | /// | ||
| 27 | /// If alternate behaviour is desired, a custom implementation of Reset can be provided as a type argument to the usb_dfu function. | ||
| 28 | pub trait Reset { | ||
| 29 | fn sys_reset() -> !; | ||
| 30 | } | ||
| 31 | |||
| 32 | #[cfg(feature = "esp32c3-hal")] | ||
| 33 | pub struct ResetImmediate; | ||
| 34 | |||
| 35 | #[cfg(feature = "esp32c3-hal")] | ||
| 36 | impl Reset for ResetImmediate { | ||
| 37 | fn sys_reset() -> ! { | ||
| 38 | esp32c3_hal::reset::software_reset(); | ||
| 39 | loop {} | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | #[cfg(feature = "cortex-m")] | ||
| 44 | pub struct ResetImmediate; | ||
| 45 | |||
| 46 | #[cfg(feature = "cortex-m")] | ||
| 47 | impl Reset for ResetImmediate { | ||
| 48 | fn sys_reset() -> ! { | ||
| 49 | cortex_m::peripheral::SCB::sys_reset() | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/examples/boot/application/stm32wb-dfu/.cargo/config.toml b/examples/boot/application/stm32wb-dfu/.cargo/config.toml new file mode 100644 index 000000000..4f8094ff2 --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/.cargo/config.toml | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | [target.'cfg(all(target_arch = "arm", target_os = "none"))'] | ||
| 2 | # replace your chip as listed in `probe-rs chip list` | ||
| 3 | runner = "probe-rs run --chip STM32WLE5JCIx" | ||
| 4 | |||
| 5 | [build] | ||
| 6 | target = "thumbv7em-none-eabihf" | ||
| 7 | |||
| 8 | [env] | ||
| 9 | DEFMT_LOG = "trace" | ||
diff --git a/examples/boot/application/stm32wb-dfu/Cargo.toml b/examples/boot/application/stm32wb-dfu/Cargo.toml new file mode 100644 index 000000000..f6beea498 --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/Cargo.toml | |||
| @@ -0,0 +1,32 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-boot-stm32wb-dfu-examples" | ||
| 4 | version = "0.1.0" | ||
| 5 | license = "MIT OR Apache-2.0" | ||
| 6 | |||
| 7 | [dependencies] | ||
| 8 | embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } | ||
| 9 | embassy-executor = { version = "0.4.0", path = "../../../../embassy-executor", features = ["arch-cortex-m", "executor-thread", "nightly", "integrated-timers"] } | ||
| 10 | embassy-time = { version = "0.2", path = "../../../../embassy-time", features = [ "tick-hz-32_768"] } | ||
| 11 | embassy-stm32 = { version = "0.1.0", path = "../../../../embassy-stm32", features = ["stm32wb55rg", "time-driver-any", "exti"] } | ||
| 12 | embassy-boot-stm32 = { version = "0.1.0", path = "../../../../embassy-boot/stm32", features = [] } | ||
| 13 | embassy-embedded-hal = { version = "0.1.0", path = "../../../../embassy-embedded-hal" } | ||
| 14 | embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb" } | ||
| 15 | embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["application", "cortex-m"] } | ||
| 16 | |||
| 17 | defmt = { version = "0.3", optional = true } | ||
| 18 | defmt-rtt = { version = "0.4", optional = true } | ||
| 19 | panic-reset = { version = "0.1.1" } | ||
| 20 | embedded-hal = { version = "0.2.6" } | ||
| 21 | |||
| 22 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | ||
| 23 | cortex-m-rt = "0.7.0" | ||
| 24 | |||
| 25 | [features] | ||
| 26 | defmt = [ | ||
| 27 | "dep:defmt", | ||
| 28 | "dep:defmt-rtt", | ||
| 29 | "embassy-stm32/defmt", | ||
| 30 | "embassy-boot-stm32/defmt", | ||
| 31 | "embassy-sync/defmt", | ||
| 32 | ] | ||
diff --git a/examples/boot/application/stm32wb-dfu/README.md b/examples/boot/application/stm32wb-dfu/README.md new file mode 100644 index 000000000..c8dce0387 --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/README.md | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | # Examples using bootloader | ||
| 2 | |||
| 3 | Example for STM32WL demonstrating the bootloader. The example consists of application binaries, 'a' | ||
| 4 | which allows you to press a button to start the DFU process, and 'b' which is the updated | ||
| 5 | application. | ||
| 6 | |||
| 7 | |||
| 8 | ## Prerequisites | ||
| 9 | |||
| 10 | * `cargo-binutils` | ||
| 11 | * `cargo-flash` | ||
| 12 | * `embassy-boot-stm32` | ||
| 13 | |||
| 14 | ## Usage | ||
| 15 | |||
| 16 | ``` | ||
| 17 | # Flash bootloader | ||
| 18 | cargo flash --manifest-path ../../bootloader/stm32/Cargo.toml --release --features embassy-stm32/stm32wl55jc-cm4 --chip STM32WLE5JCIx | ||
| 19 | # Build 'b' | ||
| 20 | cargo build --release --bin b | ||
| 21 | # Generate binary for 'b' | ||
| 22 | cargo objcopy --release --bin b -- -O binary b.bin | ||
| 23 | ``` | ||
| 24 | |||
| 25 | # Flash `a` (which includes b.bin) | ||
| 26 | |||
| 27 | ``` | ||
| 28 | cargo flash --release --bin a --chip STM32WLE5JCIx | ||
| 29 | ``` | ||
diff --git a/examples/boot/application/stm32wb-dfu/build.rs b/examples/boot/application/stm32wb-dfu/build.rs new file mode 100644 index 000000000..e1da69328 --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/build.rs | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | //! This build script copies the `memory.x` file from the crate root into | ||
| 2 | //! a directory where the linker can always find it at build time. | ||
| 3 | //! For many projects this is optional, as the linker always searches the | ||
| 4 | //! project root directory -- wherever `Cargo.toml` is. However, if you | ||
| 5 | //! are using a workspace or have a more complicated build setup, this | ||
| 6 | //! build script becomes required. Additionally, by requesting that | ||
| 7 | //! Cargo re-run the build script whenever `memory.x` is changed, | ||
| 8 | //! updating `memory.x` ensures a rebuild of the application with the | ||
| 9 | //! new memory settings. | ||
| 10 | |||
| 11 | use std::env; | ||
| 12 | use std::fs::File; | ||
| 13 | use std::io::Write; | ||
| 14 | use std::path::PathBuf; | ||
| 15 | |||
| 16 | fn main() { | ||
| 17 | // Put `memory.x` in our output directory and ensure it's | ||
| 18 | // on the linker search path. | ||
| 19 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 20 | File::create(out.join("memory.x")) | ||
| 21 | .unwrap() | ||
| 22 | .write_all(include_bytes!("memory.x")) | ||
| 23 | .unwrap(); | ||
| 24 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 25 | |||
| 26 | // By default, Cargo will re-run a build script whenever | ||
| 27 | // any file in the project changes. By specifying `memory.x` | ||
| 28 | // here, we ensure the build script is only re-run when | ||
| 29 | // `memory.x` is changed. | ||
| 30 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 31 | |||
| 32 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 33 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 34 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 35 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/examples/boot/application/stm32wb-dfu/memory.x b/examples/boot/application/stm32wb-dfu/memory.x new file mode 100644 index 000000000..ff1b800d2 --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/memory.x | |||
| @@ -0,0 +1,15 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | BOOTLOADER : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | FLASH : ORIGIN = 0x08008000, LENGTH = 128K | ||
| 7 | DFU : ORIGIN = 0x08028000, LENGTH = 132K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 32K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(BOOTLOADER); | ||
| 13 | |||
| 14 | __bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(BOOTLOADER); | ||
| 15 | __bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(BOOTLOADER); | ||
diff --git a/examples/boot/application/stm32wb-dfu/src/main.rs b/examples/boot/application/stm32wb-dfu/src/main.rs new file mode 100644 index 000000000..fbecbf23b --- /dev/null +++ b/examples/boot/application/stm32wb-dfu/src/main.rs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | #![feature(type_alias_impl_trait)] | ||
| 4 | |||
| 5 | use core::cell::RefCell; | ||
| 6 | |||
| 7 | #[cfg(feature = "defmt-rtt")] | ||
| 8 | use defmt_rtt::*; | ||
| 9 | use embassy_boot_stm32::{AlignedBuffer, BlockingFirmwareState, FirmwareUpdaterConfig}; | ||
| 10 | use embassy_executor::Spawner; | ||
| 11 | use embassy_stm32::flash::{Flash, WRITE_SIZE}; | ||
| 12 | use embassy_stm32::rcc::WPAN_DEFAULT; | ||
| 13 | use embassy_stm32::usb::{self, Driver}; | ||
| 14 | use embassy_stm32::{bind_interrupts, peripherals}; | ||
| 15 | use embassy_sync::blocking_mutex::Mutex; | ||
| 16 | use embassy_time::Duration; | ||
| 17 | use embassy_usb::Builder; | ||
| 18 | use embassy_usb_dfu::consts::DfuAttributes; | ||
| 19 | use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; | ||
| 20 | use panic_reset as _; | ||
| 21 | |||
| 22 | bind_interrupts!(struct Irqs { | ||
| 23 | USB_LP => usb::InterruptHandler<peripherals::USB>; | ||
| 24 | }); | ||
| 25 | |||
| 26 | #[embassy_executor::main] | ||
| 27 | async fn main(_spawner: Spawner) { | ||
| 28 | let mut config = embassy_stm32::Config::default(); | ||
| 29 | config.rcc = WPAN_DEFAULT; | ||
| 30 | let p = embassy_stm32::init(config); | ||
| 31 | let flash = Flash::new_blocking(p.FLASH); | ||
| 32 | let flash = Mutex::new(RefCell::new(flash)); | ||
| 33 | |||
| 34 | let config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); | ||
| 35 | let mut magic = AlignedBuffer([0; WRITE_SIZE]); | ||
| 36 | let mut firmware_state = BlockingFirmwareState::from_config(config, &mut magic.0); | ||
| 37 | firmware_state.mark_booted().expect("Failed to mark booted"); | ||
| 38 | |||
| 39 | let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); | ||
| 40 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 41 | config.manufacturer = Some("Embassy"); | ||
| 42 | config.product = Some("USB-DFU Runtime example"); | ||
| 43 | config.serial_number = Some("1235678"); | ||
| 44 | |||
| 45 | let mut device_descriptor = [0; 256]; | ||
| 46 | let mut config_descriptor = [0; 256]; | ||
| 47 | let mut bos_descriptor = [0; 256]; | ||
| 48 | let mut control_buf = [0; 64]; | ||
| 49 | let mut state = Control::new(firmware_state, DfuAttributes::CAN_DOWNLOAD); | ||
| 50 | let mut builder = Builder::new( | ||
| 51 | driver, | ||
| 52 | config, | ||
| 53 | &mut device_descriptor, | ||
| 54 | &mut config_descriptor, | ||
| 55 | &mut bos_descriptor, | ||
| 56 | &mut [], | ||
| 57 | &mut control_buf, | ||
| 58 | ); | ||
| 59 | |||
| 60 | usb_dfu::<_, _, ResetImmediate>(&mut builder, &mut state, Duration::from_millis(2500)); | ||
| 61 | |||
| 62 | let mut dev = builder.build(); | ||
| 63 | dev.run().await | ||
| 64 | } | ||
diff --git a/examples/boot/bootloader/stm32wb-dfu/Cargo.toml b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml new file mode 100644 index 000000000..ada073970 --- /dev/null +++ b/examples/boot/bootloader/stm32wb-dfu/Cargo.toml | |||
| @@ -0,0 +1,63 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "stm32wb-dfu-bootloader-example" | ||
| 4 | version = "0.1.0" | ||
| 5 | description = "Example USB DFUbootloader for the STM32WB series of chips" | ||
| 6 | license = "MIT OR Apache-2.0" | ||
| 7 | |||
| 8 | [dependencies] | ||
| 9 | defmt = { version = "0.3", optional = true } | ||
| 10 | defmt-rtt = { version = "0.4", optional = true } | ||
| 11 | |||
| 12 | embassy-stm32 = { path = "../../../../embassy-stm32", features = ["stm32wb55rg"] } | ||
| 13 | embassy-boot-stm32 = { path = "../../../../embassy-boot/stm32" } | ||
| 14 | cortex-m = { version = "0.7.6", features = ["inline-asm", "critical-section-single-core"] } | ||
| 15 | embassy-sync = { version = "0.5.0", path = "../../../../embassy-sync" } | ||
| 16 | cortex-m-rt = { version = "0.7" } | ||
| 17 | embedded-storage = "0.3.1" | ||
| 18 | embedded-storage-async = "0.4.0" | ||
| 19 | cfg-if = "1.0.0" | ||
| 20 | embassy-usb-dfu = { version = "0.1.0", path = "../../../../embassy-usb-dfu", features = ["dfu", "cortex-m"] } | ||
| 21 | embassy-usb = { version = "0.1.0", path = "../../../../embassy-usb", default-features = false } | ||
| 22 | embassy-futures = { version = "0.1.1", path = "../../../../embassy-futures" } | ||
| 23 | |||
| 24 | [features] | ||
| 25 | defmt = [ | ||
| 26 | "dep:defmt", | ||
| 27 | "embassy-boot-stm32/defmt", | ||
| 28 | "embassy-stm32/defmt", | ||
| 29 | "embassy-usb/defmt", | ||
| 30 | "embassy-usb-dfu/defmt" | ||
| 31 | ] | ||
| 32 | debug = ["defmt-rtt", "defmt"] | ||
| 33 | |||
| 34 | [profile.dev] | ||
| 35 | debug = 2 | ||
| 36 | debug-assertions = true | ||
| 37 | incremental = false | ||
| 38 | opt-level = 'z' | ||
| 39 | overflow-checks = true | ||
| 40 | |||
| 41 | [profile.release] | ||
| 42 | codegen-units = 1 | ||
| 43 | debug = 2 | ||
| 44 | debug-assertions = false | ||
| 45 | incremental = false | ||
| 46 | lto = 'fat' | ||
| 47 | opt-level = 'z' | ||
| 48 | overflow-checks = false | ||
| 49 | |||
| 50 | # do not optimize proc-macro crates = faster builds from scratch | ||
| 51 | [profile.dev.build-override] | ||
| 52 | codegen-units = 8 | ||
| 53 | debug = false | ||
| 54 | debug-assertions = false | ||
| 55 | opt-level = 0 | ||
| 56 | overflow-checks = false | ||
| 57 | |||
| 58 | [profile.release.build-override] | ||
| 59 | codegen-units = 8 | ||
| 60 | debug = false | ||
| 61 | debug-assertions = false | ||
| 62 | opt-level = 0 | ||
| 63 | overflow-checks = false | ||
diff --git a/examples/boot/bootloader/stm32wb-dfu/README.md b/examples/boot/bootloader/stm32wb-dfu/README.md new file mode 100644 index 000000000..a82b730b9 --- /dev/null +++ b/examples/boot/bootloader/stm32wb-dfu/README.md | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | # Bootloader for STM32 | ||
| 2 | |||
| 3 | The bootloader uses `embassy-boot` to interact with the flash. | ||
| 4 | |||
| 5 | # Usage | ||
| 6 | |||
| 7 | Flash the bootloader | ||
| 8 | |||
| 9 | ``` | ||
| 10 | cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx | ||
| 11 | ``` | ||
diff --git a/examples/boot/bootloader/stm32wb-dfu/build.rs b/examples/boot/bootloader/stm32wb-dfu/build.rs new file mode 100644 index 000000000..fd605991f --- /dev/null +++ b/examples/boot/bootloader/stm32wb-dfu/build.rs | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | use std::env; | ||
| 2 | use std::fs::File; | ||
| 3 | use std::io::Write; | ||
| 4 | use std::path::PathBuf; | ||
| 5 | |||
| 6 | fn main() { | ||
| 7 | // Put `memory.x` in our output directory and ensure it's | ||
| 8 | // on the linker search path. | ||
| 9 | let out = &PathBuf::from(env::var_os("OUT_DIR").unwrap()); | ||
| 10 | File::create(out.join("memory.x")) | ||
| 11 | .unwrap() | ||
| 12 | .write_all(include_bytes!("memory.x")) | ||
| 13 | .unwrap(); | ||
| 14 | println!("cargo:rustc-link-search={}", out.display()); | ||
| 15 | |||
| 16 | // By default, Cargo will re-run a build script whenever | ||
| 17 | // any file in the project changes. By specifying `memory.x` | ||
| 18 | // here, we ensure the build script is only re-run when | ||
| 19 | // `memory.x` is changed. | ||
| 20 | println!("cargo:rerun-if-changed=memory.x"); | ||
| 21 | |||
| 22 | println!("cargo:rustc-link-arg-bins=--nmagic"); | ||
| 23 | println!("cargo:rustc-link-arg-bins=-Tlink.x"); | ||
| 24 | if env::var("CARGO_FEATURE_DEFMT").is_ok() { | ||
| 25 | println!("cargo:rustc-link-arg-bins=-Tdefmt.x"); | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/examples/boot/bootloader/stm32wb-dfu/memory.x b/examples/boot/bootloader/stm32wb-dfu/memory.x new file mode 100644 index 000000000..858062631 --- /dev/null +++ b/examples/boot/bootloader/stm32wb-dfu/memory.x | |||
| @@ -0,0 +1,18 @@ | |||
| 1 | MEMORY | ||
| 2 | { | ||
| 3 | /* NOTE 1 K = 1 KiBi = 1024 bytes */ | ||
| 4 | FLASH : ORIGIN = 0x08000000, LENGTH = 24K | ||
| 5 | BOOTLOADER_STATE : ORIGIN = 0x08006000, LENGTH = 4K | ||
| 6 | ACTIVE : ORIGIN = 0x08008000, LENGTH = 128K | ||
| 7 | DFU : ORIGIN = 0x08028000, LENGTH = 132K | ||
| 8 | RAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16K | ||
| 9 | } | ||
| 10 | |||
| 11 | __bootloader_state_start = ORIGIN(BOOTLOADER_STATE) - ORIGIN(FLASH); | ||
| 12 | __bootloader_state_end = ORIGIN(BOOTLOADER_STATE) + LENGTH(BOOTLOADER_STATE) - ORIGIN(FLASH); | ||
| 13 | |||
| 14 | __bootloader_active_start = ORIGIN(ACTIVE) - ORIGIN(FLASH); | ||
| 15 | __bootloader_active_end = ORIGIN(ACTIVE) + LENGTH(ACTIVE) - ORIGIN(FLASH); | ||
| 16 | |||
| 17 | __bootloader_dfu_start = ORIGIN(DFU) - ORIGIN(FLASH); | ||
| 18 | __bootloader_dfu_end = ORIGIN(DFU) + LENGTH(DFU) - ORIGIN(FLASH); | ||
diff --git a/examples/boot/bootloader/stm32wb-dfu/src/main.rs b/examples/boot/bootloader/stm32wb-dfu/src/main.rs new file mode 100644 index 000000000..a7ab813b6 --- /dev/null +++ b/examples/boot/bootloader/stm32wb-dfu/src/main.rs | |||
| @@ -0,0 +1,93 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![no_main] | ||
| 3 | |||
| 4 | use core::cell::RefCell; | ||
| 5 | |||
| 6 | use cortex_m_rt::{entry, exception}; | ||
| 7 | #[cfg(feature = "defmt")] | ||
| 8 | use defmt_rtt as _; | ||
| 9 | use embassy_boot_stm32::*; | ||
| 10 | use embassy_stm32::flash::{Flash, BANK1_REGION, WRITE_SIZE}; | ||
| 11 | use embassy_stm32::rcc::WPAN_DEFAULT; | ||
| 12 | use embassy_stm32::usb::Driver; | ||
| 13 | use embassy_stm32::{bind_interrupts, peripherals, usb}; | ||
| 14 | use embassy_sync::blocking_mutex::Mutex; | ||
| 15 | use embassy_usb::Builder; | ||
| 16 | use embassy_usb_dfu::consts::DfuAttributes; | ||
| 17 | use embassy_usb_dfu::{usb_dfu, Control, ResetImmediate}; | ||
| 18 | |||
| 19 | bind_interrupts!(struct Irqs { | ||
| 20 | USB_LP => usb::InterruptHandler<peripherals::USB>; | ||
| 21 | }); | ||
| 22 | |||
| 23 | #[entry] | ||
| 24 | fn main() -> ! { | ||
| 25 | let mut config = embassy_stm32::Config::default(); | ||
| 26 | config.rcc = WPAN_DEFAULT; | ||
| 27 | let p = embassy_stm32::init(config); | ||
| 28 | |||
| 29 | // Prevent a hard fault when accessing flash 'too early' after boot. | ||
| 30 | #[cfg(feature = "defmt")] | ||
| 31 | for _ in 0..10000000 { | ||
| 32 | cortex_m::asm::nop(); | ||
| 33 | } | ||
| 34 | |||
| 35 | let layout = Flash::new_blocking(p.FLASH).into_blocking_regions(); | ||
| 36 | let flash = Mutex::new(RefCell::new(layout.bank1_region)); | ||
| 37 | |||
| 38 | let config = BootLoaderConfig::from_linkerfile_blocking(&flash); | ||
| 39 | let active_offset = config.active.offset(); | ||
| 40 | let bl = BootLoader::prepare::<_, _, _, 2048>(config); | ||
| 41 | if bl.state == State::DfuDetach { | ||
| 42 | let driver = Driver::new(p.USB, Irqs, p.PA12, p.PA11); | ||
| 43 | let mut config = embassy_usb::Config::new(0xc0de, 0xcafe); | ||
| 44 | config.manufacturer = Some("Embassy"); | ||
| 45 | config.product = Some("USB-DFU Bootloader example"); | ||
| 46 | config.serial_number = Some("1235678"); | ||
| 47 | |||
| 48 | let fw_config = FirmwareUpdaterConfig::from_linkerfile_blocking(&flash); | ||
| 49 | let mut buffer = AlignedBuffer([0; WRITE_SIZE]); | ||
| 50 | let updater = BlockingFirmwareUpdater::new(fw_config, &mut buffer.0[..]); | ||
| 51 | |||
| 52 | let mut device_descriptor = [0; 256]; | ||
| 53 | let mut config_descriptor = [0; 256]; | ||
| 54 | let mut bos_descriptor = [0; 256]; | ||
| 55 | let mut control_buf = [0; 4096]; | ||
| 56 | let mut state = Control::new(updater, DfuAttributes::CAN_DOWNLOAD); | ||
| 57 | let mut builder = Builder::new( | ||
| 58 | driver, | ||
| 59 | config, | ||
| 60 | &mut device_descriptor, | ||
| 61 | &mut config_descriptor, | ||
| 62 | &mut bos_descriptor, | ||
| 63 | &mut [], | ||
| 64 | &mut control_buf, | ||
| 65 | ); | ||
| 66 | |||
| 67 | usb_dfu::<_, _, _, ResetImmediate, 4096>(&mut builder, &mut state); | ||
| 68 | |||
| 69 | let mut dev = builder.build(); | ||
| 70 | embassy_futures::block_on(dev.run()); | ||
| 71 | } | ||
| 72 | |||
| 73 | unsafe { bl.load(BANK1_REGION.base + active_offset) } | ||
| 74 | } | ||
| 75 | |||
| 76 | #[no_mangle] | ||
| 77 | #[cfg_attr(target_os = "none", link_section = ".HardFault.user")] | ||
| 78 | unsafe extern "C" fn HardFault() { | ||
| 79 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 80 | } | ||
| 81 | |||
| 82 | #[exception] | ||
| 83 | unsafe fn DefaultHandler(_: i16) -> ! { | ||
| 84 | const SCB_ICSR: *const u32 = 0xE000_ED04 as *const u32; | ||
| 85 | let irqn = core::ptr::read_volatile(SCB_ICSR) as u8 as i16 - 16; | ||
| 86 | |||
| 87 | panic!("DefaultHandler #{:?}", irqn); | ||
| 88 | } | ||
| 89 | |||
| 90 | #[panic_handler] | ||
| 91 | fn panic(_info: &core::panic::PanicInfo) -> ! { | ||
| 92 | cortex_m::asm::udf(); | ||
| 93 | } | ||
