aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRasmus Melchior Jacobsen <[email protected]>2023-04-04 07:18:29 +0200
committerRasmus Melchior Jacobsen <[email protected]>2023-04-04 07:18:29 +0200
commitdf3a1e1b9d337c17e08faf41c6e3c50c35eb9a6c (patch)
treede4d4d68f1b93e58e689606a17d933fa69e8464e
parent7c11d85e1ea0d01e5e1b4d6564d258663d66509b (diff)
Avoid write to not-erased magic
This introduces an additional marker to the state partition right after the magic which indicates whether the current progress is valid or not. Validation in tests that we never write without an erase is added. There is currently a FIXME in the FirmwareUpdater. Let me know if we should take the erase value as a parameter. I opened a feature request in embedded-storage to get this value in the trait. Before this, the assumption about ERASE_VALUE=0xFF was the same.
-rw-r--r--embassy-boot/boot/src/boot_loader.rs43
-rw-r--r--embassy-boot/boot/src/firmware_updater.rs34
-rw-r--r--embassy-boot/boot/src/lib.rs6
-rw-r--r--embassy-boot/boot/src/mem_flash.rs61
4 files changed, 63 insertions, 81 deletions
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
index e2e361e3c..9d047f778 100644
--- a/embassy-boot/boot/src/boot_loader.rs
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -31,7 +31,7 @@ where
31} 31}
32 32
33/// Extension of the embedded-storage flash type information with block size and erase value. 33/// Extension of the embedded-storage flash type information with block size and erase value.
34pub trait Flash: NorFlash + ReadNorFlash { 34pub trait Flash: NorFlash {
35 /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase 35 /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase
36 /// size of the flash, but for external QSPI flash modules, this can be lower. 36 /// size of the flash, but for external QSPI flash modules, this can be lower.
37 const BLOCK_SIZE: usize; 37 const BLOCK_SIZE: usize;
@@ -60,9 +60,11 @@ pub trait FlashConfig {
60/// different page sizes and flash write sizes. 60/// different page sizes and flash write sizes.
61pub struct BootLoader { 61pub struct BootLoader {
62 // Page with current state of bootloader. The state partition has the following format: 62 // Page with current state of bootloader. The state partition has the following format:
63 // | Range | Description | 63 // All ranges are in multiples of WRITE_SIZE bytes.
64 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | 64 // | Range | Description |
65 // | WRITE_SIZE - N | Progress index used while swapping or reverting | 65 // | 0..1 | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
66 // | 1..2 | Progress validity. ERASE_VALUE means valid, !ERASE_VALUE means invalid. |
67 // | 2..2 + N | Progress index used while swapping or reverting |
66 state: Partition, 68 state: Partition,
67 // Location of the partition which will be booted from 69 // Location of the partition which will be booted from
68 active: Partition, 70 active: Partition,
@@ -192,12 +194,17 @@ impl BootLoader {
192 trace!("Reverting"); 194 trace!("Reverting");
193 self.revert(p, magic, page)?; 195 self.revert(p, magic, page)?;
194 196
195 // Overwrite magic and reset progress
196 let state_flash = p.state(); 197 let state_flash = p.state();
198
199 // Invalidate progress
197 magic.fill(!P::STATE::ERASE_VALUE); 200 magic.fill(!P::STATE::ERASE_VALUE);
198 self.state.write_blocking(state_flash, 0, magic)?; 201 self.state
202 .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, magic)?;
203
204 // Clear magic and progress
199 self.state.wipe_blocking(state_flash)?; 205 self.state.wipe_blocking(state_flash)?;
200 206
207 // Set magic
201 magic.fill(BOOT_MAGIC); 208 magic.fill(BOOT_MAGIC);
202 self.state.write_blocking(state_flash, 0, magic)?; 209 self.state.write_blocking(state_flash, 0, magic)?;
203 } 210 }
@@ -215,28 +222,34 @@ impl BootLoader {
215 222
216 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> { 223 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> {
217 let write_size = aligned.len(); 224 let write_size = aligned.len();
218 let max_index = ((self.state.len() - write_size) / write_size) - 1; 225 let max_index = ((self.state.len() - write_size) / write_size) - 2;
219 aligned.fill(!P::STATE::ERASE_VALUE); 226 aligned.fill(!P::STATE::ERASE_VALUE);
220 227
221 let state_flash = config.state(); 228 let state_flash = config.state();
222 for i in 0..max_index { 229
230 self.state
231 .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, aligned)?;
232 if aligned.iter().any(|&b| b != P::STATE::ERASE_VALUE) {
233 // Progress is invalid
234 return Ok(max_index);
235 }
236
237 for index in 0..max_index {
223 self.state 238 self.state
224 .read_blocking(state_flash, (write_size + i * write_size) as u32, aligned)?; 239 .read_blocking(state_flash, (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?;
225 240
226 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { 241 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) {
227 return Ok(i); 242 return Ok(index);
228 } 243 }
229 } 244 }
230 Ok(max_index) 245 Ok(max_index)
231 } 246 }
232 247
233 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { 248 fn update_progress<P: FlashConfig>(&mut self, index: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> {
234 let write_size = magic.len();
235
236 let aligned = magic; 249 let aligned = magic;
237 aligned.fill(!P::STATE::ERASE_VALUE); 250 aligned.fill(!P::STATE::ERASE_VALUE);
238 self.state 251 self.state
239 .write_blocking(p.state(), (write_size + idx * write_size) as u32, aligned)?; 252 .write_blocking(p.state(), (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?;
240 Ok(()) 253 Ok(())
241 } 254 }
242 255
@@ -360,7 +373,7 @@ fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_s
360 assert_eq!(active.len() % page_size, 0); 373 assert_eq!(active.len() % page_size, 0);
361 assert_eq!(dfu.len() % page_size, 0); 374 assert_eq!(dfu.len() % page_size, 0);
362 assert!(dfu.len() - active.len() >= page_size); 375 assert!(dfu.len() - active.len() >= page_size);
363 assert!(2 * (active.len() / page_size) <= (state.len() - write_size) / write_size); 376 assert!(2 + 2 * (active.len() / page_size) <= state.len() / write_size);
364} 377}
365 378
366/// A flash wrapper implementing the Flash and embedded_storage traits. 379/// A flash wrapper implementing the Flash and embedded_storage traits.
diff --git a/embassy-boot/boot/src/firmware_updater.rs b/embassy-boot/boot/src/firmware_updater.rs
index af1ba114a..fe3c0452f 100644
--- a/embassy-boot/boot/src/firmware_updater.rs
+++ b/embassy-boot/boot/src/firmware_updater.rs
@@ -220,11 +220,24 @@ impl FirmwareUpdater {
220 self.state.read(state_flash, 0, aligned).await?; 220 self.state.read(state_flash, 0, aligned).await?;
221 221
222 if aligned.iter().any(|&b| b != magic) { 222 if aligned.iter().any(|&b| b != magic) {
223 aligned.fill(0); 223 // Read progress validity
224 self.state.read(state_flash, F::WRITE_SIZE as u32, aligned).await?;
225
226 // FIXME: Do not make this assumption.
227 const STATE_ERASE_VALUE: u8 = 0xFF;
228
229 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
230 // The current progress validity marker is invalid
231 } else {
232 // Invalidate progress
233 aligned.fill(!STATE_ERASE_VALUE);
234 self.state.write(state_flash, F::WRITE_SIZE as u32, aligned).await?;
235 }
224 236
225 self.state.write(state_flash, 0, aligned).await?; 237 // Clear magic and progress
226 self.state.wipe(state_flash).await?; 238 self.state.wipe(state_flash).await?;
227 239
240 // Set magic
228 aligned.fill(magic); 241 aligned.fill(magic);
229 self.state.write(state_flash, 0, aligned).await?; 242 self.state.write(state_flash, 0, aligned).await?;
230 } 243 }
@@ -420,11 +433,24 @@ impl FirmwareUpdater {
420 self.state.read_blocking(state_flash, 0, aligned)?; 433 self.state.read_blocking(state_flash, 0, aligned)?;
421 434
422 if aligned.iter().any(|&b| b != magic) { 435 if aligned.iter().any(|&b| b != magic) {
423 aligned.fill(0); 436 // Read progress validity
437 self.state.read_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
438
439 // FIXME: Do not make this assumption.
440 const STATE_ERASE_VALUE: u8 = 0xFF;
441
442 if aligned.iter().any(|&b| b != STATE_ERASE_VALUE) {
443 // The current progress validity marker is invalid
444 } else {
445 // Invalidate progress
446 aligned.fill(!STATE_ERASE_VALUE);
447 self.state.write_blocking(state_flash, F::WRITE_SIZE as u32, aligned)?;
448 }
424 449
425 self.state.write_blocking(state_flash, 0, aligned)?; 450 // Clear magic and progress
426 self.state.wipe_blocking(state_flash)?; 451 self.state.wipe_blocking(state_flash)?;
427 452
453 // Set magic
428 aligned.fill(magic); 454 aligned.fill(magic);
429 self.state.write_blocking(state_flash, 0, aligned)?; 455 self.state.write_blocking(state_flash, 0, aligned)?;
430 } 456 }
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index a5795781f..597ce3fa3 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -93,7 +93,7 @@ mod tests {
93 const STATE: Partition = Partition::new(0, 4096); 93 const STATE: Partition = Partition::new(0, 4096);
94 const ACTIVE: Partition = Partition::new(4096, 61440); 94 const ACTIVE: Partition = Partition::new(4096, 61440);
95 const DFU: Partition = Partition::new(61440, 122880); 95 const DFU: Partition = Partition::new(61440, 122880);
96 let mut flash = MemFlash::<131072, 4096, 4>::random().with_limited_erase_before_write_verification(4..); 96 let mut flash = MemFlash::<131072, 4096, 4>::random();
97 97
98 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 98 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
99 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 99 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
@@ -166,7 +166,7 @@ mod tests {
166 166
167 let mut active = MemFlash::<16384, 4096, 8>::random(); 167 let mut active = MemFlash::<16384, 4096, 8>::random();
168 let mut dfu = MemFlash::<16384, 2048, 8>::random(); 168 let mut dfu = MemFlash::<16384, 2048, 8>::random();
169 let mut state = MemFlash::<4096, 128, 4>::random().with_limited_erase_before_write_verification(2048 + 4..); 169 let mut state = MemFlash::<4096, 128, 4>::random();
170 let mut aligned = [0; 4]; 170 let mut aligned = [0; 4];
171 171
172 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 172 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
@@ -220,7 +220,7 @@ mod tests {
220 let mut aligned = [0; 4]; 220 let mut aligned = [0; 4];
221 let mut active = MemFlash::<16384, 2048, 4>::random(); 221 let mut active = MemFlash::<16384, 2048, 4>::random();
222 let mut dfu = MemFlash::<16384, 4096, 8>::random(); 222 let mut dfu = MemFlash::<16384, 4096, 8>::random();
223 let mut state = MemFlash::<4096, 128, 4>::random().with_limited_erase_before_write_verification(2048 + 4..); 223 let mut state = MemFlash::<4096, 128, 4>::random();
224 224
225 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 225 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
226 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 226 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
diff --git a/embassy-boot/boot/src/mem_flash.rs b/embassy-boot/boot/src/mem_flash.rs
index e87ccd37a..828aad9d9 100644
--- a/embassy-boot/boot/src/mem_flash.rs
+++ b/embassy-boot/boot/src/mem_flash.rs
@@ -9,8 +9,6 @@ use crate::Flash;
9 9
10pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> { 10pub struct MemFlash<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> {
11 pub mem: [u8; SIZE], 11 pub mem: [u8; SIZE],
12 pub allow_same_write: bool,
13 pub verify_erased_before_write: Range<usize>,
14 pub pending_write_successes: Option<usize>, 12 pub pending_write_successes: Option<usize>,
15} 13}
16 14
@@ -21,8 +19,6 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
21 pub const fn new(fill: u8) -> Self { 19 pub const fn new(fill: u8) -> Self {
22 Self { 20 Self {
23 mem: [fill; SIZE], 21 mem: [fill; SIZE],
24 allow_same_write: false,
25 verify_erased_before_write: 0..SIZE,
26 pending_write_successes: None, 22 pending_write_successes: None,
27 } 23 }
28 } 24 }
@@ -35,37 +31,9 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> MemFla
35 } 31 }
36 Self { 32 Self {
37 mem, 33 mem,
38 allow_same_write: false,
39 verify_erased_before_write: 0..SIZE,
40 pending_write_successes: None, 34 pending_write_successes: None,
41 } 35 }
42 } 36 }
43
44 #[must_use]
45 pub fn allow_same_write(self, allow: bool) -> Self {
46 Self {
47 allow_same_write: allow,
48 ..self
49 }
50 }
51
52 #[must_use]
53 pub fn with_limited_erase_before_write_verification<R: RangeBounds<usize>>(self, verified_range: R) -> Self {
54 let start = match verified_range.start_bound() {
55 Bound::Included(start) => *start,
56 Bound::Excluded(start) => *start + 1,
57 Bound::Unbounded => 0,
58 };
59 let end = match verified_range.end_bound() {
60 Bound::Included(end) => *end - 1,
61 Bound::Excluded(end) => *end,
62 Bound::Unbounded => self.mem.len(),
63 };
64 Self {
65 verify_erased_before_write: start..end,
66 ..self
67 }
68 }
69} 37}
70 38
71impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default 39impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> Default
@@ -150,14 +118,8 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> NorFla
150 .take(bytes.len()) 118 .take(bytes.len())
151 .zip(bytes) 119 .zip(bytes)
152 { 120 {
153 if self.allow_same_write && mem_byte == new_byte { 121 assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
154 // Write does not change the flash memory which is allowed 122 *mem_byte = *new_byte;
155 } else {
156 if self.verify_erased_before_write.contains(&offset) {
157 assert_eq!(0xFF, *mem_byte, "Offset {} is not erased", offset);
158 }
159 *mem_byte &= *new_byte;
160 }
161 } 123 }
162 124
163 Ok(()) 125 Ok(())
@@ -192,22 +154,3 @@ impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncN
192 <Self as NorFlash>::write(self, offset, bytes) 154 <Self as NorFlash>::write(self, offset, bytes)
193 } 155 }
194} 156}
195
196#[cfg(test)]
197mod tests {
198 use core::ops::Range;
199
200 use embedded_storage::nor_flash::NorFlash;
201
202 use super::MemFlash;
203
204 #[test]
205 fn writes_only_flip_bits_from_1_to_0() {
206 let mut flash = MemFlash::<16, 16, 1>::default().with_limited_erase_before_write_verification(0..0);
207
208 flash.write(0, &[0x55]).unwrap();
209 flash.write(0, &[0xAA]).unwrap();
210
211 assert_eq!(0x00, flash.mem[0]);
212 }
213}