diff options
| author | Quentin Smith <[email protected]> | 2023-07-17 21:31:43 -0400 |
|---|---|---|
| committer | Quentin Smith <[email protected]> | 2023-07-17 21:31:43 -0400 |
| commit | 6f02403184eb7fb7990fb88fc9df9c4328a690a3 (patch) | |
| tree | 748f510e190bb2724750507a6e69ed1a8e08cb20 /embassy-boot | |
| parent | d896f80405aa8963877049ed999e4aba25d6e2bb (diff) | |
| parent | 6b5df4523aa1c4902f02e803450ae4b418e0e3ca (diff) | |
Merge remote-tracking branch 'origin/main' into nrf-pdm
Diffstat (limited to 'embassy-boot')
25 files changed, 2331 insertions, 1037 deletions
diff --git a/embassy-boot/boot/Cargo.toml b/embassy-boot/boot/Cargo.toml index a42f88688..415d7960f 100644 --- a/embassy-boot/boot/Cargo.toml +++ b/embassy-boot/boot/Cargo.toml | |||
| @@ -1,25 +1,56 @@ | |||
| 1 | [package] | 1 | [package] |
| 2 | edition = "2021" | 2 | edition = "2021" |
| 3 | name = "embassy-boot" | 3 | name = "embassy-boot" |
| 4 | version = "0.1.0" | 4 | version = "0.1.1" |
| 5 | description = "Bootloader using Embassy" | 5 | description = "A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks." |
| 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 | ] | ||
| 6 | 13 | ||
| 7 | [package.metadata.embassy_docs] | 14 | [package.metadata.embassy_docs] |
| 8 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" | 15 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-v$VERSION/embassy-boot/boot/src/" |
| 9 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" | 16 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/boot/src/" |
| 10 | target = "thumbv7em-none-eabi" | 17 | target = "thumbv7em-none-eabi" |
| 18 | features = ["defmt"] | ||
| 19 | |||
| 20 | [package.metadata.docs.rs] | ||
| 21 | features = ["defmt"] | ||
| 11 | 22 | ||
| 12 | [lib] | 23 | [lib] |
| 13 | 24 | ||
| 14 | [dependencies] | 25 | [dependencies] |
| 15 | defmt = { version = "0.3", optional = true } | 26 | defmt = { version = "0.3", optional = true } |
| 27 | digest = "0.10" | ||
| 16 | log = { version = "0.4", optional = true } | 28 | log = { version = "0.4", optional = true } |
| 17 | embassy-sync = { version = "0.1.0", path = "../../embassy-sync" } | 29 | ed25519-dalek = { version = "1.0.1", default_features = false, features = ["u32_backend"], optional = true } |
| 30 | embassy-embedded-hal = { version = "0.1.0", path = "../../embassy-embedded-hal" } | ||
| 31 | embassy-sync = { version = "0.2.0", path = "../../embassy-sync" } | ||
| 18 | embedded-storage = "0.3.0" | 32 | embedded-storage = "0.3.0" |
| 19 | embedded-storage-async = "0.3.0" | 33 | embedded-storage-async = { version = "0.4.0", optional = true } |
| 34 | salty = { git = "https://github.com/ycrypto/salty.git", rev = "a9f17911a5024698406b75c0fac56ab5ccf6a8c7", optional = true } | ||
| 35 | signature = { version = "1.6.4", default-features = false } | ||
| 20 | 36 | ||
| 21 | [dev-dependencies] | 37 | [dev-dependencies] |
| 22 | log = "0.4" | 38 | log = "0.4" |
| 23 | env_logger = "0.9" | 39 | env_logger = "0.9" |
| 24 | rand = "0.8" | 40 | rand = "0.7" # ed25519-dalek v1.0.1 depends on this exact version |
| 25 | futures = { version = "0.3", features = ["executor"] } | 41 | futures = { version = "0.3", features = ["executor"] } |
| 42 | sha1 = "0.10.5" | ||
| 43 | critical-section = { version = "1.1.1", features = ["std"] } | ||
| 44 | |||
| 45 | [dev-dependencies.ed25519-dalek] | ||
| 46 | default_features = false | ||
| 47 | features = ["rand", "std", "u32_backend"] | ||
| 48 | |||
| 49 | [features] | ||
| 50 | ed25519-dalek = ["dep:ed25519-dalek", "_verify"] | ||
| 51 | ed25519-salty = ["dep:salty", "_verify"] | ||
| 52 | |||
| 53 | nightly = ["dep:embedded-storage-async", "embassy-embedded-hal/nightly"] | ||
| 54 | |||
| 55 | #Internal features | ||
| 56 | _verify = [] | ||
diff --git a/embassy-boot/boot/README.md b/embassy-boot/boot/README.md new file mode 100644 index 000000000..07755bc6c --- /dev/null +++ b/embassy-boot/boot/README.md | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | # embassy-boot | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | A lightweight bootloader supporting firmware updates in a power-fail-safe way, with trial boots and rollbacks. | ||
| 6 | |||
| 7 | The bootloader can be used either as a library or be flashed directly with the default configuration derived from linker scripts. | ||
| 8 | |||
| 9 | By design, the bootloader does not provide any network capabilities. Networking capabilities for fetching new firmware can be provided by the user application, using the bootloader as a library for updating the firmware, or by using the bootloader as a library and adding this capability yourself. | ||
| 10 | |||
| 11 | ## Hardware support | ||
| 12 | |||
| 13 | The bootloader supports different hardware in separate crates: | ||
| 14 | |||
| 15 | * `embassy-boot-nrf` - for the nRF microcontrollers. | ||
| 16 | * `embassy-boot-rp` - for the RP2040 microcontrollers. | ||
| 17 | * `embassy-boot-stm32` - for the STM32 microcontrollers. | ||
| 18 | |||
| 19 | ## Minimum supported Rust version (MSRV) | ||
| 20 | |||
| 21 | `embassy-boot` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 22 | |||
| 23 | ## License | ||
| 24 | |||
| 25 | This work is licensed under either of | ||
| 26 | |||
| 27 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 28 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 29 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 30 | |||
| 31 | at your option. | ||
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs new file mode 100644 index 000000000..a8c19197b --- /dev/null +++ b/embassy-boot/boot/src/boot_loader.rs | |||
| @@ -0,0 +1,421 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | |||
| 3 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 4 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 5 | use embassy_sync::blocking_mutex::Mutex; | ||
| 6 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind}; | ||
| 7 | |||
| 8 | use crate::{State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 9 | |||
| 10 | /// Errors returned by bootloader | ||
| 11 | #[derive(PartialEq, Eq, Debug)] | ||
| 12 | pub enum BootError { | ||
| 13 | /// Error from flash. | ||
| 14 | Flash(NorFlashErrorKind), | ||
| 15 | /// Invalid bootloader magic | ||
| 16 | BadMagic, | ||
| 17 | } | ||
| 18 | |||
| 19 | #[cfg(feature = "defmt")] | ||
| 20 | impl defmt::Format for BootError { | ||
| 21 | fn format(&self, fmt: defmt::Formatter) { | ||
| 22 | match self { | ||
| 23 | BootError::Flash(_) => defmt::write!(fmt, "BootError::Flash(_)"), | ||
| 24 | BootError::BadMagic => defmt::write!(fmt, "BootError::BadMagic"), | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | impl<E> From<E> for BootError | ||
| 30 | where | ||
| 31 | E: NorFlashError, | ||
| 32 | { | ||
| 33 | fn from(error: E) -> Self { | ||
| 34 | BootError::Flash(error.kind()) | ||
| 35 | } | ||
| 36 | } | ||
| 37 | |||
| 38 | /// Bootloader flash configuration holding the three flashes used by the bootloader | ||
| 39 | /// | ||
| 40 | /// If only a single flash is actually used, then that flash should be partitioned into three partitions before use. | ||
| 41 | /// The easiest way to do this is to use [`BootLoaderConfig::from_linkerfile_blocking`] which will partition | ||
| 42 | /// the provided flash according to symbols defined in the linkerfile. | ||
| 43 | pub struct BootLoaderConfig<ACTIVE, DFU, STATE> { | ||
| 44 | /// Flash type used for the active partition - the partition which will be booted from. | ||
| 45 | pub active: ACTIVE, | ||
| 46 | /// Flash type used for the dfu partition - the partition which will be swapped in when requested. | ||
| 47 | pub dfu: DFU, | ||
| 48 | /// Flash type used for the state partition. | ||
| 49 | pub state: STATE, | ||
| 50 | } | ||
| 51 | |||
| 52 | impl<'a, FLASH: NorFlash> | ||
| 53 | BootLoaderConfig< | ||
| 54 | BlockingPartition<'a, NoopRawMutex, FLASH>, | ||
| 55 | BlockingPartition<'a, NoopRawMutex, FLASH>, | ||
| 56 | BlockingPartition<'a, NoopRawMutex, FLASH>, | ||
| 57 | > | ||
| 58 | { | ||
| 59 | /// Create a bootloader config from the flash and address symbols defined in the linkerfile | ||
| 60 | // #[cfg(target_os = "none")] | ||
| 61 | pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { | ||
| 62 | extern "C" { | ||
| 63 | static __bootloader_state_start: u32; | ||
| 64 | static __bootloader_state_end: u32; | ||
| 65 | static __bootloader_active_start: u32; | ||
| 66 | static __bootloader_active_end: u32; | ||
| 67 | static __bootloader_dfu_start: u32; | ||
| 68 | static __bootloader_dfu_end: u32; | ||
| 69 | } | ||
| 70 | |||
| 71 | let active = unsafe { | ||
| 72 | let start = &__bootloader_active_start as *const u32 as u32; | ||
| 73 | let end = &__bootloader_active_end as *const u32 as u32; | ||
| 74 | trace!("ACTIVE: 0x{:x} - 0x{:x}", start, end); | ||
| 75 | |||
| 76 | BlockingPartition::new(flash, start, end - start) | ||
| 77 | }; | ||
| 78 | let dfu = unsafe { | ||
| 79 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 80 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 81 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 82 | |||
| 83 | BlockingPartition::new(flash, start, end - start) | ||
| 84 | }; | ||
| 85 | let state = unsafe { | ||
| 86 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 87 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 88 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 89 | |||
| 90 | BlockingPartition::new(flash, start, end - start) | ||
| 91 | }; | ||
| 92 | |||
| 93 | Self { active, dfu, state } | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | /// BootLoader works with any flash implementing embedded_storage. | ||
| 98 | pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> { | ||
| 99 | active: ACTIVE, | ||
| 100 | dfu: DFU, | ||
| 101 | /// The state partition has the following format: | ||
| 102 | /// All ranges are in multiples of WRITE_SIZE bytes. | ||
| 103 | /// | Range | Description | | ||
| 104 | /// | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | | ||
| 105 | /// | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. | | ||
| 106 | /// | 2..2 + N | Progress index used while swapping or reverting | ||
| 107 | state: STATE, | ||
| 108 | } | ||
| 109 | |||
| 110 | impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash> BootLoader<ACTIVE, DFU, STATE> { | ||
| 111 | /// Get the page size which is the "unit of operation" within the bootloader. | ||
| 112 | const PAGE_SIZE: u32 = if ACTIVE::ERASE_SIZE > DFU::ERASE_SIZE { | ||
| 113 | ACTIVE::ERASE_SIZE as u32 | ||
| 114 | } else { | ||
| 115 | DFU::ERASE_SIZE as u32 | ||
| 116 | }; | ||
| 117 | |||
| 118 | /// Create a new instance of a bootloader with the flash partitions. | ||
| 119 | /// | ||
| 120 | /// - All partitions must be aligned with the PAGE_SIZE const generic parameter. | ||
| 121 | /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition. | ||
| 122 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 123 | Self { | ||
| 124 | active: config.active, | ||
| 125 | dfu: config.dfu, | ||
| 126 | state: config.state, | ||
| 127 | } | ||
| 128 | } | ||
| 129 | |||
| 130 | /// Perform necessary boot preparations like swapping images. | ||
| 131 | /// | ||
| 132 | /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap | ||
| 133 | /// algorithm to work correctly. | ||
| 134 | /// | ||
| 135 | /// The provided aligned_buf argument must satisfy any alignment requirements | ||
| 136 | /// given by the partition flashes. All flash operations will use this buffer. | ||
| 137 | /// | ||
| 138 | /// SWAPPING | ||
| 139 | /// | ||
| 140 | /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. | ||
| 141 | /// The swap index contains the copy progress, as to allow continuation of the copy process on | ||
| 142 | /// power failure. The index counter is represented within 1 or more pages (depending on total | ||
| 143 | /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) | ||
| 144 | /// contains a zero value. This ensures that index updates can be performed atomically and | ||
| 145 | /// avoid a situation where the wrong index value is set (page write size is "atomic"). | ||
| 146 | /// | ||
| 147 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 148 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 149 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 150 | /// | Active | 0 | 1 | 2 | 3 | - | | ||
| 151 | /// | DFU | 0 | 3 | 2 | 1 | X | | ||
| 152 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 153 | /// | ||
| 154 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is | ||
| 155 | /// as follows: | ||
| 156 | /// | ||
| 157 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 158 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 159 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 160 | /// | Active | 1 | 1 | 2 | 1 | - | | ||
| 161 | /// | DFU | 1 | 3 | 2 | 1 | 3 | | ||
| 162 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 163 | /// | ||
| 164 | /// The next iteration performs the same steps | ||
| 165 | /// | ||
| 166 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 167 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 168 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 169 | /// | Active | 2 | 1 | 2 | 1 | - | | ||
| 170 | /// | DFU | 2 | 3 | 2 | 2 | 3 | | ||
| 171 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 172 | /// | ||
| 173 | /// And again until we're done | ||
| 174 | /// | ||
| 175 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 176 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 177 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 178 | /// | Active | 3 | 3 | 2 | 1 | - | | ||
| 179 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 180 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 181 | /// | ||
| 182 | /// REVERTING | ||
| 183 | /// | ||
| 184 | /// The reverting algorithm uses the swap index to discover that images were swapped, but that | ||
| 185 | /// the application failed to mark the boot successful. In this case, the revert algorithm will | ||
| 186 | /// run. | ||
| 187 | /// | ||
| 188 | /// The revert index is located separately from the swap index, to ensure that revert can continue | ||
| 189 | /// on power failure. | ||
| 190 | /// | ||
| 191 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. | ||
| 192 | /// | ||
| 193 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 194 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 195 | //*/ | ||
| 196 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 197 | /// | Active | 3 | 1 | 2 | 1 | - | | ||
| 198 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 199 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 200 | /// | ||
| 201 | /// | ||
| 202 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 203 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 204 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 205 | /// | Active | 3 | 1 | 2 | 1 | - | | ||
| 206 | /// | DFU | 3 | 3 | 2 | 2 | 3 | | ||
| 207 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 208 | /// | ||
| 209 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 210 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 211 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 212 | /// | Active | 3 | 1 | 2 | 3 | - | | ||
| 213 | /// | DFU | 3 | 3 | 2 | 1 | 3 | | ||
| 214 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 215 | /// | ||
| 216 | pub fn prepare_boot(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||
| 217 | // Ensure we have enough progress pages to store copy progress | ||
| 218 | assert_eq!(0, Self::PAGE_SIZE % aligned_buf.len() as u32); | ||
| 219 | assert_eq!(0, Self::PAGE_SIZE % ACTIVE::WRITE_SIZE as u32); | ||
| 220 | assert_eq!(0, Self::PAGE_SIZE % ACTIVE::ERASE_SIZE as u32); | ||
| 221 | assert_eq!(0, Self::PAGE_SIZE % DFU::WRITE_SIZE as u32); | ||
| 222 | assert_eq!(0, Self::PAGE_SIZE % DFU::ERASE_SIZE as u32); | ||
| 223 | assert!(aligned_buf.len() >= STATE::WRITE_SIZE); | ||
| 224 | assert_eq!(0, aligned_buf.len() % ACTIVE::WRITE_SIZE); | ||
| 225 | assert_eq!(0, aligned_buf.len() % DFU::WRITE_SIZE); | ||
| 226 | |||
| 227 | assert_partitions(&self.active, &self.dfu, &self.state, Self::PAGE_SIZE); | ||
| 228 | |||
| 229 | // Copy contents from partition N to active | ||
| 230 | let state = self.read_state(aligned_buf)?; | ||
| 231 | if state == State::Swap { | ||
| 232 | // | ||
| 233 | // Check if we already swapped. If we're in the swap state, this means we should revert | ||
| 234 | // since the app has failed to mark boot as successful | ||
| 235 | // | ||
| 236 | if !self.is_swapped(aligned_buf)? { | ||
| 237 | trace!("Swapping"); | ||
| 238 | self.swap(aligned_buf)?; | ||
| 239 | trace!("Swapping done"); | ||
| 240 | } else { | ||
| 241 | trace!("Reverting"); | ||
| 242 | self.revert(aligned_buf)?; | ||
| 243 | |||
| 244 | let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||
| 245 | |||
| 246 | // Invalidate progress | ||
| 247 | state_word.fill(!STATE_ERASE_VALUE); | ||
| 248 | self.state.write(STATE::WRITE_SIZE as u32, state_word)?; | ||
| 249 | |||
| 250 | // Clear magic and progress | ||
| 251 | self.state.erase(0, self.state.capacity() as u32)?; | ||
| 252 | |||
| 253 | // Set magic | ||
| 254 | state_word.fill(BOOT_MAGIC); | ||
| 255 | self.state.write(0, state_word)?; | ||
| 256 | } | ||
| 257 | } | ||
| 258 | Ok(state) | ||
| 259 | } | ||
| 260 | |||
| 261 | fn is_swapped(&mut self, aligned_buf: &mut [u8]) -> Result<bool, BootError> { | ||
| 262 | let page_count = self.active.capacity() / Self::PAGE_SIZE as usize; | ||
| 263 | let progress = self.current_progress(aligned_buf)?; | ||
| 264 | |||
| 265 | Ok(progress >= page_count * 2) | ||
| 266 | } | ||
| 267 | |||
| 268 | fn current_progress(&mut self, aligned_buf: &mut [u8]) -> Result<usize, BootError> { | ||
| 269 | let write_size = STATE::WRITE_SIZE as u32; | ||
| 270 | let max_index = ((self.state.capacity() - STATE::WRITE_SIZE) / STATE::WRITE_SIZE) - 2; | ||
| 271 | let state_word = &mut aligned_buf[..write_size as usize]; | ||
| 272 | |||
| 273 | self.state.read(write_size, state_word)?; | ||
| 274 | if state_word.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 275 | // Progress is invalid | ||
| 276 | return Ok(max_index); | ||
| 277 | } | ||
| 278 | |||
| 279 | for index in 0..max_index { | ||
| 280 | self.state.read((2 + index) as u32 * write_size, state_word)?; | ||
| 281 | |||
| 282 | if state_word.iter().any(|&b| b == STATE_ERASE_VALUE) { | ||
| 283 | return Ok(index); | ||
| 284 | } | ||
| 285 | } | ||
| 286 | Ok(max_index) | ||
| 287 | } | ||
| 288 | |||
| 289 | fn update_progress(&mut self, progress_index: usize, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||
| 290 | let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||
| 291 | state_word.fill(!STATE_ERASE_VALUE); | ||
| 292 | self.state | ||
| 293 | .write((2 + progress_index) as u32 * STATE::WRITE_SIZE as u32, state_word)?; | ||
| 294 | Ok(()) | ||
| 295 | } | ||
| 296 | |||
| 297 | fn copy_page_once_to_active( | ||
| 298 | &mut self, | ||
| 299 | progress_index: usize, | ||
| 300 | from_offset: u32, | ||
| 301 | to_offset: u32, | ||
| 302 | aligned_buf: &mut [u8], | ||
| 303 | ) -> Result<(), BootError> { | ||
| 304 | if self.current_progress(aligned_buf)? <= progress_index { | ||
| 305 | let page_size = Self::PAGE_SIZE as u32; | ||
| 306 | |||
| 307 | self.active.erase(to_offset, to_offset + page_size)?; | ||
| 308 | |||
| 309 | for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { | ||
| 310 | self.dfu.read(from_offset + offset_in_page as u32, aligned_buf)?; | ||
| 311 | self.active.write(to_offset + offset_in_page as u32, aligned_buf)?; | ||
| 312 | } | ||
| 313 | |||
| 314 | self.update_progress(progress_index, aligned_buf)?; | ||
| 315 | } | ||
| 316 | Ok(()) | ||
| 317 | } | ||
| 318 | |||
| 319 | fn copy_page_once_to_dfu( | ||
| 320 | &mut self, | ||
| 321 | progress_index: usize, | ||
| 322 | from_offset: u32, | ||
| 323 | to_offset: u32, | ||
| 324 | aligned_buf: &mut [u8], | ||
| 325 | ) -> Result<(), BootError> { | ||
| 326 | if self.current_progress(aligned_buf)? <= progress_index { | ||
| 327 | let page_size = Self::PAGE_SIZE as u32; | ||
| 328 | |||
| 329 | self.dfu.erase(to_offset as u32, to_offset + page_size)?; | ||
| 330 | |||
| 331 | for offset_in_page in (0..page_size).step_by(aligned_buf.len()) { | ||
| 332 | self.active.read(from_offset + offset_in_page as u32, aligned_buf)?; | ||
| 333 | self.dfu.write(to_offset + offset_in_page as u32, aligned_buf)?; | ||
| 334 | } | ||
| 335 | |||
| 336 | self.update_progress(progress_index, aligned_buf)?; | ||
| 337 | } | ||
| 338 | Ok(()) | ||
| 339 | } | ||
| 340 | |||
| 341 | fn swap(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||
| 342 | let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; | ||
| 343 | for page_num in 0..page_count { | ||
| 344 | let progress_index = (page_num * 2) as usize; | ||
| 345 | |||
| 346 | // Copy active page to the 'next' DFU page. | ||
| 347 | let active_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||
| 348 | let dfu_to_offset = (page_count - page_num) * Self::PAGE_SIZE; | ||
| 349 | //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); | ||
| 350 | self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; | ||
| 351 | |||
| 352 | // Copy DFU page to the active page | ||
| 353 | let active_to_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||
| 354 | let dfu_from_offset = (page_count - 1 - page_num) * Self::PAGE_SIZE; | ||
| 355 | //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); | ||
| 356 | self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; | ||
| 357 | } | ||
| 358 | |||
| 359 | Ok(()) | ||
| 360 | } | ||
| 361 | |||
| 362 | fn revert(&mut self, aligned_buf: &mut [u8]) -> Result<(), BootError> { | ||
| 363 | let page_count = self.active.capacity() as u32 / Self::PAGE_SIZE; | ||
| 364 | for page_num in 0..page_count { | ||
| 365 | let progress_index = (page_count * 2 + page_num * 2) as usize; | ||
| 366 | |||
| 367 | // Copy the bad active page to the DFU page | ||
| 368 | let active_from_offset = page_num * Self::PAGE_SIZE; | ||
| 369 | let dfu_to_offset = page_num * Self::PAGE_SIZE; | ||
| 370 | self.copy_page_once_to_dfu(progress_index, active_from_offset, dfu_to_offset, aligned_buf)?; | ||
| 371 | |||
| 372 | // Copy the DFU page back to the active page | ||
| 373 | let active_to_offset = page_num * Self::PAGE_SIZE; | ||
| 374 | let dfu_from_offset = (page_num + 1) * Self::PAGE_SIZE; | ||
| 375 | self.copy_page_once_to_active(progress_index + 1, dfu_from_offset, active_to_offset, aligned_buf)?; | ||
| 376 | } | ||
| 377 | |||
| 378 | Ok(()) | ||
| 379 | } | ||
| 380 | |||
| 381 | fn read_state(&mut self, aligned_buf: &mut [u8]) -> Result<State, BootError> { | ||
| 382 | let state_word = &mut aligned_buf[..STATE::WRITE_SIZE]; | ||
| 383 | self.state.read(0, state_word)?; | ||
| 384 | |||
| 385 | if !state_word.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 386 | Ok(State::Swap) | ||
| 387 | } else { | ||
| 388 | Ok(State::Boot) | ||
| 389 | } | ||
| 390 | } | ||
| 391 | } | ||
| 392 | |||
| 393 | fn assert_partitions<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash>( | ||
| 394 | active: &ACTIVE, | ||
| 395 | dfu: &DFU, | ||
| 396 | state: &STATE, | ||
| 397 | page_size: u32, | ||
| 398 | ) { | ||
| 399 | assert_eq!(active.capacity() as u32 % page_size, 0); | ||
| 400 | assert_eq!(dfu.capacity() as u32 % page_size, 0); | ||
| 401 | assert!(dfu.capacity() as u32 - active.capacity() as u32 >= page_size); | ||
| 402 | assert!(2 + 2 * (active.capacity() as u32 / page_size) <= state.capacity() as u32 / STATE::WRITE_SIZE as u32); | ||
| 403 | } | ||
| 404 | |||
| 405 | #[cfg(test)] | ||
| 406 | mod tests { | ||
| 407 | use super::*; | ||
| 408 | use crate::mem_flash::MemFlash; | ||
| 409 | |||
| 410 | #[test] | ||
| 411 | #[should_panic] | ||
| 412 | fn test_range_asserts() { | ||
| 413 | const ACTIVE_SIZE: usize = 4194304 - 4096; | ||
| 414 | const DFU_SIZE: usize = 4194304; | ||
| 415 | const STATE_SIZE: usize = 4096; | ||
| 416 | static ACTIVE: MemFlash<ACTIVE_SIZE, 4, 4> = MemFlash::new(0xFF); | ||
| 417 | static DFU: MemFlash<DFU_SIZE, 4, 4> = MemFlash::new(0xFF); | ||
| 418 | static STATE: MemFlash<STATE_SIZE, 4, 4> = MemFlash::new(0xFF); | ||
| 419 | assert_partitions(&ACTIVE, &DFU, &STATE, 4096); | ||
| 420 | } | ||
| 421 | } | ||
diff --git a/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs new file mode 100644 index 000000000..a184d1c51 --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/ed25519_dalek.rs | |||
| @@ -0,0 +1,30 @@ | |||
| 1 | use digest::typenum::U64; | ||
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | ||
| 3 | use ed25519_dalek::Digest as _; | ||
| 4 | |||
| 5 | pub struct Sha512(ed25519_dalek::Sha512); | ||
| 6 | |||
| 7 | impl Default for Sha512 { | ||
| 8 | fn default() -> Self { | ||
| 9 | Self(ed25519_dalek::Sha512::new()) | ||
| 10 | } | ||
| 11 | } | ||
| 12 | |||
| 13 | impl Update for Sha512 { | ||
| 14 | fn update(&mut self, data: &[u8]) { | ||
| 15 | self.0.update(data) | ||
| 16 | } | ||
| 17 | } | ||
| 18 | |||
| 19 | impl FixedOutput for Sha512 { | ||
| 20 | fn finalize_into(self, out: &mut digest::Output<Self>) { | ||
| 21 | let result = self.0.finalize(); | ||
| 22 | out.as_mut_slice().copy_from_slice(result.as_slice()) | ||
| 23 | } | ||
| 24 | } | ||
| 25 | |||
| 26 | impl OutputSizeUser for Sha512 { | ||
| 27 | type OutputSize = U64; | ||
| 28 | } | ||
| 29 | |||
| 30 | impl HashMarker for Sha512 {} | ||
diff --git a/embassy-boot/boot/src/digest_adapters/mod.rs b/embassy-boot/boot/src/digest_adapters/mod.rs new file mode 100644 index 000000000..9b4b4b60c --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/mod.rs | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | #[cfg(feature = "ed25519-dalek")] | ||
| 2 | pub(crate) mod ed25519_dalek; | ||
| 3 | |||
| 4 | #[cfg(feature = "ed25519-salty")] | ||
| 5 | pub(crate) mod salty; | ||
diff --git a/embassy-boot/boot/src/digest_adapters/salty.rs b/embassy-boot/boot/src/digest_adapters/salty.rs new file mode 100644 index 000000000..2b5dcf3af --- /dev/null +++ b/embassy-boot/boot/src/digest_adapters/salty.rs | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | use digest::typenum::U64; | ||
| 2 | use digest::{FixedOutput, HashMarker, OutputSizeUser, Update}; | ||
| 3 | |||
| 4 | pub struct Sha512(salty::Sha512); | ||
| 5 | |||
| 6 | impl Default for Sha512 { | ||
| 7 | fn default() -> Self { | ||
| 8 | Self(salty::Sha512::new()) | ||
| 9 | } | ||
| 10 | } | ||
| 11 | |||
| 12 | impl Update for Sha512 { | ||
| 13 | fn update(&mut self, data: &[u8]) { | ||
| 14 | self.0.update(data) | ||
| 15 | } | ||
| 16 | } | ||
| 17 | |||
| 18 | impl FixedOutput for Sha512 { | ||
| 19 | fn finalize_into(self, out: &mut digest::Output<Self>) { | ||
| 20 | let result = self.0.finalize(); | ||
| 21 | out.as_mut_slice().copy_from_slice(result.as_slice()) | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | impl OutputSizeUser for Sha512 { | ||
| 26 | type OutputSize = U64; | ||
| 27 | } | ||
| 28 | |||
| 29 | impl HashMarker for Sha512 {} | ||
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs new file mode 100644 index 000000000..20731ee0a --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs | |||
| @@ -0,0 +1,299 @@ | |||
| 1 | use digest::Digest; | ||
| 2 | #[cfg(target_os = "none")] | ||
| 3 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 6 | use embedded_storage_async::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use super::FirmwareUpdaterConfig; | ||
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 10 | |||
| 11 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 12 | /// 'mess up' the internal bootloader state | ||
| 13 | pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> { | ||
| 14 | dfu: DFU, | ||
| 15 | state: STATE, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[cfg(target_os = "none")] | ||
| 19 | impl<'a, FLASH: NorFlash> | ||
| 20 | FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> | ||
| 21 | { | ||
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 23 | pub fn from_linkerfile(flash: &'a embassy_sync::mutex::Mutex<NoopRawMutex, FLASH>) -> Self { | ||
| 24 | extern "C" { | ||
| 25 | static __bootloader_state_start: u32; | ||
| 26 | static __bootloader_state_end: u32; | ||
| 27 | static __bootloader_dfu_start: u32; | ||
| 28 | static __bootloader_dfu_end: u32; | ||
| 29 | } | ||
| 30 | |||
| 31 | let dfu = unsafe { | ||
| 32 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 33 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 34 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 35 | |||
| 36 | Partition::new(flash, start, end - start) | ||
| 37 | }; | ||
| 38 | let state = unsafe { | ||
| 39 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 40 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 41 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 42 | |||
| 43 | Partition::new(flash, start, end - start) | ||
| 44 | }; | ||
| 45 | |||
| 46 | Self { dfu, state } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<DFU: NorFlash, STATE: NorFlash> FirmwareUpdater<DFU, STATE> { | ||
| 51 | /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||
| 52 | pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self { | ||
| 53 | Self { | ||
| 54 | dfu: config.dfu, | ||
| 55 | state: config.state, | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||
| 60 | async fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 61 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 62 | if self.get_state(aligned).await? == State::Boot { | ||
| 63 | Ok(()) | ||
| 64 | } else { | ||
| 65 | Err(FirmwareUpdaterError::BadState) | ||
| 66 | } | ||
| 67 | } | ||
| 68 | |||
| 69 | /// Obtain the current state. | ||
| 70 | /// | ||
| 71 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 72 | /// to do verifications and self-tests of the new image before calling | ||
| 73 | /// `mark_booted`. | ||
| 74 | pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> { | ||
| 75 | self.state.read(0, aligned).await?; | ||
| 76 | |||
| 77 | if !aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 78 | Ok(State::Swap) | ||
| 79 | } else { | ||
| 80 | Ok(State::Boot) | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | /// Verify the DFU given a public key. If there is an error then DO NOT | ||
| 85 | /// proceed with updating the firmware as it must be signed with a | ||
| 86 | /// corresponding private key (otherwise it could be malicious firmware). | ||
| 87 | /// | ||
| 88 | /// Mark to trigger firmware swap on next boot if verify suceeds. | ||
| 89 | /// | ||
| 90 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||
| 91 | /// been generated from a SHA-512 digest of the firmware bytes. | ||
| 92 | /// | ||
| 93 | /// If no signature feature is set then this method will always return a | ||
| 94 | /// signature error. | ||
| 95 | /// | ||
| 96 | /// # Safety | ||
| 97 | /// | ||
| 98 | /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 99 | /// and written to. | ||
| 100 | #[cfg(feature = "_verify")] | ||
| 101 | pub async fn verify_and_mark_updated( | ||
| 102 | &mut self, | ||
| 103 | _public_key: &[u8], | ||
| 104 | _signature: &[u8], | ||
| 105 | _update_len: u32, | ||
| 106 | _aligned: &mut [u8], | ||
| 107 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 108 | assert_eq!(_aligned.len(), STATE::WRITE_SIZE); | ||
| 109 | assert!(_update_len <= self.dfu.capacity() as u32); | ||
| 110 | |||
| 111 | self.verify_booted(_aligned).await?; | ||
| 112 | |||
| 113 | #[cfg(feature = "ed25519-dalek")] | ||
| 114 | { | ||
| 115 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | ||
| 116 | |||
| 117 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 118 | |||
| 119 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||
| 120 | |||
| 121 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||
| 122 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||
| 123 | |||
| 124 | let mut message = [0; 64]; | ||
| 125 | self.hash::<Sha512>(_update_len, _aligned, &mut message).await?; | ||
| 126 | |||
| 127 | public_key.verify(&message, &signature).map_err(into_signature_error)? | ||
| 128 | } | ||
| 129 | #[cfg(feature = "ed25519-salty")] | ||
| 130 | { | ||
| 131 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||
| 132 | use salty::{PublicKey, Signature}; | ||
| 133 | |||
| 134 | use crate::digest_adapters::salty::Sha512; | ||
| 135 | |||
| 136 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||
| 137 | FirmwareUpdaterError::Signature(signature::Error::default()) | ||
| 138 | } | ||
| 139 | |||
| 140 | let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | ||
| 141 | let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | ||
| 142 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||
| 143 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||
| 144 | |||
| 145 | let mut message = [0; 64]; | ||
| 146 | self.hash::<Sha512>(_update_len, _aligned, &mut message).await?; | ||
| 147 | |||
| 148 | let r = public_key.verify(&message, &signature); | ||
| 149 | trace!( | ||
| 150 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||
| 151 | public_key.to_bytes(), | ||
| 152 | signature.to_bytes(), | ||
| 153 | message, | ||
| 154 | r.is_ok() | ||
| 155 | ); | ||
| 156 | r.map_err(into_signature_error)? | ||
| 157 | } | ||
| 158 | |||
| 159 | self.set_magic(_aligned, SWAP_MAGIC).await | ||
| 160 | } | ||
| 161 | |||
| 162 | /// Verify the update in DFU with any digest. | ||
| 163 | pub async fn hash<D: Digest>( | ||
| 164 | &mut self, | ||
| 165 | update_len: u32, | ||
| 166 | chunk_buf: &mut [u8], | ||
| 167 | output: &mut [u8], | ||
| 168 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 169 | let mut digest = D::new(); | ||
| 170 | for offset in (0..update_len).step_by(chunk_buf.len()) { | ||
| 171 | self.dfu.read(offset, chunk_buf).await?; | ||
| 172 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||
| 173 | digest.update(&chunk_buf[..len]); | ||
| 174 | } | ||
| 175 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 176 | Ok(()) | ||
| 177 | } | ||
| 178 | |||
| 179 | /// Mark to trigger firmware swap on next boot. | ||
| 180 | /// | ||
| 181 | /// # Safety | ||
| 182 | /// | ||
| 183 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||
| 184 | #[cfg(not(feature = "_verify"))] | ||
| 185 | pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 186 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 187 | self.set_magic(aligned, SWAP_MAGIC).await | ||
| 188 | } | ||
| 189 | |||
| 190 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 191 | /// | ||
| 192 | /// # Safety | ||
| 193 | /// | ||
| 194 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||
| 195 | pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 196 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 197 | self.set_magic(aligned, BOOT_MAGIC).await | ||
| 198 | } | ||
| 199 | |||
| 200 | async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { | ||
| 201 | self.state.read(0, aligned).await?; | ||
| 202 | |||
| 203 | if aligned.iter().any(|&b| b != magic) { | ||
| 204 | // Read progress validity | ||
| 205 | self.state.read(STATE::WRITE_SIZE as u32, aligned).await?; | ||
| 206 | |||
| 207 | if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 208 | // The current progress validity marker is invalid | ||
| 209 | } else { | ||
| 210 | // Invalidate progress | ||
| 211 | aligned.fill(!STATE_ERASE_VALUE); | ||
| 212 | self.state.write(STATE::WRITE_SIZE as u32, aligned).await?; | ||
| 213 | } | ||
| 214 | |||
| 215 | // Clear magic and progress | ||
| 216 | self.state.erase(0, self.state.capacity() as u32).await?; | ||
| 217 | |||
| 218 | // Set magic | ||
| 219 | aligned.fill(magic); | ||
| 220 | self.state.write(0, aligned).await?; | ||
| 221 | } | ||
| 222 | Ok(()) | ||
| 223 | } | ||
| 224 | |||
| 225 | /// Write data to a flash page. | ||
| 226 | /// | ||
| 227 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||
| 228 | /// | ||
| 229 | /// # Safety | ||
| 230 | /// | ||
| 231 | /// Failing to meet alignment and size requirements may result in a panic. | ||
| 232 | pub async fn write_firmware( | ||
| 233 | &mut self, | ||
| 234 | aligned: &mut [u8], | ||
| 235 | offset: usize, | ||
| 236 | data: &[u8], | ||
| 237 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 238 | assert!(data.len() >= DFU::ERASE_SIZE); | ||
| 239 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 240 | |||
| 241 | self.verify_booted(aligned).await?; | ||
| 242 | |||
| 243 | self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; | ||
| 244 | |||
| 245 | self.dfu.write(offset as u32, data).await?; | ||
| 246 | |||
| 247 | Ok(()) | ||
| 248 | } | ||
| 249 | |||
| 250 | /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||
| 251 | /// returning its `Partition`. | ||
| 252 | /// | ||
| 253 | /// Using this instead of `write_firmware` allows for an optimized API in | ||
| 254 | /// exchange for added complexity. | ||
| 255 | /// | ||
| 256 | /// # Safety | ||
| 257 | /// | ||
| 258 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||
| 259 | pub async fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { | ||
| 260 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 261 | self.verify_booted(aligned).await?; | ||
| 262 | |||
| 263 | self.dfu.erase(0, self.dfu.capacity() as u32).await?; | ||
| 264 | |||
| 265 | Ok(&mut self.dfu) | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | #[cfg(test)] | ||
| 270 | mod tests { | ||
| 271 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 272 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 273 | use embassy_sync::mutex::Mutex; | ||
| 274 | use futures::executor::block_on; | ||
| 275 | use sha1::{Digest, Sha1}; | ||
| 276 | |||
| 277 | use super::*; | ||
| 278 | use crate::mem_flash::MemFlash; | ||
| 279 | |||
| 280 | #[test] | ||
| 281 | fn can_verify_sha1() { | ||
| 282 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default()); | ||
| 283 | let state = Partition::new(&flash, 0, 4096); | ||
| 284 | let dfu = Partition::new(&flash, 65536, 65536); | ||
| 285 | let mut aligned = [0; 8]; | ||
| 286 | |||
| 287 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 288 | let mut to_write = [0; 4096]; | ||
| 289 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 290 | |||
| 291 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); | ||
| 292 | block_on(updater.write_firmware(&mut aligned, 0, to_write.as_slice())).unwrap(); | ||
| 293 | let mut chunk_buf = [0; 2]; | ||
| 294 | let mut hash = [0; 20]; | ||
| 295 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | ||
| 296 | |||
| 297 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 298 | } | ||
| 299 | } | ||
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs new file mode 100644 index 000000000..f03f53e4d --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs | |||
| @@ -0,0 +1,302 @@ | |||
| 1 | use digest::Digest; | ||
| 2 | #[cfg(target_os = "none")] | ||
| 3 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 4 | #[cfg(target_os = "none")] | ||
| 5 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 6 | use embedded_storage::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use super::FirmwareUpdaterConfig; | ||
| 9 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 10 | |||
| 11 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 12 | /// 'mess up' the internal bootloader state | ||
| 13 | pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> { | ||
| 14 | dfu: DFU, | ||
| 15 | state: STATE, | ||
| 16 | } | ||
| 17 | |||
| 18 | #[cfg(target_os = "none")] | ||
| 19 | impl<'a, FLASH: NorFlash> | ||
| 20 | FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> | ||
| 21 | { | ||
| 22 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 23 | pub fn from_linkerfile_blocking( | ||
| 24 | flash: &'a embassy_sync::blocking_mutex::Mutex<NoopRawMutex, core::cell::RefCell<FLASH>>, | ||
| 25 | ) -> Self { | ||
| 26 | extern "C" { | ||
| 27 | static __bootloader_state_start: u32; | ||
| 28 | static __bootloader_state_end: u32; | ||
| 29 | static __bootloader_dfu_start: u32; | ||
| 30 | static __bootloader_dfu_end: u32; | ||
| 31 | } | ||
| 32 | |||
| 33 | let dfu = unsafe { | ||
| 34 | let start = &__bootloader_dfu_start as *const u32 as u32; | ||
| 35 | let end = &__bootloader_dfu_end as *const u32 as u32; | ||
| 36 | trace!("DFU: 0x{:x} - 0x{:x}", start, end); | ||
| 37 | |||
| 38 | BlockingPartition::new(flash, start, end - start) | ||
| 39 | }; | ||
| 40 | let state = unsafe { | ||
| 41 | let start = &__bootloader_state_start as *const u32 as u32; | ||
| 42 | let end = &__bootloader_state_end as *const u32 as u32; | ||
| 43 | trace!("STATE: 0x{:x} - 0x{:x}", start, end); | ||
| 44 | |||
| 45 | BlockingPartition::new(flash, start, end - start) | ||
| 46 | }; | ||
| 47 | |||
| 48 | Self { dfu, state } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | |||
| 52 | impl<DFU: NorFlash, STATE: NorFlash> BlockingFirmwareUpdater<DFU, STATE> { | ||
| 53 | /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||
| 54 | pub fn new(config: FirmwareUpdaterConfig<DFU, STATE>) -> Self { | ||
| 55 | Self { | ||
| 56 | dfu: config.dfu, | ||
| 57 | state: config.state, | ||
| 58 | } | ||
| 59 | } | ||
| 60 | |||
| 61 | // Make sure we are running a booted firmware to avoid reverting to a bad state. | ||
| 62 | fn verify_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 63 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 64 | if self.get_state(aligned)? == State::Boot { | ||
| 65 | Ok(()) | ||
| 66 | } else { | ||
| 67 | Err(FirmwareUpdaterError::BadState) | ||
| 68 | } | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Obtain the current state. | ||
| 72 | /// | ||
| 73 | /// This is useful to check if the bootloader has just done a swap, in order | ||
| 74 | /// to do verifications and self-tests of the new image before calling | ||
| 75 | /// `mark_booted`. | ||
| 76 | pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> { | ||
| 77 | self.state.read(0, aligned)?; | ||
| 78 | |||
| 79 | if !aligned.iter().any(|&b| b != SWAP_MAGIC) { | ||
| 80 | Ok(State::Swap) | ||
| 81 | } else { | ||
| 82 | Ok(State::Boot) | ||
| 83 | } | ||
| 84 | } | ||
| 85 | |||
| 86 | /// Verify the DFU given a public key. If there is an error then DO NOT | ||
| 87 | /// proceed with updating the firmware as it must be signed with a | ||
| 88 | /// corresponding private key (otherwise it could be malicious firmware). | ||
| 89 | /// | ||
| 90 | /// Mark to trigger firmware swap on next boot if verify suceeds. | ||
| 91 | /// | ||
| 92 | /// If the "ed25519-salty" feature is set (or another similar feature) then the signature is expected to have | ||
| 93 | /// been generated from a SHA-512 digest of the firmware bytes. | ||
| 94 | /// | ||
| 95 | /// If no signature feature is set then this method will always return a | ||
| 96 | /// signature error. | ||
| 97 | /// | ||
| 98 | /// # Safety | ||
| 99 | /// | ||
| 100 | /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from | ||
| 101 | /// and written to. | ||
| 102 | #[cfg(feature = "_verify")] | ||
| 103 | pub fn verify_and_mark_updated( | ||
| 104 | &mut self, | ||
| 105 | _public_key: &[u8], | ||
| 106 | _signature: &[u8], | ||
| 107 | _update_len: u32, | ||
| 108 | _aligned: &mut [u8], | ||
| 109 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 110 | assert_eq!(_aligned.len(), STATE::WRITE_SIZE); | ||
| 111 | assert!(_update_len <= self.dfu.capacity() as u32); | ||
| 112 | |||
| 113 | self.verify_booted(_aligned)?; | ||
| 114 | |||
| 115 | #[cfg(feature = "ed25519-dalek")] | ||
| 116 | { | ||
| 117 | use ed25519_dalek::{PublicKey, Signature, SignatureError, Verifier}; | ||
| 118 | |||
| 119 | use crate::digest_adapters::ed25519_dalek::Sha512; | ||
| 120 | |||
| 121 | let into_signature_error = |e: SignatureError| FirmwareUpdaterError::Signature(e.into()); | ||
| 122 | |||
| 123 | let public_key = PublicKey::from_bytes(_public_key).map_err(into_signature_error)?; | ||
| 124 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | ||
| 125 | |||
| 126 | let mut message = [0; 64]; | ||
| 127 | self.hash::<Sha512>(_update_len, _aligned, &mut message)?; | ||
| 128 | |||
| 129 | public_key.verify(&message, &signature).map_err(into_signature_error)? | ||
| 130 | } | ||
| 131 | #[cfg(feature = "ed25519-salty")] | ||
| 132 | { | ||
| 133 | use salty::constants::{PUBLICKEY_SERIALIZED_LENGTH, SIGNATURE_SERIALIZED_LENGTH}; | ||
| 134 | use salty::{PublicKey, Signature}; | ||
| 135 | |||
| 136 | use crate::digest_adapters::salty::Sha512; | ||
| 137 | |||
| 138 | fn into_signature_error<E>(_: E) -> FirmwareUpdaterError { | ||
| 139 | FirmwareUpdaterError::Signature(signature::Error::default()) | ||
| 140 | } | ||
| 141 | |||
| 142 | let public_key: [u8; PUBLICKEY_SERIALIZED_LENGTH] = _public_key.try_into().map_err(into_signature_error)?; | ||
| 143 | let public_key = PublicKey::try_from(&public_key).map_err(into_signature_error)?; | ||
| 144 | let signature: [u8; SIGNATURE_SERIALIZED_LENGTH] = _signature.try_into().map_err(into_signature_error)?; | ||
| 145 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | ||
| 146 | |||
| 147 | let mut message = [0; 64]; | ||
| 148 | self.hash::<Sha512>(_update_len, _aligned, &mut message)?; | ||
| 149 | |||
| 150 | let r = public_key.verify(&message, &signature); | ||
| 151 | trace!( | ||
| 152 | "Verifying with public key {}, signature {} and message {} yields ok: {}", | ||
| 153 | public_key.to_bytes(), | ||
| 154 | signature.to_bytes(), | ||
| 155 | message, | ||
| 156 | r.is_ok() | ||
| 157 | ); | ||
| 158 | r.map_err(into_signature_error)? | ||
| 159 | } | ||
| 160 | |||
| 161 | self.set_magic(_aligned, SWAP_MAGIC) | ||
| 162 | } | ||
| 163 | |||
| 164 | /// Verify the update in DFU with any digest. | ||
| 165 | pub fn hash<D: Digest>( | ||
| 166 | &mut self, | ||
| 167 | update_len: u32, | ||
| 168 | chunk_buf: &mut [u8], | ||
| 169 | output: &mut [u8], | ||
| 170 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 171 | let mut digest = D::new(); | ||
| 172 | for offset in (0..update_len).step_by(chunk_buf.len()) { | ||
| 173 | self.dfu.read(offset, chunk_buf)?; | ||
| 174 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | ||
| 175 | digest.update(&chunk_buf[..len]); | ||
| 176 | } | ||
| 177 | output.copy_from_slice(digest.finalize().as_slice()); | ||
| 178 | Ok(()) | ||
| 179 | } | ||
| 180 | |||
| 181 | /// Mark to trigger firmware swap on next boot. | ||
| 182 | /// | ||
| 183 | /// # Safety | ||
| 184 | /// | ||
| 185 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||
| 186 | #[cfg(not(feature = "_verify"))] | ||
| 187 | pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 188 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 189 | self.set_magic(aligned, SWAP_MAGIC) | ||
| 190 | } | ||
| 191 | |||
| 192 | /// Mark firmware boot successful and stop rollback on reset. | ||
| 193 | /// | ||
| 194 | /// # Safety | ||
| 195 | /// | ||
| 196 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||
| 197 | pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { | ||
| 198 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 199 | self.set_magic(aligned, BOOT_MAGIC) | ||
| 200 | } | ||
| 201 | |||
| 202 | fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { | ||
| 203 | self.state.read(0, aligned)?; | ||
| 204 | |||
| 205 | if aligned.iter().any(|&b| b != magic) { | ||
| 206 | // Read progress validity | ||
| 207 | self.state.read(STATE::WRITE_SIZE as u32, aligned)?; | ||
| 208 | |||
| 209 | if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | ||
| 210 | // The current progress validity marker is invalid | ||
| 211 | } else { | ||
| 212 | // Invalidate progress | ||
| 213 | aligned.fill(!STATE_ERASE_VALUE); | ||
| 214 | self.state.write(STATE::WRITE_SIZE as u32, aligned)?; | ||
| 215 | } | ||
| 216 | |||
| 217 | // Clear magic and progress | ||
| 218 | self.state.erase(0, self.state.capacity() as u32)?; | ||
| 219 | |||
| 220 | // Set magic | ||
| 221 | aligned.fill(magic); | ||
| 222 | self.state.write(0, aligned)?; | ||
| 223 | } | ||
| 224 | Ok(()) | ||
| 225 | } | ||
| 226 | |||
| 227 | /// Write data to a flash page. | ||
| 228 | /// | ||
| 229 | /// The buffer must follow alignment requirements of the target flash and a multiple of page size big. | ||
| 230 | /// | ||
| 231 | /// # Safety | ||
| 232 | /// | ||
| 233 | /// Failing to meet alignment and size requirements may result in a panic. | ||
| 234 | pub fn write_firmware( | ||
| 235 | &mut self, | ||
| 236 | aligned: &mut [u8], | ||
| 237 | offset: usize, | ||
| 238 | data: &[u8], | ||
| 239 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 240 | assert!(data.len() >= DFU::ERASE_SIZE); | ||
| 241 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 242 | self.verify_booted(aligned)?; | ||
| 243 | |||
| 244 | self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; | ||
| 245 | |||
| 246 | self.dfu.write(offset as u32, data)?; | ||
| 247 | |||
| 248 | Ok(()) | ||
| 249 | } | ||
| 250 | |||
| 251 | /// Prepare for an incoming DFU update by erasing the entire DFU area and | ||
| 252 | /// returning its `Partition`. | ||
| 253 | /// | ||
| 254 | /// Using this instead of `write_firmware` allows for an optimized API in | ||
| 255 | /// exchange for added complexity. | ||
| 256 | /// | ||
| 257 | /// # Safety | ||
| 258 | /// | ||
| 259 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. | ||
| 260 | pub fn prepare_update(&mut self, aligned: &mut [u8]) -> Result<&mut DFU, FirmwareUpdaterError> { | ||
| 261 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); | ||
| 262 | self.verify_booted(aligned)?; | ||
| 263 | self.dfu.erase(0, self.dfu.capacity() as u32)?; | ||
| 264 | |||
| 265 | Ok(&mut self.dfu) | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | #[cfg(test)] | ||
| 270 | mod tests { | ||
| 271 | use core::cell::RefCell; | ||
| 272 | |||
| 273 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 274 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 275 | use embassy_sync::blocking_mutex::Mutex; | ||
| 276 | use sha1::{Digest, Sha1}; | ||
| 277 | |||
| 278 | use super::*; | ||
| 279 | use crate::mem_flash::MemFlash; | ||
| 280 | |||
| 281 | #[test] | ||
| 282 | fn can_verify_sha1() { | ||
| 283 | let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); | ||
| 284 | let state = BlockingPartition::new(&flash, 0, 4096); | ||
| 285 | let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||
| 286 | |||
| 287 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 288 | let mut to_write = [0; 4096]; | ||
| 289 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 290 | |||
| 291 | let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); | ||
| 292 | let mut aligned = [0; 8]; | ||
| 293 | updater.write_firmware(&mut aligned, 0, to_write.as_slice()).unwrap(); | ||
| 294 | let mut chunk_buf = [0; 2]; | ||
| 295 | let mut hash = [0; 20]; | ||
| 296 | updater | ||
| 297 | .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||
| 298 | .unwrap(); | ||
| 299 | |||
| 300 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 301 | } | ||
| 302 | } | ||
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs new file mode 100644 index 000000000..55ce8f363 --- /dev/null +++ b/embassy-boot/boot/src/firmware_updater/mod.rs | |||
| @@ -0,0 +1,51 @@ | |||
| 1 | #[cfg(feature = "nightly")] | ||
| 2 | mod asynch; | ||
| 3 | mod blocking; | ||
| 4 | |||
| 5 | #[cfg(feature = "nightly")] | ||
| 6 | pub use asynch::FirmwareUpdater; | ||
| 7 | pub use blocking::BlockingFirmwareUpdater; | ||
| 8 | use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | ||
| 9 | |||
| 10 | /// Firmware updater flash configuration holding the two flashes used by the updater | ||
| 11 | /// | ||
| 12 | /// If only a single flash is actually used, then that flash should be partitioned into two partitions before use. | ||
| 13 | /// The easiest way to do this is to use [`FirmwareUpdaterConfig::from_linkerfile`] or [`FirmwareUpdaterConfig::from_linkerfile_blocking`] which will partition | ||
| 14 | /// the provided flash according to symbols defined in the linkerfile. | ||
| 15 | pub struct FirmwareUpdaterConfig<DFU, STATE> { | ||
| 16 | /// The dfu flash partition | ||
| 17 | pub dfu: DFU, | ||
| 18 | /// The state flash partition | ||
| 19 | pub state: STATE, | ||
| 20 | } | ||
| 21 | |||
| 22 | /// Errors returned by FirmwareUpdater | ||
| 23 | #[derive(Debug)] | ||
| 24 | pub enum FirmwareUpdaterError { | ||
| 25 | /// Error from flash. | ||
| 26 | Flash(NorFlashErrorKind), | ||
| 27 | /// Signature errors. | ||
| 28 | Signature(signature::Error), | ||
| 29 | /// Bad state. | ||
| 30 | BadState, | ||
| 31 | } | ||
| 32 | |||
| 33 | #[cfg(feature = "defmt")] | ||
| 34 | impl defmt::Format for FirmwareUpdaterError { | ||
| 35 | fn format(&self, fmt: defmt::Formatter) { | ||
| 36 | match self { | ||
| 37 | FirmwareUpdaterError::Flash(_) => defmt::write!(fmt, "FirmwareUpdaterError::Flash(_)"), | ||
| 38 | FirmwareUpdaterError::Signature(_) => defmt::write!(fmt, "FirmwareUpdaterError::Signature(_)"), | ||
| 39 | FirmwareUpdaterError::BadState => defmt::write!(fmt, "FirmwareUpdaterError::BadState"), | ||
| 40 | } | ||
| 41 | } | ||
| 42 | } | ||
| 43 | |||
| 44 | impl<E> From<E> for FirmwareUpdaterError | ||
| 45 | where | ||
| 46 | E: NorFlashError, | ||
| 47 | { | ||
| 48 | fn from(error: E) -> Self { | ||
| 49 | FirmwareUpdaterError::Flash(error.kind()) | ||
| 50 | } | ||
| 51 | } | ||
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 51e1056cf..016362b86 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs | |||
| @@ -1,674 +1,76 @@ | |||
| 1 | #![feature(type_alias_impl_trait)] | 1 | #![cfg_attr(feature = "nightly", feature(async_fn_in_trait))] |
| 2 | #![feature(generic_associated_types)] | ||
| 3 | #![feature(generic_const_exprs)] | ||
| 4 | #![allow(incomplete_features)] | ||
| 5 | #![no_std] | 2 | #![no_std] |
| 6 | ///! embassy-boot is a bootloader and firmware updater for embedded devices with flash | 3 | #![warn(missing_docs)] |
| 7 | ///! storage implemented using embedded-storage | 4 | #![doc = include_str!("../README.md")] |
| 8 | ///! | ||
| 9 | ///! The bootloader works in conjunction with the firmware application, and only has the | ||
| 10 | ///! ability to manage two flash banks with an active and a updatable part. It implements | ||
| 11 | ///! a swap algorithm that is power-failure safe, and allows reverting to the previous | ||
| 12 | ///! version of the firmware, should the application crash and fail to mark itself as booted. | ||
| 13 | ///! | ||
| 14 | ///! This library is intended to be used by platform-specific bootloaders, such as embassy-boot-nrf, | ||
| 15 | ///! which defines the limits and flash type for that particular platform. | ||
| 16 | ///! | ||
| 17 | mod fmt; | 5 | mod fmt; |
| 18 | 6 | ||
| 19 | use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; | 7 | mod boot_loader; |
| 20 | use embedded_storage_async::nor_flash::AsyncNorFlash; | 8 | mod digest_adapters; |
| 9 | mod firmware_updater; | ||
| 10 | #[cfg(test)] | ||
| 11 | mod mem_flash; | ||
| 12 | #[cfg(test)] | ||
| 13 | mod test_flash; | ||
| 21 | 14 | ||
| 22 | const BOOT_MAGIC: u8 = 0xD0; | 15 | // The expected value of the flash after an erase |
| 23 | const SWAP_MAGIC: u8 = 0xF0; | 16 | // TODO: Use the value provided by NorFlash when available |
| 17 | pub(crate) const STATE_ERASE_VALUE: u8 = 0xFF; | ||
| 18 | pub use boot_loader::{BootError, BootLoader, BootLoaderConfig}; | ||
| 19 | #[cfg(feature = "nightly")] | ||
| 20 | pub use firmware_updater::FirmwareUpdater; | ||
| 21 | pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; | ||
| 24 | 22 | ||
| 25 | #[derive(Copy, Clone, Debug)] | 23 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; |
| 26 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 24 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; |
| 27 | pub struct Partition { | ||
| 28 | pub from: usize, | ||
| 29 | pub to: usize, | ||
| 30 | } | ||
| 31 | 25 | ||
| 32 | impl Partition { | 26 | /// The state of the bootloader after running prepare. |
| 33 | pub const fn new(from: usize, to: usize) -> Self { | 27 | #[derive(PartialEq, Eq, Debug)] |
| 34 | Self { from, to } | ||
| 35 | } | ||
| 36 | pub const fn len(&self) -> usize { | ||
| 37 | self.to - self.from | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | #[derive(PartialEq, Debug)] | ||
| 42 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] | 28 | #[cfg_attr(feature = "defmt", derive(defmt::Format))] |
| 43 | pub enum State { | 29 | pub enum State { |
| 30 | /// Bootloader is ready to boot the active partition. | ||
| 44 | Boot, | 31 | Boot, |
| 32 | /// Bootloader has swapped the active partition with the dfu partition and will attempt boot. | ||
| 45 | Swap, | 33 | Swap, |
| 46 | } | 34 | } |
| 47 | 35 | ||
| 48 | #[derive(PartialEq, Debug)] | 36 | /// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot. |
| 49 | pub enum BootError { | ||
| 50 | Flash(NorFlashErrorKind), | ||
| 51 | BadMagic, | ||
| 52 | } | ||
| 53 | |||
| 54 | impl<E> From<E> for BootError | ||
| 55 | where | ||
| 56 | E: NorFlashError, | ||
| 57 | { | ||
| 58 | fn from(error: E) -> Self { | ||
| 59 | BootError::Flash(error.kind()) | ||
| 60 | } | ||
| 61 | } | ||
| 62 | |||
| 63 | pub trait FlashConfig { | ||
| 64 | const BLOCK_SIZE: usize; | ||
| 65 | const ERASE_VALUE: u8; | ||
| 66 | type FLASH: NorFlash + ReadNorFlash; | ||
| 67 | |||
| 68 | fn flash(&mut self) -> &mut Self::FLASH; | ||
| 69 | } | ||
| 70 | |||
| 71 | /// Trait defining the flash handles used for active and DFU partition | ||
| 72 | pub trait FlashProvider { | ||
| 73 | type STATE: FlashConfig; | ||
| 74 | type ACTIVE: FlashConfig; | ||
| 75 | type DFU: FlashConfig; | ||
| 76 | |||
| 77 | /// Return flash instance used to write/read to/from active partition. | ||
| 78 | fn active(&mut self) -> &mut Self::ACTIVE; | ||
| 79 | /// Return flash instance used to write/read to/from dfu partition. | ||
| 80 | fn dfu(&mut self) -> &mut Self::DFU; | ||
| 81 | /// Return flash instance used to write/read to/from bootloader state. | ||
| 82 | fn state(&mut self) -> &mut Self::STATE; | ||
| 83 | } | ||
| 84 | |||
| 85 | /// BootLoader works with any flash implementing embedded_storage and can also work with | ||
| 86 | /// different page sizes and flash write sizes. | ||
| 87 | /// | ||
| 88 | /// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes. | ||
| 89 | pub struct BootLoader<const PAGE_SIZE: usize> { | ||
| 90 | // Page with current state of bootloader. The state partition has the following format: | ||
| 91 | // | Range | Description | | ||
| 92 | // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | | ||
| 93 | // | WRITE_SIZE - N | Progress index used while swapping or reverting | | ||
| 94 | state: Partition, | ||
| 95 | // Location of the partition which will be booted from | ||
| 96 | active: Partition, | ||
| 97 | // Location of the partition which will be swapped in when requested | ||
| 98 | dfu: Partition, | ||
| 99 | } | ||
| 100 | |||
| 101 | impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | ||
| 102 | pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | ||
| 103 | assert_eq!(active.len() % PAGE_SIZE, 0); | ||
| 104 | assert_eq!(dfu.len() % PAGE_SIZE, 0); | ||
| 105 | // DFU partition must have an extra page | ||
| 106 | assert!(dfu.len() - active.len() >= PAGE_SIZE); | ||
| 107 | Self { active, dfu, state } | ||
| 108 | } | ||
| 109 | |||
| 110 | pub fn boot_address(&self) -> usize { | ||
| 111 | self.active.from | ||
| 112 | } | ||
| 113 | |||
| 114 | /// Perform necessary boot preparations like swapping images. | ||
| 115 | /// | ||
| 116 | /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap | ||
| 117 | /// algorithm to work correctly. | ||
| 118 | /// | ||
| 119 | /// SWAPPING | ||
| 120 | /// | ||
| 121 | /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. | ||
| 122 | /// The swap index contains the copy progress, as to allow continuation of the copy process on | ||
| 123 | /// power failure. The index counter is represented within 1 or more pages (depending on total | ||
| 124 | /// flash size), where a page X is considered swapped if index at location (X + WRITE_SIZE) | ||
| 125 | /// contains a zero value. This ensures that index updates can be performed atomically and | ||
| 126 | /// avoid a situation where the wrong index value is set (page write size is "atomic"). | ||
| 127 | /// | ||
| 128 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 129 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 130 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 131 | /// | Active | 0 | 1 | 2 | 3 | - | | ||
| 132 | /// | DFU | 0 | 3 | 2 | 1 | X | | ||
| 133 | /// +-----------+-------+--------+--------+--------+--------+ | ||
| 134 | /// | ||
| 135 | /// The algorithm starts by copying 'backwards', and after the first step, the layout is | ||
| 136 | /// as follows: | ||
| 137 | /// | ||
| 138 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 139 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 140 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 141 | /// | Active | 1 | 1 | 2 | 1 | - | | ||
| 142 | /// | DFU | 1 | 3 | 2 | 1 | 3 | | ||
| 143 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 144 | /// | ||
| 145 | /// The next iteration performs the same steps | ||
| 146 | /// | ||
| 147 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 148 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 149 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 150 | /// | Active | 2 | 1 | 2 | 1 | - | | ||
| 151 | /// | DFU | 2 | 3 | 2 | 2 | 3 | | ||
| 152 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 153 | /// | ||
| 154 | /// And again until we're done | ||
| 155 | /// | ||
| 156 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 157 | /// | Partition | Swap Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 158 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 159 | /// | Active | 3 | 3 | 2 | 1 | - | | ||
| 160 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 161 | /// +-----------+------------+--------+--------+--------+--------+ | ||
| 162 | /// | ||
| 163 | /// REVERTING | ||
| 164 | /// | ||
| 165 | /// The reverting algorithm uses the swap index to discover that images were swapped, but that | ||
| 166 | /// the application failed to mark the boot successful. In this case, the revert algorithm will | ||
| 167 | /// run. | ||
| 168 | /// | ||
| 169 | /// The revert index is located separately from the swap index, to ensure that revert can continue | ||
| 170 | /// on power failure. | ||
| 171 | /// | ||
| 172 | /// The revert algorithm works forwards, by starting copying into the 'unused' DFU page at the start. | ||
| 173 | /// | ||
| 174 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 175 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 176 | //*/ | ||
| 177 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 178 | /// | Active | 3 | 1 | 2 | 1 | - | | ||
| 179 | /// | DFU | 3 | 3 | 1 | 2 | 3 | | ||
| 180 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 181 | /// | ||
| 182 | /// | ||
| 183 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 184 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 185 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 186 | /// | Active | 3 | 1 | 2 | 1 | - | | ||
| 187 | /// | DFU | 3 | 3 | 2 | 2 | 3 | | ||
| 188 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 189 | /// | ||
| 190 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 191 | /// | Partition | Revert Index | Page 0 | Page 1 | Page 3 | Page 4 | | ||
| 192 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 193 | /// | Active | 3 | 1 | 2 | 3 | - | | ||
| 194 | /// | DFU | 3 | 3 | 2 | 1 | 3 | | ||
| 195 | /// +-----------+--------------+--------+--------+--------+--------+ | ||
| 196 | /// | ||
| 197 | pub fn prepare_boot<P: FlashProvider>(&mut self, p: &mut P) -> Result<State, BootError> | ||
| 198 | where | ||
| 199 | [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | ||
| 200 | [(); <<P as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, | ||
| 201 | { | ||
| 202 | // Ensure we have enough progress pages to store copy progress | ||
| 203 | assert!( | ||
| 204 | self.active.len() / PAGE_SIZE | ||
| 205 | <= (self.state.len() - <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE) | ||
| 206 | / <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE | ||
| 207 | ); | ||
| 208 | |||
| 209 | // Copy contents from partition N to active | ||
| 210 | let state = self.read_state(p.state())?; | ||
| 211 | match state { | ||
| 212 | State::Swap => { | ||
| 213 | // | ||
| 214 | // Check if we already swapped. If we're in the swap state, this means we should revert | ||
| 215 | // since the app has failed to mark boot as successful | ||
| 216 | // | ||
| 217 | if !self.is_swapped(p.state())? { | ||
| 218 | trace!("Swapping"); | ||
| 219 | self.swap(p)?; | ||
| 220 | trace!("Swapping done"); | ||
| 221 | } else { | ||
| 222 | trace!("Reverting"); | ||
| 223 | self.revert(p)?; | ||
| 224 | |||
| 225 | // Overwrite magic and reset progress | ||
| 226 | let fstate = p.state().flash(); | ||
| 227 | let aligned = Aligned( | ||
| 228 | [!P::STATE::ERASE_VALUE; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE], | ||
| 229 | ); | ||
| 230 | fstate.write(self.state.from as u32, &aligned.0)?; | ||
| 231 | fstate.erase(self.state.from as u32, self.state.to as u32)?; | ||
| 232 | let aligned = | ||
| 233 | Aligned([BOOT_MAGIC; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]); | ||
| 234 | fstate.write(self.state.from as u32, &aligned.0)?; | ||
| 235 | } | ||
| 236 | } | ||
| 237 | _ => {} | ||
| 238 | } | ||
| 239 | Ok(state) | ||
| 240 | } | ||
| 241 | |||
| 242 | fn is_swapped<P: FlashConfig>(&mut self, p: &mut P) -> Result<bool, BootError> | ||
| 243 | where | ||
| 244 | [(); P::FLASH::WRITE_SIZE]:, | ||
| 245 | { | ||
| 246 | let page_count = self.active.len() / P::FLASH::ERASE_SIZE; | ||
| 247 | let progress = self.current_progress(p)?; | ||
| 248 | |||
| 249 | Ok(progress >= page_count * 2) | ||
| 250 | } | ||
| 251 | |||
| 252 | fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> | ||
| 253 | where | ||
| 254 | [(); P::FLASH::WRITE_SIZE]:, | ||
| 255 | { | ||
| 256 | let write_size = P::FLASH::WRITE_SIZE; | ||
| 257 | let max_index = ((self.state.len() - write_size) / write_size) - 1; | ||
| 258 | let flash = p.flash(); | ||
| 259 | let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); | ||
| 260 | for i in 0..max_index { | ||
| 261 | flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?; | ||
| 262 | if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] { | ||
| 263 | return Ok(i); | ||
| 264 | } | ||
| 265 | } | ||
| 266 | Ok(max_index) | ||
| 267 | } | ||
| 268 | |||
| 269 | fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> | ||
| 270 | where | ||
| 271 | [(); P::FLASH::WRITE_SIZE]:, | ||
| 272 | { | ||
| 273 | let flash = p.flash(); | ||
| 274 | let write_size = P::FLASH::WRITE_SIZE; | ||
| 275 | let w = self.state.from + write_size + idx * write_size; | ||
| 276 | let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); | ||
| 277 | flash.write(w as u32, &aligned.0)?; | ||
| 278 | Ok(()) | ||
| 279 | } | ||
| 280 | |||
| 281 | fn active_addr(&self, n: usize) -> usize { | ||
| 282 | self.active.from + n * PAGE_SIZE | ||
| 283 | } | ||
| 284 | |||
| 285 | fn dfu_addr(&self, n: usize) -> usize { | ||
| 286 | self.dfu.from + n * PAGE_SIZE | ||
| 287 | } | ||
| 288 | |||
| 289 | fn copy_page_once_to_active<P: FlashProvider>( | ||
| 290 | &mut self, | ||
| 291 | idx: usize, | ||
| 292 | from_page: usize, | ||
| 293 | to_page: usize, | ||
| 294 | p: &mut P, | ||
| 295 | ) -> Result<(), BootError> | ||
| 296 | where | ||
| 297 | [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | ||
| 298 | { | ||
| 299 | let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; | ||
| 300 | if self.current_progress(p.state())? <= idx { | ||
| 301 | let mut offset = from_page; | ||
| 302 | for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { | ||
| 303 | p.dfu().flash().read(offset as u32, chunk)?; | ||
| 304 | offset += chunk.len(); | ||
| 305 | } | ||
| 306 | |||
| 307 | p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; | ||
| 308 | |||
| 309 | let mut offset = to_page; | ||
| 310 | for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { | ||
| 311 | p.active().flash().write(offset as u32, &chunk)?; | ||
| 312 | offset += chunk.len(); | ||
| 313 | } | ||
| 314 | self.update_progress(idx, p.state())?; | ||
| 315 | } | ||
| 316 | Ok(()) | ||
| 317 | } | ||
| 318 | |||
| 319 | fn copy_page_once_to_dfu<P: FlashProvider>( | ||
| 320 | &mut self, | ||
| 321 | idx: usize, | ||
| 322 | from_page: usize, | ||
| 323 | to_page: usize, | ||
| 324 | p: &mut P, | ||
| 325 | ) -> Result<(), BootError> | ||
| 326 | where | ||
| 327 | [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | ||
| 328 | { | ||
| 329 | let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; | ||
| 330 | if self.current_progress(p.state())? <= idx { | ||
| 331 | let mut offset = from_page; | ||
| 332 | for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { | ||
| 333 | p.active().flash().read(offset as u32, chunk)?; | ||
| 334 | offset += chunk.len(); | ||
| 335 | } | ||
| 336 | |||
| 337 | p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; | ||
| 338 | |||
| 339 | let mut offset = to_page; | ||
| 340 | for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { | ||
| 341 | p.dfu().flash().write(offset as u32, chunk)?; | ||
| 342 | offset += chunk.len(); | ||
| 343 | } | ||
| 344 | self.update_progress(idx, p.state())?; | ||
| 345 | } | ||
| 346 | Ok(()) | ||
| 347 | } | ||
| 348 | |||
| 349 | fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> | ||
| 350 | where | ||
| 351 | [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | ||
| 352 | { | ||
| 353 | let page_count = self.active.len() / PAGE_SIZE; | ||
| 354 | trace!("Page count: {}", page_count); | ||
| 355 | for page in 0..page_count { | ||
| 356 | trace!("COPY PAGE {}", page); | ||
| 357 | // Copy active page to the 'next' DFU page. | ||
| 358 | let active_page = self.active_addr(page_count - 1 - page); | ||
| 359 | let dfu_page = self.dfu_addr(page_count - page); | ||
| 360 | //trace!("Copy active {} to dfu {}", active_page, dfu_page); | ||
| 361 | self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; | ||
| 362 | |||
| 363 | // Copy DFU page to the active page | ||
| 364 | let active_page = self.active_addr(page_count - 1 - page); | ||
| 365 | let dfu_page = self.dfu_addr(page_count - 1 - page); | ||
| 366 | //trace!("Copy dfy {} to active {}", dfu_page, active_page); | ||
| 367 | self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; | ||
| 368 | } | ||
| 369 | |||
| 370 | Ok(()) | ||
| 371 | } | ||
| 372 | |||
| 373 | fn revert<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> | ||
| 374 | where | ||
| 375 | [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | ||
| 376 | { | ||
| 377 | let page_count = self.active.len() / PAGE_SIZE; | ||
| 378 | for page in 0..page_count { | ||
| 379 | // Copy the bad active page to the DFU page | ||
| 380 | let active_page = self.active_addr(page); | ||
| 381 | let dfu_page = self.dfu_addr(page); | ||
| 382 | self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?; | ||
| 383 | |||
| 384 | // Copy the DFU page back to the active page | ||
| 385 | let active_page = self.active_addr(page); | ||
| 386 | let dfu_page = self.dfu_addr(page + 1); | ||
| 387 | self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?; | ||
| 388 | } | ||
| 389 | |||
| 390 | Ok(()) | ||
| 391 | } | ||
| 392 | |||
| 393 | fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> | ||
| 394 | where | ||
| 395 | [(); P::FLASH::WRITE_SIZE]:, | ||
| 396 | { | ||
| 397 | let mut magic: [u8; P::FLASH::WRITE_SIZE] = [0; P::FLASH::WRITE_SIZE]; | ||
| 398 | let flash = p.flash(); | ||
| 399 | flash.read(self.state.from as u32, &mut magic)?; | ||
| 400 | |||
| 401 | if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] { | ||
| 402 | Ok(State::Swap) | ||
| 403 | } else { | ||
| 404 | Ok(State::Boot) | ||
| 405 | } | ||
| 406 | } | ||
| 407 | } | ||
| 408 | |||
| 409 | /// Convenience provider that uses a single flash for everything | ||
| 410 | pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF> | ||
| 411 | where | ||
| 412 | F: NorFlash + ReadNorFlash, | ||
| 413 | { | ||
| 414 | config: SingleFlashConfig<'a, F, ERASE_VALUE>, | ||
| 415 | } | ||
| 416 | |||
| 417 | impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE> | ||
| 418 | where | ||
| 419 | F: NorFlash + ReadNorFlash, | ||
| 420 | { | ||
| 421 | pub fn new(flash: &'a mut F) -> Self { | ||
| 422 | Self { | ||
| 423 | config: SingleFlashConfig { flash }, | ||
| 424 | } | ||
| 425 | } | ||
| 426 | } | ||
| 427 | |||
| 428 | pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF> | ||
| 429 | where | ||
| 430 | F: NorFlash + ReadNorFlash, | ||
| 431 | { | ||
| 432 | flash: &'a mut F, | ||
| 433 | } | ||
| 434 | |||
| 435 | impl<'a, F> FlashProvider for SingleFlashProvider<'a, F> | ||
| 436 | where | ||
| 437 | F: NorFlash + ReadNorFlash, | ||
| 438 | { | ||
| 439 | type STATE = SingleFlashConfig<'a, F>; | ||
| 440 | type ACTIVE = SingleFlashConfig<'a, F>; | ||
| 441 | type DFU = SingleFlashConfig<'a, F>; | ||
| 442 | |||
| 443 | fn active(&mut self) -> &mut Self::STATE { | ||
| 444 | &mut self.config | ||
| 445 | } | ||
| 446 | fn dfu(&mut self) -> &mut Self::ACTIVE { | ||
| 447 | &mut self.config | ||
| 448 | } | ||
| 449 | fn state(&mut self) -> &mut Self::DFU { | ||
| 450 | &mut self.config | ||
| 451 | } | ||
| 452 | } | ||
| 453 | |||
| 454 | impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE> | ||
| 455 | where | ||
| 456 | F: NorFlash + ReadNorFlash, | ||
| 457 | { | ||
| 458 | const BLOCK_SIZE: usize = F::ERASE_SIZE; | ||
| 459 | const ERASE_VALUE: u8 = ERASE_VALUE; | ||
| 460 | type FLASH = F; | ||
| 461 | fn flash(&mut self) -> &mut F { | ||
| 462 | self.flash | ||
| 463 | } | ||
| 464 | } | ||
| 465 | |||
| 466 | /// Convenience provider that uses a single flash for everything | ||
| 467 | pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU> | ||
| 468 | where | ||
| 469 | ACTIVE: NorFlash + ReadNorFlash, | ||
| 470 | STATE: NorFlash + ReadNorFlash, | ||
| 471 | DFU: NorFlash + ReadNorFlash, | ||
| 472 | { | ||
| 473 | active: SingleFlashConfig<'a, ACTIVE>, | ||
| 474 | state: SingleFlashConfig<'a, STATE>, | ||
| 475 | dfu: SingleFlashConfig<'a, DFU>, | ||
| 476 | } | ||
| 477 | |||
| 478 | impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU> | ||
| 479 | where | ||
| 480 | ACTIVE: NorFlash + ReadNorFlash, | ||
| 481 | STATE: NorFlash + ReadNorFlash, | ||
| 482 | DFU: NorFlash + ReadNorFlash, | ||
| 483 | { | ||
| 484 | pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { | ||
| 485 | Self { | ||
| 486 | active: SingleFlashConfig { flash: active }, | ||
| 487 | state: SingleFlashConfig { flash: state }, | ||
| 488 | dfu: SingleFlashConfig { flash: dfu }, | ||
| 489 | } | ||
| 490 | } | ||
| 491 | } | ||
| 492 | |||
| 493 | impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU> | ||
| 494 | where | ||
| 495 | ACTIVE: NorFlash + ReadNorFlash, | ||
| 496 | STATE: NorFlash + ReadNorFlash, | ||
| 497 | DFU: NorFlash + ReadNorFlash, | ||
| 498 | { | ||
| 499 | type STATE = SingleFlashConfig<'a, STATE>; | ||
| 500 | type ACTIVE = SingleFlashConfig<'a, ACTIVE>; | ||
| 501 | type DFU = SingleFlashConfig<'a, DFU>; | ||
| 502 | |||
| 503 | fn active(&mut self) -> &mut Self::ACTIVE { | ||
| 504 | &mut self.active | ||
| 505 | } | ||
| 506 | fn dfu(&mut self) -> &mut Self::DFU { | ||
| 507 | &mut self.dfu | ||
| 508 | } | ||
| 509 | fn state(&mut self) -> &mut Self::STATE { | ||
| 510 | &mut self.state | ||
| 511 | } | ||
| 512 | } | ||
| 513 | |||
| 514 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 515 | /// 'mess up' the internal bootloader state | ||
| 516 | pub struct FirmwareUpdater { | ||
| 517 | state: Partition, | ||
| 518 | dfu: Partition, | ||
| 519 | } | ||
| 520 | |||
| 521 | // NOTE: Aligned to the largest write size supported by flash | ||
| 522 | #[repr(align(32))] | 37 | #[repr(align(32))] |
| 523 | pub struct Aligned<const N: usize>([u8; N]); | 38 | pub struct AlignedBuffer<const N: usize>(pub [u8; N]); |
| 524 | |||
| 525 | impl Default for FirmwareUpdater { | ||
| 526 | fn default() -> Self { | ||
| 527 | extern "C" { | ||
| 528 | static __bootloader_state_start: u32; | ||
| 529 | static __bootloader_state_end: u32; | ||
| 530 | static __bootloader_dfu_start: u32; | ||
| 531 | static __bootloader_dfu_end: u32; | ||
| 532 | } | ||
| 533 | |||
| 534 | let dfu = unsafe { | ||
| 535 | Partition::new( | ||
| 536 | &__bootloader_dfu_start as *const u32 as usize, | ||
| 537 | &__bootloader_dfu_end as *const u32 as usize, | ||
| 538 | ) | ||
| 539 | }; | ||
| 540 | let state = unsafe { | ||
| 541 | Partition::new( | ||
| 542 | &__bootloader_state_start as *const u32 as usize, | ||
| 543 | &__bootloader_state_end as *const u32 as usize, | ||
| 544 | ) | ||
| 545 | }; | ||
| 546 | |||
| 547 | trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||
| 548 | trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||
| 549 | FirmwareUpdater::new(dfu, state) | ||
| 550 | } | ||
| 551 | } | ||
| 552 | |||
| 553 | impl FirmwareUpdater { | ||
| 554 | pub const fn new(dfu: Partition, state: Partition) -> Self { | ||
| 555 | Self { dfu, state } | ||
| 556 | } | ||
| 557 | |||
| 558 | /// Return the length of the DFU area | ||
| 559 | pub fn firmware_len(&self) -> usize { | ||
| 560 | self.dfu.len() | ||
| 561 | } | ||
| 562 | |||
| 563 | /// Instruct bootloader that DFU should commence at next boot. | ||
| 564 | /// Must be provided with an aligned buffer to use for reading and writing magic; | ||
| 565 | pub async fn update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> | ||
| 566 | where | ||
| 567 | [(); F::WRITE_SIZE]:, | ||
| 568 | { | ||
| 569 | let mut aligned = Aligned([0; { F::WRITE_SIZE }]); | ||
| 570 | self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await | ||
| 571 | } | ||
| 572 | 39 | ||
| 573 | /// Mark firmware boot successfully | 40 | impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> { |
| 574 | pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> | 41 | fn as_ref(&self) -> &[u8] { |
| 575 | where | 42 | &self.0 |
| 576 | [(); F::WRITE_SIZE]:, | ||
| 577 | { | ||
| 578 | let mut aligned = Aligned([0; { F::WRITE_SIZE }]); | ||
| 579 | self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await | ||
| 580 | } | 43 | } |
| 44 | } | ||
| 581 | 45 | ||
| 582 | async fn set_magic<F: AsyncNorFlash>( | 46 | impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> { |
| 583 | &mut self, | 47 | fn as_mut(&mut self) -> &mut [u8] { |
| 584 | aligned: &mut [u8], | 48 | &mut self.0 |
| 585 | magic: u8, | ||
| 586 | flash: &mut F, | ||
| 587 | ) -> Result<(), F::Error> { | ||
| 588 | flash.read(self.state.from as u32, aligned).await?; | ||
| 589 | |||
| 590 | if aligned.iter().find(|&&b| b != magic).is_some() { | ||
| 591 | aligned.fill(0); | ||
| 592 | |||
| 593 | flash.write(self.state.from as u32, aligned).await?; | ||
| 594 | flash.erase(self.state.from as u32, self.state.to as u32).await?; | ||
| 595 | |||
| 596 | aligned.fill(magic); | ||
| 597 | flash.write(self.state.from as u32, aligned).await?; | ||
| 598 | } | ||
| 599 | Ok(()) | ||
| 600 | } | ||
| 601 | |||
| 602 | // Write to a region of the DFU page | ||
| 603 | pub async fn write_firmware<F: AsyncNorFlash>( | ||
| 604 | &mut self, | ||
| 605 | offset: usize, | ||
| 606 | data: &[u8], | ||
| 607 | flash: &mut F, | ||
| 608 | block_size: usize, | ||
| 609 | ) -> Result<(), F::Error> { | ||
| 610 | assert!(data.len() >= F::ERASE_SIZE); | ||
| 611 | |||
| 612 | trace!( | ||
| 613 | "Writing firmware at offset 0x{:x} len {}", | ||
| 614 | self.dfu.from + offset, | ||
| 615 | data.len() | ||
| 616 | ); | ||
| 617 | |||
| 618 | flash | ||
| 619 | .erase( | ||
| 620 | (self.dfu.from + offset) as u32, | ||
| 621 | (self.dfu.from + offset + data.len()) as u32, | ||
| 622 | ) | ||
| 623 | .await?; | ||
| 624 | |||
| 625 | trace!( | ||
| 626 | "Erased from {} to {}", | ||
| 627 | self.dfu.from + offset, | ||
| 628 | self.dfu.from + offset + data.len() | ||
| 629 | ); | ||
| 630 | |||
| 631 | let mut write_offset = self.dfu.from + offset; | ||
| 632 | for chunk in data.chunks(block_size) { | ||
| 633 | trace!("Wrote chunk at {}: {:?}", write_offset, chunk); | ||
| 634 | flash.write(write_offset as u32, chunk).await?; | ||
| 635 | write_offset += chunk.len(); | ||
| 636 | } | ||
| 637 | /* | ||
| 638 | trace!("Wrote data, reading back for verification"); | ||
| 639 | |||
| 640 | let mut buf: [u8; 4096] = [0; 4096]; | ||
| 641 | let mut data_offset = 0; | ||
| 642 | let mut read_offset = self.dfu.from + offset; | ||
| 643 | for chunk in buf.chunks_mut(block_size) { | ||
| 644 | flash.read(read_offset as u32, chunk).await?; | ||
| 645 | trace!("Read chunk at {}: {:?}", read_offset, chunk); | ||
| 646 | assert_eq!(&data[data_offset..data_offset + block_size], chunk); | ||
| 647 | read_offset += chunk.len(); | ||
| 648 | data_offset += chunk.len(); | ||
| 649 | } | ||
| 650 | */ | ||
| 651 | |||
| 652 | Ok(()) | ||
| 653 | } | 49 | } |
| 654 | } | 50 | } |
| 655 | 51 | ||
| 656 | #[cfg(test)] | 52 | #[cfg(test)] |
| 657 | mod tests { | 53 | mod tests { |
| 658 | use core::convert::Infallible; | 54 | #![allow(unused_imports)] |
| 659 | use core::future::Future; | ||
| 660 | 55 | ||
| 661 | use embedded_storage::nor_flash::ErrorType; | 56 | use embedded_storage::nor_flash::{NorFlash, ReadNorFlash}; |
| 662 | use embedded_storage_async::nor_flash::AsyncReadNorFlash; | 57 | #[cfg(feature = "nightly")] |
| 58 | use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | ||
| 663 | use futures::executor::block_on; | 59 | use futures::executor::block_on; |
| 664 | 60 | ||
| 665 | use super::*; | 61 | use super::*; |
| 62 | use crate::boot_loader::BootLoaderConfig; | ||
| 63 | use crate::firmware_updater::FirmwareUpdaterConfig; | ||
| 64 | use crate::mem_flash::MemFlash; | ||
| 65 | #[cfg(feature = "nightly")] | ||
| 66 | use crate::test_flash::AsyncTestFlash; | ||
| 67 | use crate::test_flash::BlockingTestFlash; | ||
| 666 | 68 | ||
| 667 | /* | 69 | /* |
| 668 | #[test] | 70 | #[test] |
| 669 | fn test_bad_magic() { | 71 | fn test_bad_magic() { |
| 670 | let mut flash = MemFlash([0xff; 131072]); | 72 | let mut flash = MemFlash([0xff; 131072]); |
| 671 | let mut flash = SingleFlashProvider::new(&mut flash); | 73 | let mut flash = SingleFlashConfig::new(&mut flash); |
| 672 | 74 | ||
| 673 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); | 75 | let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); |
| 674 | 76 | ||
| @@ -681,281 +83,229 @@ mod tests { | |||
| 681 | 83 | ||
| 682 | #[test] | 84 | #[test] |
| 683 | fn test_boot_state() { | 85 | fn test_boot_state() { |
| 684 | const STATE: Partition = Partition::new(0, 4096); | 86 | let flash = BlockingTestFlash::new(BootLoaderConfig { |
| 685 | const ACTIVE: Partition = Partition::new(4096, 61440); | 87 | active: MemFlash::<57344, 4096, 4>::default(), |
| 686 | const DFU: Partition = Partition::new(61440, 122880); | 88 | dfu: MemFlash::<61440, 4096, 4>::default(), |
| 89 | state: MemFlash::<4096, 4096, 4>::default(), | ||
| 90 | }); | ||
| 687 | 91 | ||
| 688 | let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); | 92 | flash.state().write(0, &[BOOT_MAGIC; 4]).unwrap(); |
| 689 | flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); | ||
| 690 | let mut flash = SingleFlashProvider::new(&mut flash); | ||
| 691 | 93 | ||
| 692 | let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); | 94 | let mut bootloader = BootLoader::new(BootLoaderConfig { |
| 95 | active: flash.active(), | ||
| 96 | dfu: flash.dfu(), | ||
| 97 | state: flash.state(), | ||
| 98 | }); | ||
| 693 | 99 | ||
| 694 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); | 100 | let mut page = [0; 4096]; |
| 101 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 695 | } | 102 | } |
| 696 | 103 | ||
| 697 | #[test] | 104 | #[test] |
| 105 | #[cfg(all(feature = "nightly", not(feature = "_verify")))] | ||
| 698 | fn test_swap_state() { | 106 | fn test_swap_state() { |
| 699 | const STATE: Partition = Partition::new(0, 4096); | 107 | const FIRMWARE_SIZE: usize = 57344; |
| 700 | const ACTIVE: Partition = Partition::new(4096, 61440); | 108 | let flash = AsyncTestFlash::new(BootLoaderConfig { |
| 701 | const DFU: Partition = Partition::new(61440, 122880); | 109 | active: MemFlash::<FIRMWARE_SIZE, 4096, 4>::default(), |
| 702 | let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); | 110 | dfu: MemFlash::<61440, 4096, 4>::default(), |
| 703 | 111 | state: MemFlash::<4096, 4096, 4>::default(), | |
| 704 | let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; | 112 | }); |
| 705 | let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; | 113 | |
| 706 | 114 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; | |
| 707 | for i in ACTIVE.from..ACTIVE.to { | 115 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; |
| 708 | flash.0[i] = original[i - ACTIVE.from]; | 116 | let mut aligned = [0; 4]; |
| 709 | } | 117 | |
| 710 | 118 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); | |
| 711 | let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); | 119 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); |
| 712 | let mut updater = FirmwareUpdater::new(DFU, STATE); | 120 | |
| 713 | let mut offset = 0; | 121 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |
| 714 | for chunk in update.chunks(4096) { | 122 | dfu: flash.dfu(), |
| 715 | block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); | 123 | state: flash.state(), |
| 716 | offset += chunk.len(); | 124 | }); |
| 717 | } | 125 | block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); |
| 718 | block_on(updater.update(&mut flash)).unwrap(); | 126 | block_on(updater.mark_updated(&mut aligned)).unwrap(); |
| 719 | 127 | ||
| 720 | assert_eq!( | 128 | // Writing after marking updated is not allowed until marked as booted. |
| 721 | State::Swap, | 129 | let res: Result<(), FirmwareUpdaterError> = block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)); |
| 722 | bootloader | 130 | assert!(matches!(res, Err::<(), _>(FirmwareUpdaterError::BadState))); |
| 723 | .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) | 131 | |
| 724 | .unwrap() | 132 | let flash = flash.into_blocking(); |
| 725 | ); | 133 | let mut bootloader = BootLoader::new(BootLoaderConfig { |
| 726 | 134 | active: flash.active(), | |
| 727 | for i in ACTIVE.from..ACTIVE.to { | 135 | dfu: flash.dfu(), |
| 728 | assert_eq!(flash.0[i], update[i - ACTIVE.from], "Index {}", i); | 136 | state: flash.state(), |
| 729 | } | 137 | }); |
| 730 | 138 | ||
| 139 | let mut page = [0; 1024]; | ||
| 140 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 141 | |||
| 142 | let mut read_buf = [0; FIRMWARE_SIZE]; | ||
| 143 | flash.active().read(0, &mut read_buf).unwrap(); | ||
| 144 | assert_eq!(UPDATE, read_buf); | ||
| 731 | // First DFU page is untouched | 145 | // First DFU page is untouched |
| 732 | for i in DFU.from + 4096..DFU.to { | 146 | flash.dfu().read(4096, &mut read_buf).unwrap(); |
| 733 | assert_eq!(flash.0[i], original[i - DFU.from - 4096], "Index {}", i); | 147 | assert_eq!(ORIGINAL, read_buf); |
| 734 | } | ||
| 735 | 148 | ||
| 736 | // Running again should cause a revert | 149 | // Running again should cause a revert |
| 737 | assert_eq!( | 150 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); |
| 738 | State::Swap, | ||
| 739 | bootloader | ||
| 740 | .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) | ||
| 741 | .unwrap() | ||
| 742 | ); | ||
| 743 | 151 | ||
| 744 | for i in ACTIVE.from..ACTIVE.to { | 152 | let mut read_buf = [0; FIRMWARE_SIZE]; |
| 745 | assert_eq!(flash.0[i], original[i - ACTIVE.from], "Index {}", i); | 153 | flash.active().read(0, &mut read_buf).unwrap(); |
| 746 | } | 154 | assert_eq!(ORIGINAL, read_buf); |
| 747 | 155 | // Last DFU page is untouched | |
| 748 | // Last page is untouched | 156 | flash.dfu().read(0, &mut read_buf).unwrap(); |
| 749 | for i in DFU.from..DFU.to - 4096 { | 157 | assert_eq!(UPDATE, read_buf); |
| 750 | assert_eq!(flash.0[i], update[i - DFU.from], "Index {}", i); | ||
| 751 | } | ||
| 752 | 158 | ||
| 753 | // Mark as booted | 159 | // Mark as booted |
| 754 | block_on(updater.mark_booted(&mut flash)).unwrap(); | 160 | let flash = flash.into_async(); |
| 755 | assert_eq!( | 161 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |
| 756 | State::Boot, | 162 | dfu: flash.dfu(), |
| 757 | bootloader | 163 | state: flash.state(), |
| 758 | .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) | 164 | }); |
| 759 | .unwrap() | 165 | block_on(updater.mark_booted(&mut aligned)).unwrap(); |
| 760 | ); | 166 | |
| 167 | let flash = flash.into_blocking(); | ||
| 168 | let mut bootloader = BootLoader::new(BootLoaderConfig { | ||
| 169 | active: flash.active(), | ||
| 170 | dfu: flash.dfu(), | ||
| 171 | state: flash.state(), | ||
| 172 | }); | ||
| 173 | assert_eq!(State::Boot, bootloader.prepare_boot(&mut page).unwrap()); | ||
| 761 | } | 174 | } |
| 762 | 175 | ||
| 763 | #[test] | 176 | #[test] |
| 764 | fn test_separate_flash_active_page_biggest() { | 177 | #[cfg(all(feature = "nightly", not(feature = "_verify")))] |
| 765 | const STATE: Partition = Partition::new(2048, 4096); | 178 | fn test_swap_state_active_page_biggest() { |
| 766 | const ACTIVE: Partition = Partition::new(4096, 16384); | 179 | const FIRMWARE_SIZE: usize = 12288; |
| 767 | const DFU: Partition = Partition::new(0, 16384); | 180 | let flash = AsyncTestFlash::new(BootLoaderConfig { |
| 768 | 181 | active: MemFlash::<12288, 4096, 8>::random(), | |
| 769 | let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); | 182 | dfu: MemFlash::<16384, 2048, 8>::random(), |
| 770 | let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); | 183 | state: MemFlash::<2048, 128, 4>::random(), |
| 771 | let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); | 184 | }); |
| 772 | 185 | ||
| 773 | let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; | 186 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; |
| 774 | let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; | 187 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; |
| 775 | 188 | let mut aligned = [0; 4]; | |
| 776 | for i in ACTIVE.from..ACTIVE.to { | 189 | |
| 777 | active.0[i] = original[i - ACTIVE.from]; | 190 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); |
| 778 | } | 191 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); |
| 779 | 192 | ||
| 780 | let mut updater = FirmwareUpdater::new(DFU, STATE); | 193 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |
| 781 | 194 | dfu: flash.dfu(), | |
| 782 | let mut offset = 0; | 195 | state: flash.state(), |
| 783 | for chunk in update.chunks(2048) { | 196 | }); |
| 784 | block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); | 197 | block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); |
| 785 | offset += chunk.len(); | 198 | block_on(updater.mark_updated(&mut aligned)).unwrap(); |
| 786 | } | 199 | |
| 787 | block_on(updater.update(&mut state)).unwrap(); | 200 | let flash = flash.into_blocking(); |
| 788 | 201 | let mut bootloader = BootLoader::new(BootLoaderConfig { | |
| 789 | let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); | 202 | active: flash.active(), |
| 790 | assert_eq!( | 203 | dfu: flash.dfu(), |
| 791 | State::Swap, | 204 | state: flash.state(), |
| 792 | bootloader | 205 | }); |
| 793 | .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) | 206 | |
| 794 | .unwrap() | 207 | let mut page = [0; 4096]; |
| 795 | ); | 208 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); |
| 796 | 209 | ||
| 797 | for i in ACTIVE.from..ACTIVE.to { | 210 | let mut read_buf = [0; FIRMWARE_SIZE]; |
| 798 | assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); | 211 | flash.active().read(0, &mut read_buf).unwrap(); |
| 799 | } | 212 | assert_eq!(UPDATE, read_buf); |
| 800 | |||
| 801 | // First DFU page is untouched | 213 | // First DFU page is untouched |
| 802 | for i in DFU.from + 4096..DFU.to { | 214 | flash.dfu().read(4096, &mut read_buf).unwrap(); |
| 803 | assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); | 215 | assert_eq!(ORIGINAL, read_buf); |
| 804 | } | ||
| 805 | } | 216 | } |
| 806 | 217 | ||
| 807 | #[test] | 218 | #[test] |
| 808 | fn test_separate_flash_dfu_page_biggest() { | 219 | #[cfg(all(feature = "nightly", not(feature = "_verify")))] |
| 809 | const STATE: Partition = Partition::new(2048, 4096); | 220 | fn test_swap_state_dfu_page_biggest() { |
| 810 | const ACTIVE: Partition = Partition::new(4096, 16384); | 221 | const FIRMWARE_SIZE: usize = 12288; |
| 811 | const DFU: Partition = Partition::new(0, 16384); | 222 | let flash = AsyncTestFlash::new(BootLoaderConfig { |
| 812 | 223 | active: MemFlash::<FIRMWARE_SIZE, 2048, 4>::random(), | |
| 813 | let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); | 224 | dfu: MemFlash::<16384, 4096, 8>::random(), |
| 814 | let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); | 225 | state: MemFlash::<2048, 128, 4>::random(), |
| 815 | let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); | 226 | }); |
| 816 | 227 | ||
| 817 | let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; | 228 | const ORIGINAL: [u8; FIRMWARE_SIZE] = [0x55; FIRMWARE_SIZE]; |
| 818 | let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; | 229 | const UPDATE: [u8; FIRMWARE_SIZE] = [0xAA; FIRMWARE_SIZE]; |
| 819 | 230 | let mut aligned = [0; 4]; | |
| 820 | for i in ACTIVE.from..ACTIVE.to { | 231 | |
| 821 | active.0[i] = original[i - ACTIVE.from]; | 232 | block_on(flash.active().erase(0, ORIGINAL.len() as u32)).unwrap(); |
| 822 | } | 233 | block_on(flash.active().write(0, &ORIGINAL)).unwrap(); |
| 823 | 234 | ||
| 824 | let mut updater = FirmwareUpdater::new(DFU, STATE); | 235 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |
| 825 | 236 | dfu: flash.dfu(), | |
| 826 | let mut offset = 0; | 237 | state: flash.state(), |
| 827 | for chunk in update.chunks(4096) { | 238 | }); |
| 828 | block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); | 239 | block_on(updater.write_firmware(&mut aligned, 0, &UPDATE)).unwrap(); |
| 829 | offset += chunk.len(); | 240 | block_on(updater.mark_updated(&mut aligned)).unwrap(); |
| 830 | } | 241 | |
| 831 | block_on(updater.update(&mut state)).unwrap(); | 242 | let flash = flash.into_blocking(); |
| 832 | 243 | let mut bootloader = BootLoader::new(BootLoaderConfig { | |
| 833 | let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); | 244 | active: flash.active(), |
| 834 | assert_eq!( | 245 | dfu: flash.dfu(), |
| 835 | State::Swap, | 246 | state: flash.state(), |
| 836 | bootloader | 247 | }); |
| 837 | .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) | 248 | let mut page = [0; 4096]; |
| 838 | .unwrap() | 249 | assert_eq!(State::Swap, bootloader.prepare_boot(&mut page).unwrap()); |
| 839 | ); | 250 | |
| 840 | 251 | let mut read_buf = [0; FIRMWARE_SIZE]; | |
| 841 | for i in ACTIVE.from..ACTIVE.to { | 252 | flash.active().read(0, &mut read_buf).unwrap(); |
| 842 | assert_eq!(active.0[i], update[i - ACTIVE.from], "Index {}", i); | 253 | assert_eq!(UPDATE, read_buf); |
| 843 | } | ||
| 844 | |||
| 845 | // First DFU page is untouched | 254 | // First DFU page is untouched |
| 846 | for i in DFU.from + 4096..DFU.to { | 255 | flash.dfu().read(4096, &mut read_buf).unwrap(); |
| 847 | assert_eq!(dfu.0[i], original[i - DFU.from - 4096], "Index {}", i); | 256 | assert_eq!(ORIGINAL, read_buf); |
| 848 | } | ||
| 849 | } | ||
| 850 | |||
| 851 | struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize>([u8; SIZE]); | ||
| 852 | |||
| 853 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash | ||
| 854 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 855 | { | ||
| 856 | const WRITE_SIZE: usize = WRITE_SIZE; | ||
| 857 | const ERASE_SIZE: usize = ERASE_SIZE; | ||
| 858 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 859 | let from = from as usize; | ||
| 860 | let to = to as usize; | ||
| 861 | assert!(from % ERASE_SIZE == 0); | ||
| 862 | assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); | ||
| 863 | for i in from..to { | ||
| 864 | self.0[i] = 0xFF; | ||
| 865 | } | ||
| 866 | Ok(()) | ||
| 867 | } | ||
| 868 | |||
| 869 | fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { | ||
| 870 | assert!(data.len() % WRITE_SIZE == 0); | ||
| 871 | assert!(offset as usize % WRITE_SIZE == 0); | ||
| 872 | assert!(offset as usize + data.len() <= SIZE); | ||
| 873 | |||
| 874 | self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data); | ||
| 875 | |||
| 876 | Ok(()) | ||
| 877 | } | ||
| 878 | } | 257 | } |
| 879 | 258 | ||
| 880 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType | 259 | #[test] |
| 881 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | 260 | #[cfg(all(feature = "nightly", feature = "_verify"))] |
| 882 | { | 261 | fn test_verify() { |
| 883 | type Error = Infallible; | 262 | // The following key setup is based on: |
| 884 | } | 263 | // https://docs.rs/ed25519-dalek/latest/ed25519_dalek/#example |
| 885 | 264 | ||
| 886 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash | 265 | use ed25519_dalek::Keypair; |
| 887 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | 266 | use rand::rngs::OsRng; |
| 888 | { | 267 | |
| 889 | const READ_SIZE: usize = 4; | 268 | let mut csprng = OsRng {}; |
| 890 | 269 | let keypair: Keypair = Keypair::generate(&mut csprng); | |
| 891 | fn read(&mut self, offset: u32, buf: &mut [u8]) -> Result<(), Self::Error> { | 270 | |
| 892 | let len = buf.len(); | 271 | use ed25519_dalek::{Digest, Sha512, Signature, Signer}; |
| 893 | buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); | 272 | let firmware: &[u8] = b"This are bytes that would otherwise be firmware bytes for DFU."; |
| 894 | Ok(()) | 273 | let mut digest = Sha512::new(); |
| 895 | } | 274 | digest.update(&firmware); |
| 896 | 275 | let message = digest.finalize(); | |
| 897 | fn capacity(&self) -> usize { | 276 | let signature: Signature = keypair.sign(&message); |
| 898 | SIZE | 277 | |
| 899 | } | 278 | use ed25519_dalek::PublicKey; |
| 900 | } | 279 | let public_key: PublicKey = keypair.public; |
| 901 | 280 | ||
| 902 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash | 281 | // Setup flash |
| 903 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | 282 | let flash = BlockingTestFlash::new(BootLoaderConfig { |
| 904 | { | 283 | active: MemFlash::<0, 0, 0>::default(), |
| 905 | const READ_SIZE: usize = 4; | 284 | dfu: MemFlash::<4096, 4096, 4>::default(), |
| 906 | 285 | state: MemFlash::<4096, 4096, 4>::default(), | |
| 907 | type ReadFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; | 286 | }); |
| 908 | fn read<'a>(&'a mut self, offset: u32, buf: &'a mut [u8]) -> Self::ReadFuture<'a> { | 287 | |
| 909 | async move { | 288 | let firmware_len = firmware.len(); |
| 910 | let len = buf.len(); | 289 | |
| 911 | buf[..].copy_from_slice(&self.0[offset as usize..offset as usize + len]); | 290 | let mut write_buf = [0; 4096]; |
| 912 | Ok(()) | 291 | write_buf[0..firmware_len].copy_from_slice(firmware); |
| 913 | } | 292 | flash.dfu().write(0, &write_buf).unwrap(); |
| 914 | } | 293 | |
| 915 | 294 | // On with the test | |
| 916 | fn capacity(&self) -> usize { | 295 | let flash = flash.into_async(); |
| 917 | SIZE | 296 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { |
| 918 | } | 297 | dfu: flash.dfu(), |
| 919 | } | 298 | state: flash.state(), |
| 920 | 299 | }); | |
| 921 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash | 300 | |
| 922 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | 301 | let mut aligned = [0; 4]; |
| 923 | { | 302 | |
| 924 | const WRITE_SIZE: usize = WRITE_SIZE; | 303 | assert!(block_on(updater.verify_and_mark_updated( |
| 925 | const ERASE_SIZE: usize = ERASE_SIZE; | 304 | &public_key.to_bytes(), |
| 926 | 305 | &signature.to_bytes(), | |
| 927 | type EraseFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; | 306 | firmware_len as u32, |
| 928 | fn erase<'a>(&'a mut self, from: u32, to: u32) -> Self::EraseFuture<'a> { | 307 | &mut aligned, |
| 929 | async move { | 308 | )) |
| 930 | let from = from as usize; | 309 | .is_ok()); |
| 931 | let to = to as usize; | ||
| 932 | assert!(from % ERASE_SIZE == 0); | ||
| 933 | assert!(to % ERASE_SIZE == 0); | ||
| 934 | for i in from..to { | ||
| 935 | self.0[i] = 0xFF; | ||
| 936 | } | ||
| 937 | Ok(()) | ||
| 938 | } | ||
| 939 | } | ||
| 940 | |||
| 941 | type WriteFuture<'a> = impl Future<Output = Result<(), Self::Error>> + 'a; | ||
| 942 | fn write<'a>(&'a mut self, offset: u32, data: &'a [u8]) -> Self::WriteFuture<'a> { | ||
| 943 | info!("Writing {} bytes to 0x{:x}", data.len(), offset); | ||
| 944 | async move { | ||
| 945 | assert!(data.len() % WRITE_SIZE == 0); | ||
| 946 | assert!(offset as usize % WRITE_SIZE == 0); | ||
| 947 | assert!( | ||
| 948 | offset as usize + data.len() <= SIZE, | ||
| 949 | "OFFSET: {}, LEN: {}, FLASH SIZE: {}", | ||
| 950 | offset, | ||
| 951 | data.len(), | ||
| 952 | SIZE | ||
| 953 | ); | ||
| 954 | |||
| 955 | self.0[offset as usize..offset as usize + data.len()].copy_from_slice(data); | ||
| 956 | |||
| 957 | Ok(()) | ||
| 958 | } | ||
| 959 | } | ||
| 960 | } | 310 | } |
| 961 | } | 311 | } |
diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs new file mode 100644 index 000000000..2728e9720 --- /dev/null +++ b/embassy-boot/boot/src/mem_flash.rs | |||
| @@ -0,0 +1,173 @@ | |||
| 1 | #![allow(unused)] | ||
| 2 | |||
| 3 | use core::ops::{Bound, Range, RangeBounds}; | ||
| 4 | |||
| 5 | use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; | ||
| 6 | #[cfg(feature = "nightly")] | ||
| 7 | use embedded_storage_async::nor_flash::{NorFlash as AsyncNorFlash, ReadNorFlash as AsyncReadNorFlash}; | ||
| 8 | |||
| 9 | pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> { | ||
| 10 | pub mem: [u8; SIZE], | ||
| 11 | pub pending_write_successes: Option<usize>, | ||
| 12 | } | ||
| 13 | |||
| 14 | #[derive(Debug)] | ||
| 15 | pub struct MemFlashError; | ||
| 16 | |||
| 17 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> { | ||
| 18 | pub const fn new(fill: u8) -> Self { | ||
| 19 | Self { | ||
| 20 | mem: [fill; SIZE], | ||
| 21 | pending_write_successes: None, | ||
| 22 | } | ||
| 23 | } | ||
| 24 | |||
| 25 | #[cfg(test)] | ||
| 26 | pub fn random() -> Self { | ||
| 27 | let mut mem = [0; SIZE]; | ||
| 28 | for byte in mem.iter_mut() { | ||
| 29 | *byte = rand::random::<u8>(); | ||
| 30 | } | ||
| 31 | Self { | ||
| 32 | mem, | ||
| 33 | pending_write_successes: None, | ||
| 34 | } | ||
| 35 | } | ||
| 36 | |||
| 37 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), MemFlashError> { | ||
| 38 | let len = bytes.len(); | ||
| 39 | bytes.copy_from_slice(&self.mem[offset as usize..offset as usize + len]); | ||
| 40 | Ok(()) | ||
| 41 | } | ||
| 42 | |||
| 43 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { | ||
| 44 | let offset = offset as usize; | ||
| 45 | assert!(bytes.len() % WRITE_SIZE == 0); | ||
| 46 | assert!(offset % WRITE_SIZE == 0); | ||
| 47 | assert!(offset + bytes.len() <= SIZE); | ||
| 48 | |||
| 49 | if let Some(pending_successes) = self.pending_write_successes { | ||
| 50 | if pending_successes > 0 { | ||
| 51 | self.pending_write_successes = Some(pending_successes - 1); | ||
| 52 | } else { | ||
| 53 | return Err(MemFlashError); | ||
| 54 | } | ||
| 55 | } | ||
| 56 | |||
| 57 | for ((offset, mem_byte), new_byte) in self | ||
| 58 | .mem | ||
| 59 | .iter_mut() | ||
| 60 | .enumerate() | ||
| 61 | .skip(offset) | ||
| 62 | .take(bytes.len()) | ||
| 63 | .zip(bytes) | ||
| 64 | { | ||
| 65 | assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset); | ||
| 66 | *mem_byte = *new_byte; | ||
| 67 | } | ||
| 68 | |||
| 69 | Ok(()) | ||
| 70 | } | ||
| 71 | |||
| 72 | fn erase(&mut self, from: u32, to: u32) -> Result<(), MemFlashError> { | ||
| 73 | let from = from as usize; | ||
| 74 | let to = to as usize; | ||
| 75 | assert!(from % ERASE_SIZE == 0); | ||
| 76 | assert!(to % ERASE_SIZE == 0, "To: {}, erase size: {}", to, ERASE_SIZE); | ||
| 77 | for i in from..to { | ||
| 78 | self.mem[i] = 0xFF; | ||
| 79 | } | ||
| 80 | Ok(()) | ||
| 81 | } | ||
| 82 | |||
| 83 | pub fn program(&mut self, offset: u32, bytes: &[u8]) -> Result<(), MemFlashError> { | ||
| 84 | let offset = offset as usize; | ||
| 85 | assert!(bytes.len() % WRITE_SIZE == 0); | ||
| 86 | assert!(offset % WRITE_SIZE == 0); | ||
| 87 | assert!(offset + bytes.len() <= SIZE); | ||
| 88 | |||
| 89 | self.mem[offset..offset + bytes.len()].copy_from_slice(bytes); | ||
| 90 | |||
| 91 | Ok(()) | ||
| 92 | } | ||
| 93 | } | ||
| 94 | |||
| 95 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default | ||
| 96 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 97 | { | ||
| 98 | fn default() -> Self { | ||
| 99 | Self::new(0xFF) | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ErrorType | ||
| 104 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 105 | { | ||
| 106 | type Error = MemFlashError; | ||
| 107 | } | ||
| 108 | |||
| 109 | impl NorFlashError for MemFlashError { | ||
| 110 | fn kind(&self) -> NorFlashErrorKind { | ||
| 111 | NorFlashErrorKind::Other | ||
| 112 | } | ||
| 113 | } | ||
| 114 | |||
| 115 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> ReadNorFlash | ||
| 116 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 117 | { | ||
| 118 | const READ_SIZE: usize = 1; | ||
| 119 | |||
| 120 | fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||
| 121 | self.read(offset, bytes) | ||
| 122 | } | ||
| 123 | |||
| 124 | fn capacity(&self) -> usize { | ||
| 125 | SIZE | ||
| 126 | } | ||
| 127 | } | ||
| 128 | |||
| 129 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFlash | ||
| 130 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 131 | { | ||
| 132 | const WRITE_SIZE: usize = WRITE_SIZE; | ||
| 133 | const ERASE_SIZE: usize = ERASE_SIZE; | ||
| 134 | |||
| 135 | fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||
| 136 | self.write(offset, bytes) | ||
| 137 | } | ||
| 138 | |||
| 139 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 140 | self.erase(from, to) | ||
| 141 | } | ||
| 142 | } | ||
| 143 | |||
| 144 | #[cfg(feature = "nightly")] | ||
| 145 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash | ||
| 146 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 147 | { | ||
| 148 | const READ_SIZE: usize = 1; | ||
| 149 | |||
| 150 | async fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> { | ||
| 151 | self.read(offset, bytes) | ||
| 152 | } | ||
| 153 | |||
| 154 | fn capacity(&self) -> usize { | ||
| 155 | SIZE | ||
| 156 | } | ||
| 157 | } | ||
| 158 | |||
| 159 | #[cfg(feature = "nightly")] | ||
| 160 | impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncNorFlash | ||
| 161 | for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> | ||
| 162 | { | ||
| 163 | const WRITE_SIZE: usize = WRITE_SIZE; | ||
| 164 | const ERASE_SIZE: usize = ERASE_SIZE; | ||
| 165 | |||
| 166 | async fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> { | ||
| 167 | self.write(offset, bytes) | ||
| 168 | } | ||
| 169 | |||
| 170 | async fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 171 | self.erase(from, to) | ||
| 172 | } | ||
| 173 | } | ||
diff --git a/embassy-boot/boot/src/test_flash/asynch.rs b/embassy-boot/boot/src/test_flash/asynch.rs new file mode 100644 index 000000000..3ac9e71ab --- /dev/null +++ b/embassy-boot/boot/src/test_flash/asynch.rs | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 2 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 3 | use embassy_sync::mutex::Mutex; | ||
| 4 | use embedded_storage_async::nor_flash::NorFlash; | ||
| 5 | |||
| 6 | use crate::BootLoaderConfig; | ||
| 7 | |||
| 8 | pub struct AsyncTestFlash<ACTIVE, DFU, STATE> | ||
| 9 | where | ||
| 10 | ACTIVE: NorFlash, | ||
| 11 | DFU: NorFlash, | ||
| 12 | STATE: NorFlash, | ||
| 13 | { | ||
| 14 | active: Mutex<NoopRawMutex, ACTIVE>, | ||
| 15 | dfu: Mutex<NoopRawMutex, DFU>, | ||
| 16 | state: Mutex<NoopRawMutex, STATE>, | ||
| 17 | } | ||
| 18 | |||
| 19 | impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE> | ||
| 20 | where | ||
| 21 | ACTIVE: NorFlash, | ||
| 22 | DFU: NorFlash, | ||
| 23 | STATE: NorFlash, | ||
| 24 | { | ||
| 25 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 26 | Self { | ||
| 27 | active: Mutex::new(config.active), | ||
| 28 | dfu: Mutex::new(config.dfu), | ||
| 29 | state: Mutex::new(config.state), | ||
| 30 | } | ||
| 31 | } | ||
| 32 | |||
| 33 | pub fn active(&self) -> Partition<NoopRawMutex, ACTIVE> { | ||
| 34 | Self::create_partition(&self.active) | ||
| 35 | } | ||
| 36 | |||
| 37 | pub fn dfu(&self) -> Partition<NoopRawMutex, DFU> { | ||
| 38 | Self::create_partition(&self.dfu) | ||
| 39 | } | ||
| 40 | |||
| 41 | pub fn state(&self) -> Partition<NoopRawMutex, STATE> { | ||
| 42 | Self::create_partition(&self.state) | ||
| 43 | } | ||
| 44 | |||
| 45 | fn create_partition<T: NorFlash>(mutex: &Mutex<NoopRawMutex, T>) -> Partition<NoopRawMutex, T> { | ||
| 46 | Partition::new(mutex, 0, mutex.try_lock().unwrap().capacity() as u32) | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | impl<ACTIVE, DFU, STATE> AsyncTestFlash<ACTIVE, DFU, STATE> | ||
| 51 | where | ||
| 52 | ACTIVE: NorFlash + embedded_storage::nor_flash::NorFlash, | ||
| 53 | DFU: NorFlash + embedded_storage::nor_flash::NorFlash, | ||
| 54 | STATE: NorFlash + embedded_storage::nor_flash::NorFlash, | ||
| 55 | { | ||
| 56 | pub fn into_blocking(self) -> super::BlockingTestFlash<ACTIVE, DFU, STATE> { | ||
| 57 | let config = BootLoaderConfig { | ||
| 58 | active: self.active.into_inner(), | ||
| 59 | dfu: self.dfu.into_inner(), | ||
| 60 | state: self.state.into_inner(), | ||
| 61 | }; | ||
| 62 | super::BlockingTestFlash::new(config) | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/embassy-boot/boot/src/test_flash/blocking.rs b/embassy-boot/boot/src/test_flash/blocking.rs new file mode 100644 index 000000000..ba33c9208 --- /dev/null +++ b/embassy-boot/boot/src/test_flash/blocking.rs | |||
| @@ -0,0 +1,69 @@ | |||
| 1 | use core::cell::RefCell; | ||
| 2 | |||
| 3 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 4 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 5 | use embassy_sync::blocking_mutex::Mutex; | ||
| 6 | use embedded_storage::nor_flash::NorFlash; | ||
| 7 | |||
| 8 | use crate::BootLoaderConfig; | ||
| 9 | |||
| 10 | pub struct BlockingTestFlash<ACTIVE, DFU, STATE> | ||
| 11 | where | ||
| 12 | ACTIVE: NorFlash, | ||
| 13 | DFU: NorFlash, | ||
| 14 | STATE: NorFlash, | ||
| 15 | { | ||
| 16 | active: Mutex<NoopRawMutex, RefCell<ACTIVE>>, | ||
| 17 | dfu: Mutex<NoopRawMutex, RefCell<DFU>>, | ||
| 18 | state: Mutex<NoopRawMutex, RefCell<STATE>>, | ||
| 19 | } | ||
| 20 | |||
| 21 | impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE> | ||
| 22 | where | ||
| 23 | ACTIVE: NorFlash, | ||
| 24 | DFU: NorFlash, | ||
| 25 | STATE: NorFlash, | ||
| 26 | { | ||
| 27 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 28 | Self { | ||
| 29 | active: Mutex::new(RefCell::new(config.active)), | ||
| 30 | dfu: Mutex::new(RefCell::new(config.dfu)), | ||
| 31 | state: Mutex::new(RefCell::new(config.state)), | ||
| 32 | } | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn active(&self) -> BlockingPartition<NoopRawMutex, ACTIVE> { | ||
| 36 | Self::create_partition(&self.active) | ||
| 37 | } | ||
| 38 | |||
| 39 | pub fn dfu(&self) -> BlockingPartition<NoopRawMutex, DFU> { | ||
| 40 | Self::create_partition(&self.dfu) | ||
| 41 | } | ||
| 42 | |||
| 43 | pub fn state(&self) -> BlockingPartition<NoopRawMutex, STATE> { | ||
| 44 | Self::create_partition(&self.state) | ||
| 45 | } | ||
| 46 | |||
| 47 | pub fn create_partition<T: NorFlash>( | ||
| 48 | mutex: &Mutex<NoopRawMutex, RefCell<T>>, | ||
| 49 | ) -> BlockingPartition<NoopRawMutex, T> { | ||
| 50 | BlockingPartition::new(mutex, 0, mutex.lock(|f| f.borrow().capacity()) as u32) | ||
| 51 | } | ||
| 52 | } | ||
| 53 | |||
| 54 | #[cfg(feature = "nightly")] | ||
| 55 | impl<ACTIVE, DFU, STATE> BlockingTestFlash<ACTIVE, DFU, STATE> | ||
| 56 | where | ||
| 57 | ACTIVE: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||
| 58 | DFU: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||
| 59 | STATE: NorFlash + embedded_storage_async::nor_flash::NorFlash, | ||
| 60 | { | ||
| 61 | pub fn into_async(self) -> super::AsyncTestFlash<ACTIVE, DFU, STATE> { | ||
| 62 | let config = BootLoaderConfig { | ||
| 63 | active: self.active.into_inner().into_inner(), | ||
| 64 | dfu: self.dfu.into_inner().into_inner(), | ||
| 65 | state: self.state.into_inner().into_inner(), | ||
| 66 | }; | ||
| 67 | super::AsyncTestFlash::new(config) | ||
| 68 | } | ||
| 69 | } | ||
diff --git a/embassy-boot/boot/src/test_flash/mod.rs b/embassy-boot/boot/src/test_flash/mod.rs new file mode 100644 index 000000000..a0672322e --- /dev/null +++ b/embassy-boot/boot/src/test_flash/mod.rs | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | #[cfg(feature = "nightly")] | ||
| 2 | mod asynch; | ||
| 3 | mod blocking; | ||
| 4 | |||
| 5 | #[cfg(feature = "nightly")] | ||
| 6 | pub(crate) use asynch::AsyncTestFlash; | ||
| 7 | pub(crate) use blocking::BlockingTestFlash; | ||
diff --git a/embassy-boot/nrf/Cargo.toml b/embassy-boot/nrf/Cargo.toml index 234393e7c..8186a9958 100644 --- a/embassy-boot/nrf/Cargo.toml +++ b/embassy-boot/nrf/Cargo.toml | |||
| @@ -3,6 +3,7 @@ edition = "2021" | |||
| 3 | name = "embassy-boot-nrf" | 3 | name = "embassy-boot-nrf" |
| 4 | version = "0.1.0" | 4 | version = "0.1.0" |
| 5 | description = "Bootloader lib for nRF chips" | 5 | description = "Bootloader lib for nRF chips" |
| 6 | license = "MIT OR Apache-2.0" | ||
| 6 | 7 | ||
| 7 | [package.metadata.embassy_docs] | 8 | [package.metadata.embassy_docs] |
| 8 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" | 9 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/nrf/src/" |
| @@ -16,12 +17,12 @@ target = "thumbv7em-none-eabi" | |||
| 16 | defmt = { version = "0.3", optional = true } | 17 | defmt = { version = "0.3", optional = true } |
| 17 | 18 | ||
| 18 | embassy-sync = { path = "../../embassy-sync" } | 19 | embassy-sync = { path = "../../embassy-sync" } |
| 19 | embassy-nrf = { path = "../../embassy-nrf", default-features = false, features = ["nightly"] } | 20 | embassy-nrf = { path = "../../embassy-nrf" } |
| 20 | embassy-boot = { path = "../boot", default-features = false } | 21 | embassy-boot = { path = "../boot", default-features = false } |
| 21 | cortex-m = { version = "0.7.6" } | 22 | cortex-m = { version = "0.7.6" } |
| 22 | cortex-m-rt = { version = "0.7" } | 23 | cortex-m-rt = { version = "0.7" } |
| 23 | embedded-storage = "0.3.0" | 24 | embedded-storage = "0.3.0" |
| 24 | embedded-storage-async = "0.3.0" | 25 | embedded-storage-async = { version = "0.4.0", optional = true } |
| 25 | cfg-if = "1.0.0" | 26 | cfg-if = "1.0.0" |
| 26 | 27 | ||
| 27 | nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true } | 28 | nrf-softdevice-mbr = { version = "0.1.0", git = "https://github.com/embassy-rs/nrf-softdevice.git", branch = "master", optional = true } |
| @@ -35,3 +36,8 @@ defmt = [ | |||
| 35 | softdevice = [ | 36 | softdevice = [ |
| 36 | "nrf-softdevice-mbr", | 37 | "nrf-softdevice-mbr", |
| 37 | ] | 38 | ] |
| 39 | nightly = [ | ||
| 40 | "dep:embedded-storage-async", | ||
| 41 | "embassy-boot/nightly", | ||
| 42 | "embassy-nrf/nightly" | ||
| 43 | ] | ||
diff --git a/embassy-boot/nrf/README.md b/embassy-boot/nrf/README.md new file mode 100644 index 000000000..fe581823d --- /dev/null +++ b/embassy-boot/nrf/README.md | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | # embassy-boot-nrf | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | An adaptation of `embassy-boot` for nRF. | ||
| 6 | |||
| 7 | ## Features | ||
| 8 | |||
| 9 | * Load applications with or without the softdevice. | ||
| 10 | * Configure bootloader partitions based on linker script. | ||
| 11 | * Using watchdog timer to detect application failure. | ||
| 12 | |||
| 13 | |||
| 14 | ## Minimum supported Rust version (MSRV) | ||
| 15 | |||
| 16 | `embassy-boot-nrf` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 17 | |||
| 18 | ## License | ||
| 19 | |||
| 20 | This work is licensed under either of | ||
| 21 | |||
| 22 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 23 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 24 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 25 | |||
| 26 | at your option. | ||
diff --git a/embassy-boot/nrf/src/lib.rs b/embassy-boot/nrf/src/lib.rs index 2d6b837cb..bb702073c 100644 --- a/embassy-boot/nrf/src/lib.rs +++ b/embassy-boot/nrf/src/lib.rs | |||
| @@ -1,88 +1,63 @@ | |||
| 1 | #![no_std] | 1 | #![no_std] |
| 2 | #![feature(generic_associated_types)] | 2 | #![warn(missing_docs)] |
| 3 | #![feature(type_alias_impl_trait)] | 3 | #![doc = include_str!("../README.md")] |
| 4 | #![allow(incomplete_features)] | ||
| 5 | #![feature(generic_const_exprs)] | ||
| 6 | |||
| 7 | mod fmt; | 4 | mod fmt; |
| 8 | 5 | ||
| 9 | pub use embassy_boot::{FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider}; | 6 | #[cfg(feature = "nightly")] |
| 7 | pub use embassy_boot::FirmwareUpdater; | ||
| 8 | pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig}; | ||
| 10 | use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; | 9 | use embassy_nrf::nvmc::{Nvmc, PAGE_SIZE}; |
| 11 | use embassy_nrf::peripherals::WDT; | 10 | use embassy_nrf::peripherals::WDT; |
| 12 | use embassy_nrf::wdt; | 11 | use embassy_nrf::wdt; |
| 13 | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | 12 | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; |
| 14 | 13 | ||
| 15 | pub struct BootLoader { | 14 | /// A bootloader for nRF devices. |
| 16 | boot: embassy_boot::BootLoader<PAGE_SIZE>, | 15 | pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = PAGE_SIZE> { |
| 16 | boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>, | ||
| 17 | aligned_buf: AlignedBuffer<BUFFER_SIZE>, | ||
| 17 | } | 18 | } |
| 18 | 19 | ||
| 19 | impl BootLoader { | 20 | impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> |
| 20 | /// Create a new bootloader instance using parameters from linker script | 21 | BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE> |
| 21 | pub fn default() -> Self { | 22 | { |
| 22 | extern "C" { | ||
| 23 | static __bootloader_state_start: u32; | ||
| 24 | static __bootloader_state_end: u32; | ||
| 25 | static __bootloader_active_start: u32; | ||
| 26 | static __bootloader_active_end: u32; | ||
| 27 | static __bootloader_dfu_start: u32; | ||
| 28 | static __bootloader_dfu_end: u32; | ||
| 29 | } | ||
| 30 | |||
| 31 | let active = unsafe { | ||
| 32 | Partition::new( | ||
| 33 | &__bootloader_active_start as *const u32 as usize, | ||
| 34 | &__bootloader_active_end as *const u32 as usize, | ||
| 35 | ) | ||
| 36 | }; | ||
| 37 | let dfu = unsafe { | ||
| 38 | Partition::new( | ||
| 39 | &__bootloader_dfu_start as *const u32 as usize, | ||
| 40 | &__bootloader_dfu_end as *const u32 as usize, | ||
| 41 | ) | ||
| 42 | }; | ||
| 43 | let state = unsafe { | ||
| 44 | Partition::new( | ||
| 45 | &__bootloader_state_start as *const u32 as usize, | ||
| 46 | &__bootloader_state_end as *const u32 as usize, | ||
| 47 | ) | ||
| 48 | }; | ||
| 49 | |||
| 50 | trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); | ||
| 51 | trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||
| 52 | trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||
| 53 | |||
| 54 | Self::new(active, dfu, state) | ||
| 55 | } | ||
| 56 | |||
| 57 | /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | 23 | /// Create a new bootloader instance using the supplied partitions for active, dfu and state. |
| 58 | pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | 24 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { |
| 59 | Self { | 25 | Self { |
| 60 | boot: embassy_boot::BootLoader::new(active, dfu, state), | 26 | boot: embassy_boot::BootLoader::new(config), |
| 27 | aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), | ||
| 61 | } | 28 | } |
| 62 | } | 29 | } |
| 63 | 30 | ||
| 64 | /// Boots the application without softdevice mechanisms | 31 | /// Inspect the bootloader state and perform actions required before booting, such as swapping |
| 65 | pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize | 32 | /// firmware. |
| 66 | where | 33 | pub fn prepare(&mut self) { |
| 67 | [(); <<F as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | 34 | self.boot |
| 68 | [(); <<F as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, | 35 | .prepare_boot(&mut self.aligned_buf.0) |
| 69 | { | 36 | .expect("Boot prepare error"); |
| 70 | match self.boot.prepare_boot(flash) { | ||
| 71 | Ok(_) => self.boot.boot_address(), | ||
| 72 | Err(_) => panic!("boot prepare error!"), | ||
| 73 | } | ||
| 74 | } | 37 | } |
| 75 | 38 | ||
| 39 | /// Boots the application without softdevice mechanisms. | ||
| 40 | /// | ||
| 41 | /// # Safety | ||
| 42 | /// | ||
| 43 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 76 | #[cfg(not(feature = "softdevice"))] | 44 | #[cfg(not(feature = "softdevice"))] |
| 77 | pub unsafe fn load(&mut self, start: usize) -> ! { | 45 | pub unsafe fn load(self, start: u32) -> ! { |
| 46 | core::mem::drop(self.boot); | ||
| 47 | |||
| 78 | let mut p = cortex_m::Peripherals::steal(); | 48 | let mut p = cortex_m::Peripherals::steal(); |
| 79 | p.SCB.invalidate_icache(); | 49 | p.SCB.invalidate_icache(); |
| 80 | p.SCB.vtor.write(start as u32); | 50 | p.SCB.vtor.write(start); |
| 81 | cortex_m::asm::bootload(start as *const u32) | 51 | cortex_m::asm::bootload(start as *const u32) |
| 82 | } | 52 | } |
| 83 | 53 | ||
| 54 | /// Boots the application assuming softdevice is present. | ||
| 55 | /// | ||
| 56 | /// # Safety | ||
| 57 | /// | ||
| 58 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 84 | #[cfg(feature = "softdevice")] | 59 | #[cfg(feature = "softdevice")] |
| 85 | pub unsafe fn load(&mut self, _app: usize) -> ! { | 60 | pub unsafe fn load(&mut self, _app: u32) -> ! { |
| 86 | use nrf_softdevice_mbr as mbr; | 61 | use nrf_softdevice_mbr as mbr; |
| 87 | const NRF_SUCCESS: u32 = 0; | 62 | const NRF_SUCCESS: u32 = 0; |
| 88 | 63 | ||
| @@ -137,11 +112,7 @@ pub struct WatchdogFlash<'d> { | |||
| 137 | 112 | ||
| 138 | impl<'d> WatchdogFlash<'d> { | 113 | impl<'d> WatchdogFlash<'d> { |
| 139 | /// Start a new watchdog with a given flash and WDT peripheral and a timeout | 114 | /// Start a new watchdog with a given flash and WDT peripheral and a timeout |
| 140 | pub fn start(flash: Nvmc<'d>, wdt: WDT, timeout: u32) -> Self { | 115 | pub fn start(flash: Nvmc<'d>, wdt: WDT, config: wdt::Config) -> Self { |
| 141 | let mut config = wdt::Config::default(); | ||
| 142 | config.timeout_ticks = 32768 * timeout; // timeout seconds | ||
| 143 | config.run_during_sleep = true; | ||
| 144 | config.run_during_debug_halt = false; | ||
| 145 | let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { | 116 | let (_wdt, [wdt]) = match wdt::Watchdog::try_new(wdt, config) { |
| 146 | Ok(x) => x, | 117 | Ok(x) => x, |
| 147 | Err(_) => { | 118 | Err(_) => { |
diff --git a/embassy-boot/rp/Cargo.toml b/embassy-boot/rp/Cargo.toml new file mode 100644 index 000000000..5147392ce --- /dev/null +++ b/embassy-boot/rp/Cargo.toml | |||
| @@ -0,0 +1,79 @@ | |||
| 1 | [package] | ||
| 2 | edition = "2021" | ||
| 3 | name = "embassy-boot-rp" | ||
| 4 | version = "0.1.0" | ||
| 5 | description = "Bootloader lib for RP2040 chips" | ||
| 6 | license = "MIT OR Apache-2.0" | ||
| 7 | |||
| 8 | [package.metadata.embassy_docs] | ||
| 9 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-rp-v$VERSION/src/" | ||
| 10 | src_base_git = "https://github.com/embassy-rs/embassy/blob/$COMMIT/embassy-boot/rp/src/" | ||
| 11 | target = "thumbv6m-none-eabi" | ||
| 12 | |||
| 13 | [lib] | ||
| 14 | |||
| 15 | [dependencies] | ||
| 16 | defmt = { version = "0.3", optional = true } | ||
| 17 | defmt-rtt = { version = "0.4", optional = true } | ||
| 18 | log = { version = "0.4", optional = true } | ||
| 19 | |||
| 20 | embassy-sync = { path = "../../embassy-sync" } | ||
| 21 | embassy-rp = { path = "../../embassy-rp", default-features = false } | ||
| 22 | embassy-boot = { path = "../boot", default-features = false } | ||
| 23 | embassy-time = { path = "../../embassy-time" } | ||
| 24 | |||
| 25 | cortex-m = { version = "0.7.6" } | ||
| 26 | cortex-m-rt = { version = "0.7" } | ||
| 27 | embedded-storage = "0.3.0" | ||
| 28 | embedded-storage-async = { version = "0.4.0", optional = true } | ||
| 29 | cfg-if = "1.0.0" | ||
| 30 | |||
| 31 | [features] | ||
| 32 | defmt = [ | ||
| 33 | "dep:defmt", | ||
| 34 | "embassy-boot/defmt", | ||
| 35 | "embassy-rp/defmt", | ||
| 36 | ] | ||
| 37 | log = [ | ||
| 38 | "dep:log", | ||
| 39 | "embassy-boot/log", | ||
| 40 | "embassy-rp/log", | ||
| 41 | ] | ||
| 42 | debug = ["defmt-rtt"] | ||
| 43 | nightly = [ | ||
| 44 | "dep:embedded-storage-async", | ||
| 45 | "embassy-boot/nightly", | ||
| 46 | "embassy-rp/nightly", | ||
| 47 | "embassy-time/nightly" | ||
| 48 | ] | ||
| 49 | |||
| 50 | [profile.dev] | ||
| 51 | debug = 2 | ||
| 52 | debug-assertions = true | ||
| 53 | incremental = false | ||
| 54 | opt-level = 'z' | ||
| 55 | overflow-checks = true | ||
| 56 | |||
| 57 | [profile.release] | ||
| 58 | codegen-units = 1 | ||
| 59 | debug = 2 | ||
| 60 | debug-assertions = false | ||
| 61 | incremental = false | ||
| 62 | lto = 'fat' | ||
| 63 | opt-level = 'z' | ||
| 64 | overflow-checks = false | ||
| 65 | |||
| 66 | # do not optimize proc-macro crates = faster builds from scratch | ||
| 67 | [profile.dev.build-override] | ||
| 68 | codegen-units = 8 | ||
| 69 | debug = false | ||
| 70 | debug-assertions = false | ||
| 71 | opt-level = 0 | ||
| 72 | overflow-checks = false | ||
| 73 | |||
| 74 | [profile.release.build-override] | ||
| 75 | codegen-units = 8 | ||
| 76 | debug = false | ||
| 77 | debug-assertions = false | ||
| 78 | opt-level = 0 | ||
| 79 | overflow-checks = false | ||
diff --git a/embassy-boot/rp/README.md b/embassy-boot/rp/README.md new file mode 100644 index 000000000..315d655e3 --- /dev/null +++ b/embassy-boot/rp/README.md | |||
| @@ -0,0 +1,26 @@ | |||
| 1 | # embassy-boot-rp | ||
| 2 | |||
| 3 | An [Embassy](https://embassy.dev) project. | ||
| 4 | |||
| 5 | An adaptation of `embassy-boot` for RP2040. | ||
| 6 | |||
| 7 | NOTE: The applications using this bootloader should not link with the `link-rp.x` linker script. | ||
| 8 | |||
| 9 | ## Features | ||
| 10 | |||
| 11 | * Configure bootloader partitions based on linker script. | ||
| 12 | * Load applications from active partition. | ||
| 13 | |||
| 14 | ## Minimum supported Rust version (MSRV) | ||
| 15 | |||
| 16 | `embassy-boot-rp` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 17 | |||
| 18 | ## License | ||
| 19 | |||
| 20 | This work is licensed under either of | ||
| 21 | |||
| 22 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 23 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 24 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 25 | |||
| 26 | at your option. | ||
diff --git a/embassy-boot/rp/build.rs b/embassy-boot/rp/build.rs new file mode 100644 index 000000000..2cbc7ef5e --- /dev/null +++ b/embassy-boot/rp/build.rs | |||
| @@ -0,0 +1,8 @@ | |||
| 1 | use std::env; | ||
| 2 | |||
| 3 | fn main() { | ||
| 4 | let target = env::var("TARGET").unwrap(); | ||
| 5 | if target.starts_with("thumbv6m-") { | ||
| 6 | println!("cargo:rustc-cfg=armv6m"); | ||
| 7 | } | ||
| 8 | } | ||
diff --git a/embassy-boot/rp/src/fmt.rs b/embassy-boot/rp/src/fmt.rs new file mode 100644 index 000000000..066970813 --- /dev/null +++ b/embassy-boot/rp/src/fmt.rs | |||
| @@ -0,0 +1,225 @@ | |||
| 1 | #![macro_use] | ||
| 2 | #![allow(unused_macros)] | ||
| 3 | |||
| 4 | #[cfg(all(feature = "defmt", feature = "log"))] | ||
| 5 | compile_error!("You may not enable both `defmt` and `log` features."); | ||
| 6 | |||
| 7 | macro_rules! assert { | ||
| 8 | ($($x:tt)*) => { | ||
| 9 | { | ||
| 10 | #[cfg(not(feature = "defmt"))] | ||
| 11 | ::core::assert!($($x)*); | ||
| 12 | #[cfg(feature = "defmt")] | ||
| 13 | ::defmt::assert!($($x)*); | ||
| 14 | } | ||
| 15 | }; | ||
| 16 | } | ||
| 17 | |||
| 18 | macro_rules! assert_eq { | ||
| 19 | ($($x:tt)*) => { | ||
| 20 | { | ||
| 21 | #[cfg(not(feature = "defmt"))] | ||
| 22 | ::core::assert_eq!($($x)*); | ||
| 23 | #[cfg(feature = "defmt")] | ||
| 24 | ::defmt::assert_eq!($($x)*); | ||
| 25 | } | ||
| 26 | }; | ||
| 27 | } | ||
| 28 | |||
| 29 | macro_rules! assert_ne { | ||
| 30 | ($($x:tt)*) => { | ||
| 31 | { | ||
| 32 | #[cfg(not(feature = "defmt"))] | ||
| 33 | ::core::assert_ne!($($x)*); | ||
| 34 | #[cfg(feature = "defmt")] | ||
| 35 | ::defmt::assert_ne!($($x)*); | ||
| 36 | } | ||
| 37 | }; | ||
| 38 | } | ||
| 39 | |||
| 40 | macro_rules! debug_assert { | ||
| 41 | ($($x:tt)*) => { | ||
| 42 | { | ||
| 43 | #[cfg(not(feature = "defmt"))] | ||
| 44 | ::core::debug_assert!($($x)*); | ||
| 45 | #[cfg(feature = "defmt")] | ||
| 46 | ::defmt::debug_assert!($($x)*); | ||
| 47 | } | ||
| 48 | }; | ||
| 49 | } | ||
| 50 | |||
| 51 | macro_rules! debug_assert_eq { | ||
| 52 | ($($x:tt)*) => { | ||
| 53 | { | ||
| 54 | #[cfg(not(feature = "defmt"))] | ||
| 55 | ::core::debug_assert_eq!($($x)*); | ||
| 56 | #[cfg(feature = "defmt")] | ||
| 57 | ::defmt::debug_assert_eq!($($x)*); | ||
| 58 | } | ||
| 59 | }; | ||
| 60 | } | ||
| 61 | |||
| 62 | macro_rules! debug_assert_ne { | ||
| 63 | ($($x:tt)*) => { | ||
| 64 | { | ||
| 65 | #[cfg(not(feature = "defmt"))] | ||
| 66 | ::core::debug_assert_ne!($($x)*); | ||
| 67 | #[cfg(feature = "defmt")] | ||
| 68 | ::defmt::debug_assert_ne!($($x)*); | ||
| 69 | } | ||
| 70 | }; | ||
| 71 | } | ||
| 72 | |||
| 73 | macro_rules! todo { | ||
| 74 | ($($x:tt)*) => { | ||
| 75 | { | ||
| 76 | #[cfg(not(feature = "defmt"))] | ||
| 77 | ::core::todo!($($x)*); | ||
| 78 | #[cfg(feature = "defmt")] | ||
| 79 | ::defmt::todo!($($x)*); | ||
| 80 | } | ||
| 81 | }; | ||
| 82 | } | ||
| 83 | |||
| 84 | macro_rules! unreachable { | ||
| 85 | ($($x:tt)*) => { | ||
| 86 | { | ||
| 87 | #[cfg(not(feature = "defmt"))] | ||
| 88 | ::core::unreachable!($($x)*); | ||
| 89 | #[cfg(feature = "defmt")] | ||
| 90 | ::defmt::unreachable!($($x)*); | ||
| 91 | } | ||
| 92 | }; | ||
| 93 | } | ||
| 94 | |||
| 95 | macro_rules! panic { | ||
| 96 | ($($x:tt)*) => { | ||
| 97 | { | ||
| 98 | #[cfg(not(feature = "defmt"))] | ||
| 99 | ::core::panic!($($x)*); | ||
| 100 | #[cfg(feature = "defmt")] | ||
| 101 | ::defmt::panic!($($x)*); | ||
| 102 | } | ||
| 103 | }; | ||
| 104 | } | ||
| 105 | |||
| 106 | macro_rules! trace { | ||
| 107 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 108 | { | ||
| 109 | #[cfg(feature = "log")] | ||
| 110 | ::log::trace!($s $(, $x)*); | ||
| 111 | #[cfg(feature = "defmt")] | ||
| 112 | ::defmt::trace!($s $(, $x)*); | ||
| 113 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 114 | let _ = ($( & $x ),*); | ||
| 115 | } | ||
| 116 | }; | ||
| 117 | } | ||
| 118 | |||
| 119 | macro_rules! debug { | ||
| 120 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 121 | { | ||
| 122 | #[cfg(feature = "log")] | ||
| 123 | ::log::debug!($s $(, $x)*); | ||
| 124 | #[cfg(feature = "defmt")] | ||
| 125 | ::defmt::debug!($s $(, $x)*); | ||
| 126 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 127 | let _ = ($( & $x ),*); | ||
| 128 | } | ||
| 129 | }; | ||
| 130 | } | ||
| 131 | |||
| 132 | macro_rules! info { | ||
| 133 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 134 | { | ||
| 135 | #[cfg(feature = "log")] | ||
| 136 | ::log::info!($s $(, $x)*); | ||
| 137 | #[cfg(feature = "defmt")] | ||
| 138 | ::defmt::info!($s $(, $x)*); | ||
| 139 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 140 | let _ = ($( & $x ),*); | ||
| 141 | } | ||
| 142 | }; | ||
| 143 | } | ||
| 144 | |||
| 145 | macro_rules! warn { | ||
| 146 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 147 | { | ||
| 148 | #[cfg(feature = "log")] | ||
| 149 | ::log::warn!($s $(, $x)*); | ||
| 150 | #[cfg(feature = "defmt")] | ||
| 151 | ::defmt::warn!($s $(, $x)*); | ||
| 152 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 153 | let _ = ($( & $x ),*); | ||
| 154 | } | ||
| 155 | }; | ||
| 156 | } | ||
| 157 | |||
| 158 | macro_rules! error { | ||
| 159 | ($s:literal $(, $x:expr)* $(,)?) => { | ||
| 160 | { | ||
| 161 | #[cfg(feature = "log")] | ||
| 162 | ::log::error!($s $(, $x)*); | ||
| 163 | #[cfg(feature = "defmt")] | ||
| 164 | ::defmt::error!($s $(, $x)*); | ||
| 165 | #[cfg(not(any(feature = "log", feature="defmt")))] | ||
| 166 | let _ = ($( & $x ),*); | ||
| 167 | } | ||
| 168 | }; | ||
| 169 | } | ||
| 170 | |||
| 171 | #[cfg(feature = "defmt")] | ||
| 172 | macro_rules! unwrap { | ||
| 173 | ($($x:tt)*) => { | ||
| 174 | ::defmt::unwrap!($($x)*) | ||
| 175 | }; | ||
| 176 | } | ||
| 177 | |||
| 178 | #[cfg(not(feature = "defmt"))] | ||
| 179 | macro_rules! unwrap { | ||
| 180 | ($arg:expr) => { | ||
| 181 | match $crate::fmt::Try::into_result($arg) { | ||
| 182 | ::core::result::Result::Ok(t) => t, | ||
| 183 | ::core::result::Result::Err(e) => { | ||
| 184 | ::core::panic!("unwrap of `{}` failed: {:?}", ::core::stringify!($arg), e); | ||
| 185 | } | ||
| 186 | } | ||
| 187 | }; | ||
| 188 | ($arg:expr, $($msg:expr),+ $(,)? ) => { | ||
| 189 | match $crate::fmt::Try::into_result($arg) { | ||
| 190 | ::core::result::Result::Ok(t) => t, | ||
| 191 | ::core::result::Result::Err(e) => { | ||
| 192 | ::core::panic!("unwrap of `{}` failed: {}: {:?}", ::core::stringify!($arg), ::core::format_args!($($msg,)*), e); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | } | ||
| 196 | } | ||
| 197 | |||
| 198 | #[derive(Debug, Copy, Clone, Eq, PartialEq)] | ||
| 199 | pub struct NoneError; | ||
| 200 | |||
| 201 | pub trait Try { | ||
| 202 | type Ok; | ||
| 203 | type Error; | ||
| 204 | fn into_result(self) -> Result<Self::Ok, Self::Error>; | ||
| 205 | } | ||
| 206 | |||
| 207 | impl<T> Try for Option<T> { | ||
| 208 | type Ok = T; | ||
| 209 | type Error = NoneError; | ||
| 210 | |||
| 211 | #[inline] | ||
| 212 | fn into_result(self) -> Result<T, NoneError> { | ||
| 213 | self.ok_or(NoneError) | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | impl<T, E> Try for Result<T, E> { | ||
| 218 | type Ok = T; | ||
| 219 | type Error = E; | ||
| 220 | |||
| 221 | #[inline] | ||
| 222 | fn into_result(self) -> Self { | ||
| 223 | self | ||
| 224 | } | ||
| 225 | } | ||
diff --git a/embassy-boot/rp/src/lib.rs b/embassy-boot/rp/src/lib.rs new file mode 100644 index 000000000..25329f9e9 --- /dev/null +++ b/embassy-boot/rp/src/lib.rs | |||
| @@ -0,0 +1,102 @@ | |||
| 1 | #![no_std] | ||
| 2 | #![warn(missing_docs)] | ||
| 3 | #![doc = include_str!("../README.md")] | ||
| 4 | mod fmt; | ||
| 5 | |||
| 6 | #[cfg(feature = "nightly")] | ||
| 7 | pub use embassy_boot::FirmwareUpdater; | ||
| 8 | pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; | ||
| 9 | use embassy_rp::flash::{Flash, ERASE_SIZE}; | ||
| 10 | use embassy_rp::peripherals::{FLASH, WATCHDOG}; | ||
| 11 | use embassy_rp::watchdog::Watchdog; | ||
| 12 | use embassy_time::Duration; | ||
| 13 | use embedded_storage::nor_flash::{ErrorType, NorFlash, ReadNorFlash}; | ||
| 14 | |||
| 15 | /// A bootloader for RP2040 devices. | ||
| 16 | pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize = ERASE_SIZE> { | ||
| 17 | boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>, | ||
| 18 | aligned_buf: AlignedBuffer<BUFFER_SIZE>, | ||
| 19 | } | ||
| 20 | |||
| 21 | impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> | ||
| 22 | BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE> | ||
| 23 | { | ||
| 24 | /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | ||
| 25 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { | ||
| 26 | Self { | ||
| 27 | boot: embassy_boot::BootLoader::new(config), | ||
| 28 | aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), | ||
| 29 | } | ||
| 30 | } | ||
| 31 | |||
| 32 | /// Inspect the bootloader state and perform actions required before booting, such as swapping | ||
| 33 | /// firmware. | ||
| 34 | pub fn prepare(&mut self) { | ||
| 35 | self.boot | ||
| 36 | .prepare_boot(self.aligned_buf.as_mut()) | ||
| 37 | .expect("Boot prepare error"); | ||
| 38 | } | ||
| 39 | |||
| 40 | /// Boots the application. | ||
| 41 | /// | ||
| 42 | /// # Safety | ||
| 43 | /// | ||
| 44 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 45 | pub unsafe fn load(self, start: u32) -> ! { | ||
| 46 | core::mem::drop(self.boot); | ||
| 47 | |||
| 48 | trace!("Loading app at 0x{:x}", start); | ||
| 49 | #[allow(unused_mut)] | ||
| 50 | let mut p = cortex_m::Peripherals::steal(); | ||
| 51 | #[cfg(not(armv6m))] | ||
| 52 | p.SCB.invalidate_icache(); | ||
| 53 | p.SCB.vtor.write(start); | ||
| 54 | |||
| 55 | cortex_m::asm::bootload(start as *const u32) | ||
| 56 | } | ||
| 57 | } | ||
| 58 | |||
| 59 | /// A flash implementation that will feed a watchdog when touching flash. | ||
| 60 | pub struct WatchdogFlash<'d, const SIZE: usize> { | ||
| 61 | flash: Flash<'d, FLASH, SIZE>, | ||
| 62 | watchdog: Watchdog, | ||
| 63 | } | ||
| 64 | |||
| 65 | impl<'d, const SIZE: usize> WatchdogFlash<'d, SIZE> { | ||
| 66 | /// Start a new watchdog with a given flash and watchdog peripheral and a timeout | ||
| 67 | pub fn start(flash: FLASH, watchdog: WATCHDOG, timeout: Duration) -> Self { | ||
| 68 | let flash: Flash<'_, FLASH, SIZE> = Flash::new(flash); | ||
| 69 | let mut watchdog = Watchdog::new(watchdog); | ||
| 70 | watchdog.start(timeout); | ||
| 71 | Self { flash, watchdog } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | impl<'d, const SIZE: usize> ErrorType for WatchdogFlash<'d, SIZE> { | ||
| 76 | type Error = <Flash<'d, FLASH, SIZE> as ErrorType>::Error; | ||
| 77 | } | ||
| 78 | |||
| 79 | impl<'d, const SIZE: usize> NorFlash for WatchdogFlash<'d, SIZE> { | ||
| 80 | const WRITE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::WRITE_SIZE; | ||
| 81 | const ERASE_SIZE: usize = <Flash<'d, FLASH, SIZE> as NorFlash>::ERASE_SIZE; | ||
| 82 | |||
| 83 | fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> { | ||
| 84 | self.watchdog.feed(); | ||
| 85 | self.flash.erase(from, to) | ||
| 86 | } | ||
| 87 | fn write(&mut self, offset: u32, data: &[u8]) -> Result<(), Self::Error> { | ||
| 88 | self.watchdog.feed(); | ||
| 89 | self.flash.write(offset, data) | ||
| 90 | } | ||
| 91 | } | ||
| 92 | |||
| 93 | impl<'d, const SIZE: usize> ReadNorFlash for WatchdogFlash<'d, SIZE> { | ||
| 94 | const READ_SIZE: usize = <Flash<'d, FLASH, SIZE> as ReadNorFlash>::READ_SIZE; | ||
| 95 | fn read(&mut self, offset: u32, data: &mut [u8]) -> Result<(), Self::Error> { | ||
| 96 | self.watchdog.feed(); | ||
| 97 | self.flash.read(offset, data) | ||
| 98 | } | ||
| 99 | fn capacity(&self) -> usize { | ||
| 100 | self.flash.capacity() | ||
| 101 | } | ||
| 102 | } | ||
diff --git a/embassy-boot/stm32/Cargo.toml b/embassy-boot/stm32/Cargo.toml index ad4657e0d..99a6b8e0e 100644 --- a/embassy-boot/stm32/Cargo.toml +++ b/embassy-boot/stm32/Cargo.toml | |||
| @@ -3,6 +3,7 @@ edition = "2021" | |||
| 3 | name = "embassy-boot-stm32" | 3 | name = "embassy-boot-stm32" |
| 4 | version = "0.1.0" | 4 | version = "0.1.0" |
| 5 | description = "Bootloader lib for STM32 chips" | 5 | description = "Bootloader lib for STM32 chips" |
| 6 | license = "MIT OR Apache-2.0" | ||
| 6 | 7 | ||
| 7 | [package.metadata.embassy_docs] | 8 | [package.metadata.embassy_docs] |
| 8 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" | 9 | src_base = "https://github.com/embassy-rs/embassy/blob/embassy-boot-nrf-v$VERSION/embassy-boot/stm32/src/" |
| @@ -14,16 +15,16 @@ target = "thumbv7em-none-eabi" | |||
| 14 | 15 | ||
| 15 | [dependencies] | 16 | [dependencies] |
| 16 | defmt = { version = "0.3", optional = true } | 17 | defmt = { version = "0.3", optional = true } |
| 17 | defmt-rtt = { version = "0.3", optional = true } | 18 | defmt-rtt = { version = "0.4", optional = true } |
| 18 | log = { version = "0.4", optional = true } | 19 | log = { version = "0.4", optional = true } |
| 19 | 20 | ||
| 20 | embassy-sync = { path = "../../embassy-sync" } | 21 | embassy-sync = { path = "../../embassy-sync" } |
| 21 | embassy-stm32 = { path = "../../embassy-stm32", default-features = false, features = ["nightly"] } | 22 | embassy-stm32 = { path = "../../embassy-stm32", default-features = false } |
| 22 | embassy-boot = { path = "../boot", default-features = false } | 23 | embassy-boot = { path = "../boot", default-features = false } |
| 23 | cortex-m = { version = "0.7.6" } | 24 | cortex-m = { version = "0.7.6" } |
| 24 | cortex-m-rt = { version = "0.7" } | 25 | cortex-m-rt = { version = "0.7" } |
| 25 | embedded-storage = "0.3.0" | 26 | embedded-storage = "0.3.0" |
| 26 | embedded-storage-async = "0.3.0" | 27 | embedded-storage-async = { version = "0.4.0", optional = true } |
| 27 | cfg-if = "1.0.0" | 28 | cfg-if = "1.0.0" |
| 28 | 29 | ||
| 29 | [features] | 30 | [features] |
| @@ -38,6 +39,11 @@ log = [ | |||
| 38 | "embassy-stm32/log", | 39 | "embassy-stm32/log", |
| 39 | ] | 40 | ] |
| 40 | debug = ["defmt-rtt"] | 41 | debug = ["defmt-rtt"] |
| 42 | nightly = [ | ||
| 43 | "dep:embedded-storage-async", | ||
| 44 | "embassy-boot/nightly", | ||
| 45 | "embassy-stm32/nightly" | ||
| 46 | ] | ||
| 41 | 47 | ||
| 42 | [profile.dev] | 48 | [profile.dev] |
| 43 | debug = 2 | 49 | debug = 2 |
diff --git a/embassy-boot/stm32/README.md b/embassy-boot/stm32/README.md index a82b730b9..b4d7ba5a4 100644 --- a/embassy-boot/stm32/README.md +++ b/embassy-boot/stm32/README.md | |||
| @@ -1,11 +1,24 @@ | |||
| 1 | # Bootloader for STM32 | 1 | # embassy-boot-stm32 |
| 2 | 2 | ||
| 3 | The bootloader uses `embassy-boot` to interact with the flash. | 3 | An [Embassy](https://embassy.dev) project. |
| 4 | 4 | ||
| 5 | # Usage | 5 | An adaptation of `embassy-boot` for STM32. |
| 6 | 6 | ||
| 7 | Flash the bootloader | 7 | ## Features |
| 8 | 8 | ||
| 9 | ``` | 9 | * Configure bootloader partitions based on linker script. |
| 10 | cargo flash --features embassy-stm32/stm32wl55jc-cm4 --release --chip STM32WLE5JCIx | 10 | * Load applications from active partition. |
| 11 | ``` | 11 | |
| 12 | ## Minimum supported Rust version (MSRV) | ||
| 13 | |||
| 14 | `embassy-boot-stm32` is guaranteed to compile on the latest stable Rust version at the time of release. It might compile with older versions but that may change in any new patch release. | ||
| 15 | |||
| 16 | ## License | ||
| 17 | |||
| 18 | This work is licensed under either of | ||
| 19 | |||
| 20 | - Apache License, Version 2.0 ([LICENSE-APACHE](LICENSE-APACHE) or | ||
| 21 | <http://www.apache.org/licenses/LICENSE-2.0>) | ||
| 22 | - MIT license ([LICENSE-MIT](LICENSE-MIT) or <http://opensource.org/licenses/MIT>) | ||
| 23 | |||
| 24 | at your option. | ||
diff --git a/embassy-boot/stm32/src/lib.rs b/embassy-boot/stm32/src/lib.rs index 5a4f2d058..069de0d1a 100644 --- a/embassy-boot/stm32/src/lib.rs +++ b/embassy-boot/stm32/src/lib.rs | |||
| @@ -1,82 +1,52 @@ | |||
| 1 | #![no_std] | 1 | #![no_std] |
| 2 | #![feature(generic_associated_types)] | 2 | #![warn(missing_docs)] |
| 3 | #![feature(type_alias_impl_trait)] | 3 | #![doc = include_str!("../README.md")] |
| 4 | #![allow(incomplete_features)] | ||
| 5 | #![feature(generic_const_exprs)] | ||
| 6 | |||
| 7 | mod fmt; | 4 | mod fmt; |
| 8 | 5 | ||
| 9 | pub use embassy_boot::{FirmwareUpdater, FlashConfig, FlashProvider, Partition, SingleFlashProvider, State}; | 6 | #[cfg(feature = "nightly")] |
| 7 | pub use embassy_boot::FirmwareUpdater; | ||
| 8 | pub use embassy_boot::{AlignedBuffer, BlockingFirmwareUpdater, BootLoaderConfig, FirmwareUpdaterConfig, State}; | ||
| 10 | use embedded_storage::nor_flash::NorFlash; | 9 | use embedded_storage::nor_flash::NorFlash; |
| 11 | 10 | ||
| 12 | pub struct BootLoader<const PAGE_SIZE: usize> { | 11 | /// A bootloader for STM32 devices. |
| 13 | boot: embassy_boot::BootLoader<PAGE_SIZE>, | 12 | pub struct BootLoader<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> { |
| 13 | boot: embassy_boot::BootLoader<ACTIVE, DFU, STATE>, | ||
| 14 | aligned_buf: AlignedBuffer<BUFFER_SIZE>, | ||
| 14 | } | 15 | } |
| 15 | 16 | ||
| 16 | impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { | 17 | impl<ACTIVE: NorFlash, DFU: NorFlash, STATE: NorFlash, const BUFFER_SIZE: usize> |
| 17 | /// Create a new bootloader instance using parameters from linker script | 18 | BootLoader<ACTIVE, DFU, STATE, BUFFER_SIZE> |
| 18 | pub fn default() -> Self { | 19 | { |
| 19 | extern "C" { | ||
| 20 | static __bootloader_state_start: u32; | ||
| 21 | static __bootloader_state_end: u32; | ||
| 22 | static __bootloader_active_start: u32; | ||
| 23 | static __bootloader_active_end: u32; | ||
| 24 | static __bootloader_dfu_start: u32; | ||
| 25 | static __bootloader_dfu_end: u32; | ||
| 26 | } | ||
| 27 | |||
| 28 | let active = unsafe { | ||
| 29 | Partition::new( | ||
| 30 | &__bootloader_active_start as *const u32 as usize, | ||
| 31 | &__bootloader_active_end as *const u32 as usize, | ||
| 32 | ) | ||
| 33 | }; | ||
| 34 | let dfu = unsafe { | ||
| 35 | Partition::new( | ||
| 36 | &__bootloader_dfu_start as *const u32 as usize, | ||
| 37 | &__bootloader_dfu_end as *const u32 as usize, | ||
| 38 | ) | ||
| 39 | }; | ||
| 40 | let state = unsafe { | ||
| 41 | Partition::new( | ||
| 42 | &__bootloader_state_start as *const u32 as usize, | ||
| 43 | &__bootloader_state_end as *const u32 as usize, | ||
| 44 | ) | ||
| 45 | }; | ||
| 46 | |||
| 47 | trace!("ACTIVE: 0x{:x} - 0x{:x}", active.from, active.to); | ||
| 48 | trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||
| 49 | trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||
| 50 | |||
| 51 | Self::new(active, dfu, state) | ||
| 52 | } | ||
| 53 | |||
| 54 | /// Create a new bootloader instance using the supplied partitions for active, dfu and state. | 20 | /// Create a new bootloader instance using the supplied partitions for active, dfu and state. |
| 55 | pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { | 21 | pub fn new(config: BootLoaderConfig<ACTIVE, DFU, STATE>) -> Self { |
| 56 | Self { | 22 | Self { |
| 57 | boot: embassy_boot::BootLoader::new(active, dfu, state), | 23 | boot: embassy_boot::BootLoader::new(config), |
| 24 | aligned_buf: AlignedBuffer([0; BUFFER_SIZE]), | ||
| 58 | } | 25 | } |
| 59 | } | 26 | } |
| 60 | 27 | ||
| 61 | /// Boots the application | 28 | /// Inspect the bootloader state and perform actions required before booting, such as swapping |
| 62 | pub fn prepare<F: FlashProvider>(&mut self, flash: &mut F) -> usize | 29 | /// firmware. |
| 63 | where | 30 | pub fn prepare(&mut self) { |
| 64 | [(); <<F as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, | 31 | self.boot |
| 65 | [(); <<F as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, | 32 | .prepare_boot(self.aligned_buf.as_mut()) |
| 66 | { | 33 | .expect("Boot prepare error"); |
| 67 | match self.boot.prepare_boot(flash) { | ||
| 68 | Ok(_) => embassy_stm32::flash::FLASH_BASE + self.boot.boot_address(), | ||
| 69 | Err(_) => panic!("boot prepare error!"), | ||
| 70 | } | ||
| 71 | } | 34 | } |
| 72 | 35 | ||
| 73 | pub unsafe fn load(&mut self, start: usize) -> ! { | 36 | /// Boots the application. |
| 37 | /// | ||
| 38 | /// # Safety | ||
| 39 | /// | ||
| 40 | /// This modifies the stack pointer and reset vector and will run code placed in the active partition. | ||
| 41 | pub unsafe fn load(self, start: u32) -> ! { | ||
| 42 | core::mem::drop(self.boot); | ||
| 43 | |||
| 74 | trace!("Loading app at 0x{:x}", start); | 44 | trace!("Loading app at 0x{:x}", start); |
| 75 | #[allow(unused_mut)] | 45 | #[allow(unused_mut)] |
| 76 | let mut p = cortex_m::Peripherals::steal(); | 46 | let mut p = cortex_m::Peripherals::steal(); |
| 77 | #[cfg(not(armv6m))] | 47 | #[cfg(not(armv6m))] |
| 78 | p.SCB.invalidate_icache(); | 48 | p.SCB.invalidate_icache(); |
| 79 | p.SCB.vtor.write(start as u32); | 49 | p.SCB.vtor.write(start); |
| 80 | 50 | ||
| 81 | cortex_m::asm::bootload(start as *const u32) | 51 | cortex_m::asm::bootload(start as *const u32) |
| 82 | } | 52 | } |
