diff options
| -rw-r--r-- | embassy-boot/boot/src/firmware_updater/asynch.rs | 176 | ||||
| -rw-r--r-- | embassy-boot/boot/src/firmware_updater/blocking.rs | 187 | ||||
| -rw-r--r-- | embassy-boot/boot/src/firmware_updater/mod.rs | 51 | ||||
| -rw-r--r-- | embassy-boot/boot/src/lib.rs | 4 |
4 files changed, 236 insertions, 182 deletions
diff --git a/embassy-boot/boot/src/firmware_updater/asynch.rs b/embassy-boot/boot/src/firmware_updater/asynch.rs index bdd03bff4..d0780bdf1 100644 --- a/embassy-boot/boot/src/firmware_updater/asynch.rs +++ b/embassy-boot/boot/src/firmware_updater/asynch.rs | |||
| @@ -1,20 +1,68 @@ | |||
| 1 | use digest::Digest; | 1 | use digest::Digest; |
| 2 | use embedded_storage_async::nor_flash::NorFlash as AsyncNorFlash; | 2 | use embassy_embedded_hal::flash::partition::Partition; |
| 3 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 4 | use embedded_storage_async::nor_flash::NorFlash; | ||
| 5 | |||
| 6 | use super::FirmwareUpdaterConfig; | ||
| 7 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 8 | |||
| 9 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 10 | /// 'mess up' the internal bootloader state | ||
| 11 | pub struct FirmwareUpdater<DFU: NorFlash, STATE: NorFlash> { | ||
| 12 | dfu: DFU, | ||
| 13 | state: STATE, | ||
| 14 | } | ||
| 15 | |||
| 16 | impl<'a, FLASH: NorFlash> | ||
| 17 | FirmwareUpdaterConfig<Partition<'a, NoopRawMutex, FLASH>, Partition<'a, NoopRawMutex, FLASH>> | ||
| 18 | { | ||
| 19 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 20 | #[cfg(target_os = "none")] | ||
| 21 | pub fn from_linkerfile(flash: &'a Mutex<NoopRawMutex, FLASH>) -> Self { | ||
| 22 | use embassy_sync::mutex::Mutex; | ||
| 23 | |||
| 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 | }; | ||
| 3 | 45 | ||
| 4 | use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; | 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 | } | ||
| 5 | 58 | ||
| 6 | impl FirmwareUpdater { | ||
| 7 | /// Obtain the current state. | 59 | /// Obtain the current state. |
| 8 | /// | 60 | /// |
| 9 | /// This is useful to check if the bootloader has just done a swap, in order | 61 | /// This is useful to check if the bootloader has just done a swap, in order |
| 10 | /// to do verifications and self-tests of the new image before calling | 62 | /// to do verifications and self-tests of the new image before calling |
| 11 | /// `mark_booted`. | 63 | /// `mark_booted`. |
| 12 | pub async fn get_state<F: AsyncNorFlash>( | 64 | pub async fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> { |
| 13 | &mut self, | 65 | self.state.read(0, aligned).await?; |
| 14 | state_flash: &mut F, | ||
| 15 | aligned: &mut [u8], | ||
| 16 | ) -> Result<State, FirmwareUpdaterError> { | ||
| 17 | self.state.read(state_flash, 0, aligned).await?; | ||
| 18 | 66 | ||
| 19 | if !aligned.iter().any(|&b| b != SWAP_MAGIC) { | 67 | if !aligned.iter().any(|&b| b != SWAP_MAGIC) { |
| 20 | Ok(State::Swap) | 68 | Ok(State::Swap) |
| @@ -37,19 +85,18 @@ impl FirmwareUpdater { | |||
| 37 | /// | 85 | /// |
| 38 | /// # Safety | 86 | /// # Safety |
| 39 | /// | 87 | /// |
| 40 | /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from | 88 | /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from |
| 41 | /// and written to. | 89 | /// and written to. |
| 42 | #[cfg(all(feature = "_verify", feature = "nightly"))] | 90 | #[cfg(feature = "_verify")] |
| 43 | pub async fn verify_and_mark_updated<F: AsyncNorFlash>( | 91 | pub async fn verify_and_mark_updated( |
| 44 | &mut self, | 92 | &mut self, |
| 45 | _state_and_dfu_flash: &mut F, | ||
| 46 | _public_key: &[u8], | 93 | _public_key: &[u8], |
| 47 | _signature: &[u8], | 94 | _signature: &[u8], |
| 48 | _update_len: u32, | 95 | _update_len: u32, |
| 49 | _aligned: &mut [u8], | 96 | _aligned: &mut [u8], |
| 50 | ) -> Result<(), FirmwareUpdaterError> { | 97 | ) -> Result<(), FirmwareUpdaterError> { |
| 51 | assert_eq!(_aligned.len(), F::WRITE_SIZE); | 98 | assert_eq!(_aligned.len(), STATE::WRITE_SIZE); |
| 52 | assert!(_update_len <= self.dfu.size()); | 99 | assert!(_update_len <= self.dfu.capacity() as u32); |
| 53 | 100 | ||
| 54 | #[cfg(feature = "ed25519-dalek")] | 101 | #[cfg(feature = "ed25519-dalek")] |
| 55 | { | 102 | { |
| @@ -63,8 +110,7 @@ impl FirmwareUpdater { | |||
| 63 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | 110 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; |
| 64 | 111 | ||
| 65 | let mut message = [0; 64]; | 112 | let mut message = [0; 64]; |
| 66 | self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) | 113 | self.hash::<Sha512>(_update_len, _aligned, &mut message).await?; |
| 67 | .await?; | ||
| 68 | 114 | ||
| 69 | public_key.verify(&message, &signature).map_err(into_signature_error)? | 115 | public_key.verify(&message, &signature).map_err(into_signature_error)? |
| 70 | } | 116 | } |
| @@ -85,8 +131,7 @@ impl FirmwareUpdater { | |||
| 85 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | 131 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; |
| 86 | 132 | ||
| 87 | let mut message = [0; 64]; | 133 | let mut message = [0; 64]; |
| 88 | self.hash::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message) | 134 | self.hash::<Sha512>(_update_len, _aligned, &mut message).await?; |
| 89 | .await?; | ||
| 90 | 135 | ||
| 91 | let r = public_key.verify(&message, &signature); | 136 | let r = public_key.verify(&message, &signature); |
| 92 | trace!( | 137 | trace!( |
| @@ -99,20 +144,19 @@ impl FirmwareUpdater { | |||
| 99 | r.map_err(into_signature_error)? | 144 | r.map_err(into_signature_error)? |
| 100 | } | 145 | } |
| 101 | 146 | ||
| 102 | self.set_magic(_aligned, SWAP_MAGIC, _state_and_dfu_flash).await | 147 | self.set_magic(_aligned, SWAP_MAGIC).await |
| 103 | } | 148 | } |
| 104 | 149 | ||
| 105 | /// Verify the update in DFU with any digest. | 150 | /// Verify the update in DFU with any digest. |
| 106 | pub async fn hash<F: AsyncNorFlash, D: Digest>( | 151 | pub async fn hash<D: Digest>( |
| 107 | &mut self, | 152 | &mut self, |
| 108 | dfu_flash: &mut F, | ||
| 109 | update_len: u32, | 153 | update_len: u32, |
| 110 | chunk_buf: &mut [u8], | 154 | chunk_buf: &mut [u8], |
| 111 | output: &mut [u8], | 155 | output: &mut [u8], |
| 112 | ) -> Result<(), FirmwareUpdaterError> { | 156 | ) -> Result<(), FirmwareUpdaterError> { |
| 113 | let mut digest = D::new(); | 157 | let mut digest = D::new(); |
| 114 | for offset in (0..update_len).step_by(chunk_buf.len()) { | 158 | for offset in (0..update_len).step_by(chunk_buf.len()) { |
| 115 | self.dfu.read(dfu_flash, offset, chunk_buf).await?; | 159 | self.dfu.read(offset, chunk_buf).await?; |
| 116 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | 160 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); |
| 117 | digest.update(&chunk_buf[..len]); | 161 | digest.update(&chunk_buf[..len]); |
| 118 | } | 162 | } |
| @@ -124,60 +168,44 @@ impl FirmwareUpdater { | |||
| 124 | /// | 168 | /// |
| 125 | /// # Safety | 169 | /// # Safety |
| 126 | /// | 170 | /// |
| 127 | /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | 171 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |
| 128 | #[cfg(all(feature = "nightly", not(feature = "_verify")))] | 172 | #[cfg(not(feature = "_verify"))] |
| 129 | pub async fn mark_updated<F: AsyncNorFlash>( | 173 | pub async fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |
| 130 | &mut self, | 174 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); |
| 131 | state_flash: &mut F, | 175 | self.set_magic(aligned, SWAP_MAGIC).await |
| 132 | aligned: &mut [u8], | ||
| 133 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 134 | assert_eq!(aligned.len(), F::WRITE_SIZE); | ||
| 135 | self.set_magic(aligned, SWAP_MAGIC, state_flash).await | ||
| 136 | } | 176 | } |
| 137 | 177 | ||
| 138 | /// Mark firmware boot successful and stop rollback on reset. | 178 | /// Mark firmware boot successful and stop rollback on reset. |
| 139 | /// | 179 | /// |
| 140 | /// # Safety | 180 | /// # Safety |
| 141 | /// | 181 | /// |
| 142 | /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | 182 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |
| 143 | pub async fn mark_booted<F: AsyncNorFlash>( | 183 | pub async fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |
| 144 | &mut self, | 184 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); |
| 145 | state_flash: &mut F, | 185 | self.set_magic(aligned, BOOT_MAGIC).await |
| 146 | aligned: &mut [u8], | ||
| 147 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 148 | assert_eq!(aligned.len(), F::WRITE_SIZE); | ||
| 149 | self.set_magic(aligned, BOOT_MAGIC, state_flash).await | ||
| 150 | } | 186 | } |
| 151 | 187 | ||
| 152 | async fn set_magic<F: AsyncNorFlash>( | 188 | async fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { |
| 153 | &mut self, | 189 | self.state.read(0, aligned).await?; |
| 154 | aligned: &mut [u8], | ||
| 155 | magic: u8, | ||
| 156 | state_flash: &mut F, | ||
| 157 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 158 | self.state.read(state_flash, 0, aligned).await?; | ||
| 159 | 190 | ||
| 160 | if aligned.iter().any(|&b| b != magic) { | 191 | if aligned.iter().any(|&b| b != magic) { |
| 161 | // Read progress validity | 192 | // Read progress validity |
| 162 | self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?; | 193 | self.state.read(STATE::WRITE_SIZE as u32, aligned).await?; |
| 163 | |||
| 164 | // FIXME: Do not make this assumption. | ||
| 165 | const STATE_ERASE_VALUE: u8 = 0xFF; | ||
| 166 | 194 | ||
| 167 | if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | 195 | if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { |
| 168 | // The current progress validity marker is invalid | 196 | // The current progress validity marker is invalid |
| 169 | } else { | 197 | } else { |
| 170 | // Invalidate progress | 198 | // Invalidate progress |
| 171 | aligned.fill(!STATE_ERASE_VALUE); | 199 | aligned.fill(!STATE_ERASE_VALUE); |
| 172 | self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?; | 200 | self.state.write(STATE::WRITE_SIZE as u32, aligned).await?; |
| 173 | } | 201 | } |
| 174 | 202 | ||
| 175 | // Clear magic and progress | 203 | // Clear magic and progress |
| 176 | self.state.wipe(state_flash).await?; | 204 | self.state.erase(0, self.state.capacity() as u32).await?; |
| 177 | 205 | ||
| 178 | // Set magic | 206 | // Set magic |
| 179 | aligned.fill(magic); | 207 | aligned.fill(magic); |
| 180 | self.state.write(state_flash, 0, aligned).await?; | 208 | self.state.write(0, aligned).await?; |
| 181 | } | 209 | } |
| 182 | Ok(()) | 210 | Ok(()) |
| 183 | } | 211 | } |
| @@ -189,19 +217,12 @@ impl FirmwareUpdater { | |||
| 189 | /// # Safety | 217 | /// # Safety |
| 190 | /// | 218 | /// |
| 191 | /// Failing to meet alignment and size requirements may result in a panic. | 219 | /// Failing to meet alignment and size requirements may result in a panic. |
| 192 | pub async fn write_firmware<F: AsyncNorFlash>( | 220 | pub async fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { |
| 193 | &mut self, | 221 | assert!(data.len() >= DFU::ERASE_SIZE); |
| 194 | offset: usize, | ||
| 195 | data: &[u8], | ||
| 196 | dfu_flash: &mut F, | ||
| 197 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 198 | assert!(data.len() >= F::ERASE_SIZE); | ||
| 199 | 222 | ||
| 200 | self.dfu | 223 | self.dfu.erase(offset as u32, (offset + data.len()) as u32).await?; |
| 201 | .erase(dfu_flash, offset as u32, (offset + data.len()) as u32) | ||
| 202 | .await?; | ||
| 203 | 224 | ||
| 204 | self.dfu.write(dfu_flash, offset as u32, data).await?; | 225 | self.dfu.write(offset as u32, data).await?; |
| 205 | 226 | ||
| 206 | Ok(()) | 227 | Ok(()) |
| 207 | } | 228 | } |
| @@ -211,18 +232,18 @@ impl FirmwareUpdater { | |||
| 211 | /// | 232 | /// |
| 212 | /// Using this instead of `write_firmware` allows for an optimized API in | 233 | /// Using this instead of `write_firmware` allows for an optimized API in |
| 213 | /// exchange for added complexity. | 234 | /// exchange for added complexity. |
| 214 | pub async fn prepare_update<F: AsyncNorFlash>( | 235 | pub async fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { |
| 215 | &mut self, | 236 | self.dfu.erase(0, self.dfu.capacity() as u32).await?; |
| 216 | dfu_flash: &mut F, | ||
| 217 | ) -> Result<Partition, FirmwareUpdaterError> { | ||
| 218 | self.dfu.wipe(dfu_flash).await?; | ||
| 219 | 237 | ||
| 220 | Ok(self.dfu) | 238 | Ok(&mut self.dfu) |
| 221 | } | 239 | } |
| 222 | } | 240 | } |
| 223 | 241 | ||
| 224 | #[cfg(test)] | 242 | #[cfg(test)] |
| 225 | mod tests { | 243 | mod tests { |
| 244 | use embassy_embedded_hal::flash::partition::Partition; | ||
| 245 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 246 | use embassy_sync::mutex::Mutex; | ||
| 226 | use futures::executor::block_on; | 247 | use futures::executor::block_on; |
| 227 | use sha1::{Digest, Sha1}; | 248 | use sha1::{Digest, Sha1}; |
| 228 | 249 | ||
| @@ -231,20 +252,19 @@ mod tests { | |||
| 231 | 252 | ||
| 232 | #[test] | 253 | #[test] |
| 233 | fn can_verify_sha1() { | 254 | fn can_verify_sha1() { |
| 234 | const STATE: Partition = Partition::new(0, 4096); | 255 | let flash = Mutex::<NoopRawMutex, _>::new(MemFlash::<131072, 4096, 8>::default()); |
| 235 | const DFU: Partition = Partition::new(65536, 131072); | 256 | let state = Partition::new(&flash, 0, 4096); |
| 236 | 257 | let dfu = Partition::new(&flash, 65536, 65536); | |
| 237 | let mut flash = MemFlash::<131072, 4096, 8>::default(); | ||
| 238 | 258 | ||
| 239 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | 259 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; |
| 240 | let mut to_write = [0; 4096]; | 260 | let mut to_write = [0; 4096]; |
| 241 | to_write[..7].copy_from_slice(update.as_slice()); | 261 | to_write[..7].copy_from_slice(update.as_slice()); |
| 242 | 262 | ||
| 243 | let mut updater = FirmwareUpdater::new(DFU, STATE); | 263 | let mut updater = FirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); |
| 244 | block_on(updater.write_firmware(0, to_write.as_slice(), &mut flash)).unwrap(); | 264 | block_on(updater.write_firmware(0, to_write.as_slice())).unwrap(); |
| 245 | let mut chunk_buf = [0; 2]; | 265 | let mut chunk_buf = [0; 2]; |
| 246 | let mut hash = [0; 20]; | 266 | let mut hash = [0; 20]; |
| 247 | block_on(updater.hash::<_, Sha1>(&mut flash, update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); | 267 | block_on(updater.hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash)).unwrap(); |
| 248 | 268 | ||
| 249 | assert_eq!(Sha1::digest(update).as_slice(), hash); | 269 | assert_eq!(Sha1::digest(update).as_slice(), hash); |
| 250 | } | 270 | } |
diff --git a/embassy-boot/boot/src/firmware_updater/blocking.rs b/embassy-boot/boot/src/firmware_updater/blocking.rs index 50caaf08c..c44126149 100644 --- a/embassy-boot/boot/src/firmware_updater/blocking.rs +++ b/embassy-boot/boot/src/firmware_updater/blocking.rs | |||
| @@ -1,25 +1,70 @@ | |||
| 1 | use digest::Digest; | 1 | use digest::Digest; |
| 2 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 3 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 2 | use embedded_storage::nor_flash::NorFlash; | 4 | use embedded_storage::nor_flash::NorFlash; |
| 3 | 5 | ||
| 4 | use crate::{FirmwareUpdater, FirmwareUpdaterError, Partition, State, BOOT_MAGIC, SWAP_MAGIC}; | 6 | use super::FirmwareUpdaterConfig; |
| 7 | use crate::{FirmwareUpdaterError, State, BOOT_MAGIC, STATE_ERASE_VALUE, SWAP_MAGIC}; | ||
| 8 | |||
| 9 | /// Blocking FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 10 | /// 'mess up' the internal bootloader state | ||
| 11 | pub struct BlockingFirmwareUpdater<DFU: NorFlash, STATE: NorFlash> { | ||
| 12 | dfu: DFU, | ||
| 13 | state: STATE, | ||
| 14 | } | ||
| 15 | |||
| 16 | impl<'a, FLASH: NorFlash> | ||
| 17 | FirmwareUpdaterConfig<BlockingPartition<'a, NoopRawMutex, FLASH>, BlockingPartition<'a, NoopRawMutex, FLASH>> | ||
| 18 | { | ||
| 19 | /// Create a firmware updater config from the flash and address symbols defined in the linkerfile | ||
| 20 | #[cfg(target_os = "none")] | ||
| 21 | pub fn from_linkerfile_blocking(flash: &'a Mutex<NoopRawMutex, RefCell<FLASH>>) -> Self { | ||
| 22 | use core::cell::RefCell; | ||
| 23 | |||
| 24 | use embassy_sync::blocking_mutex::Mutex; | ||
| 25 | |||
| 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 | }; | ||
| 5 | 47 | ||
| 6 | impl FirmwareUpdater { | ||
| 7 | /// Create a firmware updater instance with partition ranges for the update and state partitions. | ||
| 8 | pub const fn new(dfu: Partition, state: Partition) -> Self { | ||
| 9 | Self { dfu, state } | 48 | Self { dfu, state } |
| 10 | } | 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 | } | ||
| 11 | 60 | ||
| 12 | /// Obtain the current state. | 61 | /// Obtain the current state. |
| 13 | /// | 62 | /// |
| 14 | /// This is useful to check if the bootloader has just done a swap, in order | 63 | /// This is useful to check if the bootloader has just done a swap, in order |
| 15 | /// to do verifications and self-tests of the new image before calling | 64 | /// to do verifications and self-tests of the new image before calling |
| 16 | /// `mark_booted`. | 65 | /// `mark_booted`. |
| 17 | pub fn get_state_blocking<F: NorFlash>( | 66 | pub fn get_state(&mut self, aligned: &mut [u8]) -> Result<State, FirmwareUpdaterError> { |
| 18 | &mut self, | 67 | self.state.read(0, aligned)?; |
| 19 | state_flash: &mut F, | ||
| 20 | aligned: &mut [u8], | ||
| 21 | ) -> Result<State, FirmwareUpdaterError> { | ||
| 22 | self.state.read_blocking(state_flash, 0, aligned)?; | ||
| 23 | 68 | ||
| 24 | if !aligned.iter().any(|&b| b != SWAP_MAGIC) { | 69 | if !aligned.iter().any(|&b| b != SWAP_MAGIC) { |
| 25 | Ok(State::Swap) | 70 | Ok(State::Swap) |
| @@ -42,19 +87,18 @@ impl FirmwareUpdater { | |||
| 42 | /// | 87 | /// |
| 43 | /// # Safety | 88 | /// # Safety |
| 44 | /// | 89 | /// |
| 45 | /// The `_aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being read from | 90 | /// The `_aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being read from |
| 46 | /// and written to. | 91 | /// and written to. |
| 47 | #[cfg(feature = "_verify")] | 92 | #[cfg(feature = "_verify")] |
| 48 | pub fn verify_and_mark_updated_blocking<F: NorFlash>( | 93 | pub fn verify_and_mark_updated( |
| 49 | &mut self, | 94 | &mut self, |
| 50 | _state_and_dfu_flash: &mut F, | ||
| 51 | _public_key: &[u8], | 95 | _public_key: &[u8], |
| 52 | _signature: &[u8], | 96 | _signature: &[u8], |
| 53 | _update_len: u32, | 97 | _update_len: u32, |
| 54 | _aligned: &mut [u8], | 98 | _aligned: &mut [u8], |
| 55 | ) -> Result<(), FirmwareUpdaterError> { | 99 | ) -> Result<(), FirmwareUpdaterError> { |
| 56 | assert_eq!(_aligned.len(), F::WRITE_SIZE); | 100 | assert_eq!(_aligned.len(), STATE::WRITE_SIZE); |
| 57 | assert!(_update_len <= self.dfu.size()); | 101 | assert!(_update_len <= self.dfu.capacity() as u32); |
| 58 | 102 | ||
| 59 | #[cfg(feature = "ed25519-dalek")] | 103 | #[cfg(feature = "ed25519-dalek")] |
| 60 | { | 104 | { |
| @@ -68,7 +112,7 @@ impl FirmwareUpdater { | |||
| 68 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; | 112 | let signature = Signature::from_bytes(_signature).map_err(into_signature_error)?; |
| 69 | 113 | ||
| 70 | let mut message = [0; 64]; | 114 | let mut message = [0; 64]; |
| 71 | self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; | 115 | self.hash::<Sha512>(_update_len, _aligned, &mut message)?; |
| 72 | 116 | ||
| 73 | public_key.verify(&message, &signature).map_err(into_signature_error)? | 117 | public_key.verify(&message, &signature).map_err(into_signature_error)? |
| 74 | } | 118 | } |
| @@ -89,7 +133,7 @@ impl FirmwareUpdater { | |||
| 89 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; | 133 | let signature = Signature::try_from(&signature).map_err(into_signature_error)?; |
| 90 | 134 | ||
| 91 | let mut message = [0; 64]; | 135 | let mut message = [0; 64]; |
| 92 | self.hash_blocking::<_, Sha512>(_state_and_dfu_flash, _update_len, _aligned, &mut message)?; | 136 | self.hash::<Sha512>(_update_len, _aligned, &mut message)?; |
| 93 | 137 | ||
| 94 | let r = public_key.verify(&message, &signature); | 138 | let r = public_key.verify(&message, &signature); |
| 95 | trace!( | 139 | trace!( |
| @@ -102,20 +146,19 @@ impl FirmwareUpdater { | |||
| 102 | r.map_err(into_signature_error)? | 146 | r.map_err(into_signature_error)? |
| 103 | } | 147 | } |
| 104 | 148 | ||
| 105 | self.set_magic_blocking(_aligned, SWAP_MAGIC, _state_and_dfu_flash) | 149 | self.set_magic(_aligned, SWAP_MAGIC) |
| 106 | } | 150 | } |
| 107 | 151 | ||
| 108 | /// Verify the update in DFU with any digest. | 152 | /// Verify the update in DFU with any digest. |
| 109 | pub fn hash_blocking<F: NorFlash, D: Digest>( | 153 | pub fn hash<D: Digest>( |
| 110 | &mut self, | 154 | &mut self, |
| 111 | dfu_flash: &mut F, | ||
| 112 | update_len: u32, | 155 | update_len: u32, |
| 113 | chunk_buf: &mut [u8], | 156 | chunk_buf: &mut [u8], |
| 114 | output: &mut [u8], | 157 | output: &mut [u8], |
| 115 | ) -> Result<(), FirmwareUpdaterError> { | 158 | ) -> Result<(), FirmwareUpdaterError> { |
| 116 | let mut digest = D::new(); | 159 | let mut digest = D::new(); |
| 117 | for offset in (0..update_len).step_by(chunk_buf.len()) { | 160 | for offset in (0..update_len).step_by(chunk_buf.len()) { |
| 118 | self.dfu.read_blocking(dfu_flash, offset, chunk_buf)?; | 161 | self.dfu.read(offset, chunk_buf)?; |
| 119 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); | 162 | let len = core::cmp::min((update_len - offset) as usize, chunk_buf.len()); |
| 120 | digest.update(&chunk_buf[..len]); | 163 | digest.update(&chunk_buf[..len]); |
| 121 | } | 164 | } |
| @@ -127,60 +170,44 @@ impl FirmwareUpdater { | |||
| 127 | /// | 170 | /// |
| 128 | /// # Safety | 171 | /// # Safety |
| 129 | /// | 172 | /// |
| 130 | /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | 173 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |
| 131 | #[cfg(not(feature = "_verify"))] | 174 | #[cfg(not(feature = "_verify"))] |
| 132 | pub fn mark_updated_blocking<F: NorFlash>( | 175 | pub fn mark_updated(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |
| 133 | &mut self, | 176 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); |
| 134 | state_flash: &mut F, | 177 | self.set_magic(aligned, SWAP_MAGIC) |
| 135 | aligned: &mut [u8], | ||
| 136 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 137 | assert_eq!(aligned.len(), F::WRITE_SIZE); | ||
| 138 | self.set_magic_blocking(aligned, SWAP_MAGIC, state_flash) | ||
| 139 | } | 178 | } |
| 140 | 179 | ||
| 141 | /// Mark firmware boot successful and stop rollback on reset. | 180 | /// Mark firmware boot successful and stop rollback on reset. |
| 142 | /// | 181 | /// |
| 143 | /// # Safety | 182 | /// # Safety |
| 144 | /// | 183 | /// |
| 145 | /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to. | 184 | /// The `aligned` buffer must have a size of STATE::WRITE_SIZE, and follow the alignment rules for the flash being written to. |
| 146 | pub fn mark_booted_blocking<F: NorFlash>( | 185 | pub fn mark_booted(&mut self, aligned: &mut [u8]) -> Result<(), FirmwareUpdaterError> { |
| 147 | &mut self, | 186 | assert_eq!(aligned.len(), STATE::WRITE_SIZE); |
| 148 | state_flash: &mut F, | 187 | self.set_magic(aligned, BOOT_MAGIC) |
| 149 | aligned: &mut [u8], | ||
| 150 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 151 | assert_eq!(aligned.len(), F::WRITE_SIZE); | ||
| 152 | self.set_magic_blocking(aligned, BOOT_MAGIC, state_flash) | ||
| 153 | } | 188 | } |
| 154 | 189 | ||
| 155 | fn set_magic_blocking<F: NorFlash>( | 190 | fn set_magic(&mut self, aligned: &mut [u8], magic: u8) -> Result<(), FirmwareUpdaterError> { |
| 156 | &mut self, | 191 | self.state.read(0, aligned)?; |
| 157 | aligned: &mut [u8], | ||
| 158 | magic: u8, | ||
| 159 | state_flash: &mut F, | ||
| 160 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 161 | self.state.read_blocking(state_flash, 0, aligned)?; | ||
| 162 | 192 | ||
| 163 | if aligned.iter().any(|&b| b != magic) { | 193 | if aligned.iter().any(|&b| b != magic) { |
| 164 | // Read progress validity | 194 | // Read progress validity |
| 165 | self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; | 195 | self.state.read(STATE::WRITE_SIZE as u32, aligned)?; |
| 166 | |||
| 167 | // FIXME: Do not make this assumption. | ||
| 168 | const STATE_ERASE_VALUE: u8 = 0xFF; | ||
| 169 | 196 | ||
| 170 | if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { | 197 | if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) { |
| 171 | // The current progress validity marker is invalid | 198 | // The current progress validity marker is invalid |
| 172 | } else { | 199 | } else { |
| 173 | // Invalidate progress | 200 | // Invalidate progress |
| 174 | aligned.fill(!STATE_ERASE_VALUE); | 201 | aligned.fill(!STATE_ERASE_VALUE); |
| 175 | self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?; | 202 | self.state.write(STATE::WRITE_SIZE as u32, aligned)?; |
| 176 | } | 203 | } |
| 177 | 204 | ||
| 178 | // Clear magic and progress | 205 | // Clear magic and progress |
| 179 | self.state.wipe_blocking(state_flash)?; | 206 | self.state.erase(0, self.state.capacity() as u32)?; |
| 180 | 207 | ||
| 181 | // Set magic | 208 | // Set magic |
| 182 | aligned.fill(magic); | 209 | aligned.fill(magic); |
| 183 | self.state.write_blocking(state_flash, 0, aligned)?; | 210 | self.state.write(0, aligned)?; |
| 184 | } | 211 | } |
| 185 | Ok(()) | 212 | Ok(()) |
| 186 | } | 213 | } |
| @@ -192,18 +219,12 @@ impl FirmwareUpdater { | |||
| 192 | /// # Safety | 219 | /// # Safety |
| 193 | /// | 220 | /// |
| 194 | /// Failing to meet alignment and size requirements may result in a panic. | 221 | /// Failing to meet alignment and size requirements may result in a panic. |
| 195 | pub fn write_firmware_blocking<F: NorFlash>( | 222 | pub fn write_firmware(&mut self, offset: usize, data: &[u8]) -> Result<(), FirmwareUpdaterError> { |
| 196 | &mut self, | 223 | assert!(data.len() >= DFU::ERASE_SIZE); |
| 197 | offset: usize, | ||
| 198 | data: &[u8], | ||
| 199 | dfu_flash: &mut F, | ||
| 200 | ) -> Result<(), FirmwareUpdaterError> { | ||
| 201 | assert!(data.len() >= F::ERASE_SIZE); | ||
| 202 | 224 | ||
| 203 | self.dfu | 225 | self.dfu.erase(offset as u32, (offset + data.len()) as u32)?; |
| 204 | .erase_blocking(dfu_flash, offset as u32, (offset + data.len()) as u32)?; | ||
| 205 | 226 | ||
| 206 | self.dfu.write_blocking(dfu_flash, offset as u32, data)?; | 227 | self.dfu.write(offset as u32, data)?; |
| 207 | 228 | ||
| 208 | Ok(()) | 229 | Ok(()) |
| 209 | } | 230 | } |
| @@ -211,11 +232,45 @@ impl FirmwareUpdater { | |||
| 211 | /// Prepare for an incoming DFU update by erasing the entire DFU area and | 232 | /// Prepare for an incoming DFU update by erasing the entire DFU area and |
| 212 | /// returning its `Partition`. | 233 | /// returning its `Partition`. |
| 213 | /// | 234 | /// |
| 214 | /// Using this instead of `write_firmware_blocking` allows for an optimized | 235 | /// Using this instead of `write_firmware` allows for an optimized API in |
| 215 | /// API in exchange for added complexity. | 236 | /// exchange for added complexity. |
| 216 | pub fn prepare_update_blocking<F: NorFlash>(&mut self, flash: &mut F) -> Result<Partition, FirmwareUpdaterError> { | 237 | pub fn prepare_update(&mut self) -> Result<&mut DFU, FirmwareUpdaterError> { |
| 217 | self.dfu.wipe_blocking(flash)?; | 238 | self.dfu.erase(0, self.dfu.capacity() as u32)?; |
| 239 | |||
| 240 | Ok(&mut self.dfu) | ||
| 241 | } | ||
| 242 | } | ||
| 218 | 243 | ||
| 219 | Ok(self.dfu) | 244 | #[cfg(test)] |
| 245 | mod tests { | ||
| 246 | use core::cell::RefCell; | ||
| 247 | |||
| 248 | use embassy_embedded_hal::flash::partition::BlockingPartition; | ||
| 249 | use embassy_sync::blocking_mutex::raw::NoopRawMutex; | ||
| 250 | use embassy_sync::blocking_mutex::Mutex; | ||
| 251 | use sha1::{Digest, Sha1}; | ||
| 252 | |||
| 253 | use super::*; | ||
| 254 | use crate::mem_flash::MemFlash; | ||
| 255 | |||
| 256 | #[test] | ||
| 257 | fn can_verify_sha1() { | ||
| 258 | let flash = Mutex::<NoopRawMutex, _>::new(RefCell::new(MemFlash::<131072, 4096, 8>::default())); | ||
| 259 | let state = BlockingPartition::new(&flash, 0, 4096); | ||
| 260 | let dfu = BlockingPartition::new(&flash, 65536, 65536); | ||
| 261 | |||
| 262 | let update = [0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66]; | ||
| 263 | let mut to_write = [0; 4096]; | ||
| 264 | to_write[..7].copy_from_slice(update.as_slice()); | ||
| 265 | |||
| 266 | let mut updater = BlockingFirmwareUpdater::new(FirmwareUpdaterConfig { dfu, state }); | ||
| 267 | updater.write_firmware(0, to_write.as_slice()).unwrap(); | ||
| 268 | let mut chunk_buf = [0; 2]; | ||
| 269 | let mut hash = [0; 20]; | ||
| 270 | updater | ||
| 271 | .hash::<Sha1>(update.len() as u32, &mut chunk_buf, &mut hash) | ||
| 272 | .unwrap(); | ||
| 273 | |||
| 274 | assert_eq!(Sha1::digest(update).as_slice(), hash); | ||
| 220 | } | 275 | } |
| 221 | } | 276 | } |
diff --git a/embassy-boot/boot/src/firmware_updater/mod.rs b/embassy-boot/boot/src/firmware_updater/mod.rs index e09f5eb6c..a37984a3a 100644 --- a/embassy-boot/boot/src/firmware_updater/mod.rs +++ b/embassy-boot/boot/src/firmware_updater/mod.rs | |||
| @@ -2,9 +2,22 @@ | |||
| 2 | mod asynch; | 2 | mod asynch; |
| 3 | mod blocking; | 3 | mod blocking; |
| 4 | 4 | ||
| 5 | #[cfg(feature = "nightly")] | ||
| 6 | pub use asynch::FirmwareUpdater; | ||
| 7 | pub use blocking::BlockingFirmwareUpdater; | ||
| 5 | use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; | 8 | use embedded_storage::nor_flash::{NorFlashError, NorFlashErrorKind}; |
| 6 | 9 | ||
| 7 | use crate::Partition; | 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 | } | ||
| 8 | 21 | ||
| 9 | /// Errors returned by FirmwareUpdater | 22 | /// Errors returned by FirmwareUpdater |
| 10 | #[derive(Debug)] | 23 | #[derive(Debug)] |
| @@ -33,39 +46,3 @@ where | |||
| 33 | FirmwareUpdaterError::Flash(error.kind()) | 46 | FirmwareUpdaterError::Flash(error.kind()) |
| 34 | } | 47 | } |
| 35 | } | 48 | } |
| 36 | |||
| 37 | /// FirmwareUpdater is an application API for interacting with the BootLoader without the ability to | ||
| 38 | /// 'mess up' the internal bootloader state | ||
| 39 | pub struct FirmwareUpdater { | ||
| 40 | state: Partition, | ||
| 41 | dfu: Partition, | ||
| 42 | } | ||
| 43 | |||
| 44 | #[cfg(target_os = "none")] | ||
| 45 | impl Default for FirmwareUpdater { | ||
| 46 | fn default() -> Self { | ||
| 47 | extern "C" { | ||
| 48 | static __bootloader_state_start: u32; | ||
| 49 | static __bootloader_state_end: u32; | ||
| 50 | static __bootloader_dfu_start: u32; | ||
| 51 | static __bootloader_dfu_end: u32; | ||
| 52 | } | ||
| 53 | |||
| 54 | let dfu = unsafe { | ||
| 55 | Partition::new( | ||
| 56 | &__bootloader_dfu_start as *const u32 as u32, | ||
| 57 | &__bootloader_dfu_end as *const u32 as u32, | ||
| 58 | ) | ||
| 59 | }; | ||
| 60 | let state = unsafe { | ||
| 61 | Partition::new( | ||
| 62 | &__bootloader_state_start as *const u32 as u32, | ||
| 63 | &__bootloader_state_end as *const u32 as u32, | ||
| 64 | ) | ||
| 65 | }; | ||
| 66 | |||
| 67 | trace!("DFU: 0x{:x} - 0x{:x}", dfu.from, dfu.to); | ||
| 68 | trace!("STATE: 0x{:x} - 0x{:x}", state.from, state.to); | ||
| 69 | FirmwareUpdater::new(dfu, state) | ||
| 70 | } | ||
| 71 | } | ||
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs index 4521fecb0..af2d3ce02 100644 --- a/embassy-boot/boot/src/lib.rs +++ b/embassy-boot/boot/src/lib.rs | |||
| @@ -11,8 +11,10 @@ mod mem_flash; | |||
| 11 | mod partition; | 11 | mod partition; |
| 12 | 12 | ||
| 13 | pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; | 13 | pub use boot_loader::{BootError, BootFlash, BootLoader, FlashConfig, MultiFlashConfig, SingleFlashConfig}; |
| 14 | pub use firmware_updater::{FirmwareUpdater, FirmwareUpdaterError}; | ||
| 15 | pub use partition::Partition; | 14 | pub use partition::Partition; |
| 15 | #[cfg(feature = "nightly")] | ||
| 16 | pub use firmware_updater::FirmwareUpdater; | ||
| 17 | pub use firmware_updater::{BlockingFirmwareUpdater, FirmwareUpdaterConfig, FirmwareUpdaterError}; | ||
| 16 | 18 | ||
| 17 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; | 19 | pub(crate) const BOOT_MAGIC: u8 = 0xD0; |
| 18 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; | 20 | pub(crate) const SWAP_MAGIC: u8 = 0xF0; |
