aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/boot/src
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2022-09-02 06:26:08 +0000
committerGitHub <[email protected]>2022-09-02 06:26:08 +0000
commit8b464d2668657284527693fbf9cc348766a00758 (patch)
treed2f9a27e103cab8e02b42c84201d37bf8a02e1b3 /embassy-boot/boot/src
parent835b69456d6a270e6d5c869da46c0df30fe54254 (diff)
parent3ca73144765411994759194a2279b567f4508be5 (diff)
Merge #935
935: Remove generic const expressions from embassy-boot r=lulf a=lulf * Remove the need for generic const expressions and use buffers provided in the flash config. * Extend embedded-storage traits to simplify generics. * Document all public APIs * Add toplevel README * Expose AlignedBuffer type for convenience. * Update examples Co-authored-by: Ulf Lilleengen <[email protected]>
Diffstat (limited to 'embassy-boot/boot/src')
-rw-r--r--embassy-boot/boot/src/lib.rs544
1 files changed, 312 insertions, 232 deletions
diff --git a/embassy-boot/boot/src/lib.rs b/embassy-boot/boot/src/lib.rs
index 51e1056cf..e8ebe628d 100644
--- a/embassy-boot/boot/src/lib.rs
+++ b/embassy-boot/boot/src/lib.rs
@@ -1,53 +1,54 @@
1#![feature(type_alias_impl_trait)] 1#![feature(type_alias_impl_trait)]
2#![feature(generic_associated_types)] 2#![feature(generic_associated_types)]
3#![feature(generic_const_exprs)]
4#![allow(incomplete_features)]
5#![no_std] 3#![no_std]
6///! embassy-boot is a bootloader and firmware updater for embedded devices with flash 4#![warn(missing_docs)]
7///! storage implemented using embedded-storage 5#![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///!
17mod fmt; 6mod fmt;
18 7
19use embedded_storage::nor_flash::{NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash}; 8use embedded_storage::nor_flash::{ErrorType, NorFlash, NorFlashError, NorFlashErrorKind, ReadNorFlash};
20use embedded_storage_async::nor_flash::AsyncNorFlash; 9use embedded_storage_async::nor_flash::AsyncNorFlash;
21 10
22const BOOT_MAGIC: u8 = 0xD0; 11const BOOT_MAGIC: u8 = 0xD0;
23const SWAP_MAGIC: u8 = 0xF0; 12const SWAP_MAGIC: u8 = 0xF0;
24 13
14/// A region in flash used by the bootloader.
25#[derive(Copy, Clone, Debug)] 15#[derive(Copy, Clone, Debug)]
26#[cfg_attr(feature = "defmt", derive(defmt::Format))] 16#[cfg_attr(feature = "defmt", derive(defmt::Format))]
27pub struct Partition { 17pub struct Partition {
18 /// Start of the flash region.
28 pub from: usize, 19 pub from: usize,
20 /// End of the flash region.
29 pub to: usize, 21 pub to: usize,
30} 22}
31 23
32impl Partition { 24impl Partition {
25 /// Create a new partition with the provided range
33 pub const fn new(from: usize, to: usize) -> Self { 26 pub const fn new(from: usize, to: usize) -> Self {
34 Self { from, to } 27 Self { from, to }
35 } 28 }
29
30 /// Return the length of the partition
36 pub const fn len(&self) -> usize { 31 pub const fn len(&self) -> usize {
37 self.to - self.from 32 self.to - self.from
38 } 33 }
39} 34}
40 35
41#[derive(PartialEq, Debug)] 36/// The state of the bootloader after running prepare.
37#[derive(PartialEq, Eq, Debug)]
42#[cfg_attr(feature = "defmt", derive(defmt::Format))] 38#[cfg_attr(feature = "defmt", derive(defmt::Format))]
43pub enum State { 39pub enum State {
40 /// Bootloader is ready to boot the active partition.
44 Boot, 41 Boot,
42 /// Bootloader has swapped the active partition with the dfu partition and will attempt boot.
45 Swap, 43 Swap,
46} 44}
47 45
48#[derive(PartialEq, Debug)] 46/// Errors returned by bootloader
47#[derive(PartialEq, Eq, Debug)]
49pub enum BootError { 48pub enum BootError {
49 /// Error from flash.
50 Flash(NorFlashErrorKind), 50 Flash(NorFlashErrorKind),
51 /// Invalid bootloader magic
51 BadMagic, 52 BadMagic,
52} 53}
53 54
@@ -60,19 +61,39 @@ where
60 } 61 }
61} 62}
62 63
63pub trait FlashConfig { 64/// Buffer aligned to 32 byte boundary, largest known alignment requirement for embassy-boot.
64 const BLOCK_SIZE: usize; 65#[repr(align(32))]
65 const ERASE_VALUE: u8; 66pub struct AlignedBuffer<const N: usize>(pub [u8; N]);
66 type FLASH: NorFlash + ReadNorFlash; 67
68impl<const N: usize> AsRef<[u8]> for AlignedBuffer<N> {
69 fn as_ref(&self) -> &[u8] {
70 &self.0
71 }
72}
73
74impl<const N: usize> AsMut<[u8]> for AlignedBuffer<N> {
75 fn as_mut(&mut self) -> &mut [u8] {
76 &mut self.0
77 }
78}
67 79
68 fn flash(&mut self) -> &mut Self::FLASH; 80/// Extension of the embedded-storage flash type information with block size and erase value.
81pub trait Flash: NorFlash + ReadNorFlash {
82 /// The block size that should be used when writing to flash. For most builtin flashes, this is the same as the erase
83 /// size of the flash, but for external QSPI flash modules, this can be lower.
84 const BLOCK_SIZE: usize;
85 /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value.
86 const ERASE_VALUE: u8 = 0xFF;
69} 87}
70 88
71/// Trait defining the flash handles used for active and DFU partition 89/// Trait defining the flash handles used for active and DFU partition
72pub trait FlashProvider { 90pub trait FlashConfig {
73 type STATE: FlashConfig; 91 /// Flash type used for the state partition.
74 type ACTIVE: FlashConfig; 92 type STATE: Flash;
75 type DFU: FlashConfig; 93 /// Flash type used for the active partition.
94 type ACTIVE: Flash;
95 /// Flash type used for the dfu partition.
96 type DFU: Flash;
76 97
77 /// Return flash instance used to write/read to/from active partition. 98 /// Return flash instance used to write/read to/from active partition.
78 fn active(&mut self) -> &mut Self::ACTIVE; 99 fn active(&mut self) -> &mut Self::ACTIVE;
@@ -84,9 +105,7 @@ pub trait FlashProvider {
84 105
85/// BootLoader works with any flash implementing embedded_storage and can also work with 106/// BootLoader works with any flash implementing embedded_storage and can also work with
86/// different page sizes and flash write sizes. 107/// different page sizes and flash write sizes.
87/// 108pub struct BootLoader {
88/// The PAGE_SIZE const parameter must be a multiple of the ACTIVE and DFU page sizes.
89pub struct BootLoader<const PAGE_SIZE: usize> {
90 // Page with current state of bootloader. The state partition has the following format: 109 // Page with current state of bootloader. The state partition has the following format:
91 // | Range | Description | 110 // | Range | Description |
92 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. | 111 // | 0 - WRITE_SIZE | Magic indicating bootloader state. BOOT_MAGIC means boot, SWAP_MAGIC means swap. |
@@ -98,15 +117,16 @@ pub struct BootLoader<const PAGE_SIZE: usize> {
98 dfu: Partition, 117 dfu: Partition,
99} 118}
100 119
101impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> { 120impl BootLoader {
121 /// Create a new instance of a bootloader with the given partitions.
122 ///
123 /// - All partitions must be aligned with the PAGE_SIZE const generic parameter.
124 /// - The dfu partition must be at least PAGE_SIZE bigger than the active partition.
102 pub fn new(active: Partition, dfu: Partition, state: Partition) -> Self { 125 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 } 126 Self { active, dfu, state }
108 } 127 }
109 128
129 /// Return the boot address for the active partition.
110 pub fn boot_address(&self) -> usize { 130 pub fn boot_address(&self) -> usize {
111 self.active.from 131 self.active.from
112 } 132 }
@@ -194,44 +214,43 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
194 /// | DFU | 3 | 3 | 2 | 1 | 3 | 214 /// | DFU | 3 | 3 | 2 | 1 | 3 |
195 /// +-----------+--------------+--------+--------+--------+--------+ 215 /// +-----------+--------------+--------+--------+--------+--------+
196 /// 216 ///
197 pub fn prepare_boot<P: FlashProvider>(&mut self, p: &mut P) -> Result<State, BootError> 217 pub fn prepare_boot<P: FlashConfig>(
198 where 218 &mut self,
199 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, 219 p: &mut P,
200 [(); <<P as FlashProvider>::ACTIVE as FlashConfig>::FLASH::ERASE_SIZE]:, 220 magic: &mut [u8],
201 { 221 page: &mut [u8],
222 ) -> Result<State, BootError> {
202 // Ensure we have enough progress pages to store copy progress 223 // Ensure we have enough progress pages to store copy progress
203 assert!( 224 assert_eq!(self.active.len() % page.len(), 0);
204 self.active.len() / PAGE_SIZE 225 assert_eq!(self.dfu.len() % page.len(), 0);
205 <= (self.state.len() - <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE) 226 assert!(self.dfu.len() - self.active.len() >= page.len());
206 / <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE 227 assert!(self.active.len() / page.len() <= (self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE);
207 ); 228 assert_eq!(magic.len(), P::STATE::WRITE_SIZE);
208 229
209 // Copy contents from partition N to active 230 // Copy contents from partition N to active
210 let state = self.read_state(p.state())?; 231 let state = self.read_state(p, magic)?;
211 match state { 232 match state {
212 State::Swap => { 233 State::Swap => {
213 // 234 //
214 // Check if we already swapped. If we're in the swap state, this means we should revert 235 // 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 236 // since the app has failed to mark boot as successful
216 // 237 //
217 if !self.is_swapped(p.state())? { 238 if !self.is_swapped(p, magic, page)? {
218 trace!("Swapping"); 239 trace!("Swapping");
219 self.swap(p)?; 240 self.swap(p, magic, page)?;
220 trace!("Swapping done"); 241 trace!("Swapping done");
221 } else { 242 } else {
222 trace!("Reverting"); 243 trace!("Reverting");
223 self.revert(p)?; 244 self.revert(p, magic, page)?;
224 245
225 // Overwrite magic and reset progress 246 // Overwrite magic and reset progress
226 let fstate = p.state().flash(); 247 let fstate = p.state();
227 let aligned = Aligned( 248 magic.fill(!P::STATE::ERASE_VALUE);
228 [!P::STATE::ERASE_VALUE; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE], 249 fstate.write(self.state.from as u32, magic)?;
229 );
230 fstate.write(self.state.from as u32, &aligned.0)?;
231 fstate.erase(self.state.from as u32, self.state.to as u32)?; 250 fstate.erase(self.state.from as u32, self.state.to as u32)?;
232 let aligned = 251
233 Aligned([BOOT_MAGIC; <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]); 252 magic.fill(BOOT_MAGIC);
234 fstate.write(self.state.from as u32, &aligned.0)?; 253 fstate.write(self.state.from as u32, magic)?;
235 } 254 }
236 } 255 }
237 _ => {} 256 _ => {}
@@ -239,166 +258,152 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
239 Ok(state) 258 Ok(state)
240 } 259 }
241 260
242 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P) -> Result<bool, BootError> 261 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<bool, BootError> {
243 where 262 let page_size = page.len();
244 [(); P::FLASH::WRITE_SIZE]:, 263 let page_count = self.active.len() / page_size;
245 { 264 let progress = self.current_progress(p, magic)?;
246 let page_count = self.active.len() / P::FLASH::ERASE_SIZE;
247 let progress = self.current_progress(p)?;
248 265
249 Ok(progress >= page_count * 2) 266 Ok(progress >= page_count * 2)
250 } 267 }
251 268
252 fn current_progress<P: FlashConfig>(&mut self, p: &mut P) -> Result<usize, BootError> 269 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> {
253 where 270 let write_size = aligned.len();
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; 271 let max_index = ((self.state.len() - write_size) / write_size) - 1;
258 let flash = p.flash(); 272 aligned.fill(!P::STATE::ERASE_VALUE);
259 let mut aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); 273
274 let flash = config.state();
260 for i in 0..max_index { 275 for i in 0..max_index {
261 flash.read((self.state.from + write_size + i * write_size) as u32, &mut aligned.0)?; 276 flash.read((self.state.from + write_size + i * write_size) as u32, aligned)?;
262 if aligned.0 == [P::ERASE_VALUE; P::FLASH::WRITE_SIZE] { 277
278 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) {
263 return Ok(i); 279 return Ok(i);
264 } 280 }
265 } 281 }
266 Ok(max_index) 282 Ok(max_index)
267 } 283 }
268 284
269 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P) -> Result<(), BootError> 285 fn update_progress<P: FlashConfig>(&mut self, idx: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> {
270 where 286 let flash = p.state();
271 [(); P::FLASH::WRITE_SIZE]:, 287 let write_size = magic.len();
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; 288 let w = self.state.from + write_size + idx * write_size;
276 let aligned = Aligned([!P::ERASE_VALUE; P::FLASH::WRITE_SIZE]); 289
277 flash.write(w as u32, &aligned.0)?; 290 let aligned = magic;
291 aligned.fill(!P::STATE::ERASE_VALUE);
292 flash.write(w as u32, aligned)?;
278 Ok(()) 293 Ok(())
279 } 294 }
280 295
281 fn active_addr(&self, n: usize) -> usize { 296 fn active_addr(&self, n: usize, page_size: usize) -> usize {
282 self.active.from + n * PAGE_SIZE 297 self.active.from + n * page_size
283 } 298 }
284 299
285 fn dfu_addr(&self, n: usize) -> usize { 300 fn dfu_addr(&self, n: usize, page_size: usize) -> usize {
286 self.dfu.from + n * PAGE_SIZE 301 self.dfu.from + n * page_size
287 } 302 }
288 303
289 fn copy_page_once_to_active<P: FlashProvider>( 304 fn copy_page_once_to_active<P: FlashConfig>(
290 &mut self, 305 &mut self,
291 idx: usize, 306 idx: usize,
292 from_page: usize, 307 from_page: usize,
293 to_page: usize, 308 to_page: usize,
294 p: &mut P, 309 p: &mut P,
295 ) -> Result<(), BootError> 310 magic: &mut [u8],
296 where 311 page: &mut [u8],
297 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, 312 ) -> Result<(), BootError> {
298 { 313 let buf = page;
299 let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; 314 if self.current_progress(p, magic)? <= idx {
300 if self.current_progress(p.state())? <= idx {
301 let mut offset = from_page; 315 let mut offset = from_page;
302 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) { 316 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
303 p.dfu().flash().read(offset as u32, chunk)?; 317 p.dfu().read(offset as u32, chunk)?;
304 offset += chunk.len(); 318 offset += chunk.len();
305 } 319 }
306 320
307 p.active().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; 321 p.active().erase(to_page as u32, (to_page + buf.len()) as u32)?;
308 322
309 let mut offset = to_page; 323 let mut offset = to_page;
310 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { 324 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) {
311 p.active().flash().write(offset as u32, &chunk)?; 325 p.active().write(offset as u32, chunk)?;
312 offset += chunk.len(); 326 offset += chunk.len();
313 } 327 }
314 self.update_progress(idx, p.state())?; 328 self.update_progress(idx, p, magic)?;
315 } 329 }
316 Ok(()) 330 Ok(())
317 } 331 }
318 332
319 fn copy_page_once_to_dfu<P: FlashProvider>( 333 fn copy_page_once_to_dfu<P: FlashConfig>(
320 &mut self, 334 &mut self,
321 idx: usize, 335 idx: usize,
322 from_page: usize, 336 from_page: usize,
323 to_page: usize, 337 to_page: usize,
324 p: &mut P, 338 p: &mut P,
325 ) -> Result<(), BootError> 339 magic: &mut [u8],
326 where 340 page: &mut [u8],
327 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, 341 ) -> Result<(), BootError> {
328 { 342 let buf = page;
329 let mut buf: [u8; PAGE_SIZE] = [0; PAGE_SIZE]; 343 if self.current_progress(p, magic)? <= idx {
330 if self.current_progress(p.state())? <= idx {
331 let mut offset = from_page; 344 let mut offset = from_page;
332 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) { 345 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
333 p.active().flash().read(offset as u32, chunk)?; 346 p.active().read(offset as u32, chunk)?;
334 offset += chunk.len(); 347 offset += chunk.len();
335 } 348 }
336 349
337 p.dfu().flash().erase(to_page as u32, (to_page + PAGE_SIZE) as u32)?; 350 p.dfu().erase(to_page as u32, (to_page + buf.len()) as u32)?;
338 351
339 let mut offset = to_page; 352 let mut offset = to_page;
340 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { 353 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) {
341 p.dfu().flash().write(offset as u32, chunk)?; 354 p.dfu().write(offset as u32, chunk)?;
342 offset += chunk.len(); 355 offset += chunk.len();
343 } 356 }
344 self.update_progress(idx, p.state())?; 357 self.update_progress(idx, p, magic)?;
345 } 358 }
346 Ok(()) 359 Ok(())
347 } 360 }
348 361
349 fn swap<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> 362 fn swap<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
350 where 363 let page_size = page.len();
351 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, 364 let page_count = self.active.len() / page_size;
352 {
353 let page_count = self.active.len() / PAGE_SIZE;
354 trace!("Page count: {}", page_count); 365 trace!("Page count: {}", page_count);
355 for page in 0..page_count { 366 for page_num in 0..page_count {
356 trace!("COPY PAGE {}", page); 367 trace!("COPY PAGE {}", page_num);
357 // Copy active page to the 'next' DFU page. 368 // Copy active page to the 'next' DFU page.
358 let active_page = self.active_addr(page_count - 1 - page); 369 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
359 let dfu_page = self.dfu_addr(page_count - page); 370 let dfu_page = self.dfu_addr(page_count - page_num, page_size);
360 //trace!("Copy active {} to dfu {}", active_page, dfu_page); 371 //trace!("Copy active {} to dfu {}", active_page, dfu_page);
361 self.copy_page_once_to_dfu(page * 2, active_page, dfu_page, p)?; 372 self.copy_page_once_to_dfu(page_num * 2, active_page, dfu_page, p, magic, page)?;
362 373
363 // Copy DFU page to the active page 374 // Copy DFU page to the active page
364 let active_page = self.active_addr(page_count - 1 - page); 375 let active_page = self.active_addr(page_count - 1 - page_num, page_size);
365 let dfu_page = self.dfu_addr(page_count - 1 - page); 376 let dfu_page = self.dfu_addr(page_count - 1 - page_num, page_size);
366 //trace!("Copy dfy {} to active {}", dfu_page, active_page); 377 //trace!("Copy dfy {} to active {}", dfu_page, active_page);
367 self.copy_page_once_to_active(page * 2 + 1, dfu_page, active_page, p)?; 378 self.copy_page_once_to_active(page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
368 } 379 }
369 380
370 Ok(()) 381 Ok(())
371 } 382 }
372 383
373 fn revert<P: FlashProvider>(&mut self, p: &mut P) -> Result<(), BootError> 384 fn revert<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> {
374 where 385 let page_size = page.len();
375 [(); <<P as FlashProvider>::STATE as FlashConfig>::FLASH::WRITE_SIZE]:, 386 let page_count = self.active.len() / page_size;
376 { 387 for page_num in 0..page_count {
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 388 // Copy the bad active page to the DFU page
380 let active_page = self.active_addr(page); 389 let active_page = self.active_addr(page_num, page_size);
381 let dfu_page = self.dfu_addr(page); 390 let dfu_page = self.dfu_addr(page_num, page_size);
382 self.copy_page_once_to_dfu(page_count * 2 + page * 2, active_page, dfu_page, p)?; 391 self.copy_page_once_to_dfu(page_count * 2 + page_num * 2, active_page, dfu_page, p, magic, page)?;
383 392
384 // Copy the DFU page back to the active page 393 // Copy the DFU page back to the active page
385 let active_page = self.active_addr(page); 394 let active_page = self.active_addr(page_num, page_size);
386 let dfu_page = self.dfu_addr(page + 1); 395 let dfu_page = self.dfu_addr(page_num + 1, page_size);
387 self.copy_page_once_to_active(page_count * 2 + page * 2 + 1, dfu_page, active_page, p)?; 396 self.copy_page_once_to_active(page_count * 2 + page_num * 2 + 1, dfu_page, active_page, p, magic, page)?;
388 } 397 }
389 398
390 Ok(()) 399 Ok(())
391 } 400 }
392 401
393 fn read_state<P: FlashConfig>(&mut self, p: &mut P) -> Result<State, BootError> 402 fn read_state<P: FlashConfig>(&mut self, config: &mut P, magic: &mut [u8]) -> Result<State, BootError> {
394 where 403 let flash = config.state();
395 [(); P::FLASH::WRITE_SIZE]:, 404 flash.read(self.state.from as u32, magic)?;
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 405
401 if magic == [SWAP_MAGIC; P::FLASH::WRITE_SIZE] { 406 if !magic.iter().any(|&b| b != SWAP_MAGIC) {
402 Ok(State::Swap) 407 Ok(State::Swap)
403 } else { 408 } else {
404 Ok(State::Boot) 409 Ok(State::Boot)
@@ -406,108 +411,149 @@ impl<const PAGE_SIZE: usize> BootLoader<PAGE_SIZE> {
406 } 411 }
407} 412}
408 413
409/// Convenience provider that uses a single flash for everything 414/// Convenience provider that uses a single flash for all partitions.
410pub struct SingleFlashProvider<'a, F, const ERASE_VALUE: u8 = 0xFF> 415pub struct SingleFlashConfig<'a, F>
411where 416where
412 F: NorFlash + ReadNorFlash, 417 F: Flash,
413{ 418{
414 config: SingleFlashConfig<'a, F, ERASE_VALUE>, 419 flash: &'a mut F,
415} 420}
416 421
417impl<'a, F, const ERASE_VALUE: u8> SingleFlashProvider<'a, F, ERASE_VALUE> 422impl<'a, F> SingleFlashConfig<'a, F>
418where 423where
419 F: NorFlash + ReadNorFlash, 424 F: Flash,
420{ 425{
426 /// Create a provider for a single flash.
421 pub fn new(flash: &'a mut F) -> Self { 427 pub fn new(flash: &'a mut F) -> Self {
422 Self { 428 Self { flash }
423 config: SingleFlashConfig { flash }, 429 }
424 } 430}
431
432impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
433where
434 F: Flash,
435{
436 type STATE = F;
437 type ACTIVE = F;
438 type DFU = F;
439
440 fn active(&mut self) -> &mut Self::STATE {
441 self.flash
442 }
443 fn dfu(&mut self) -> &mut Self::ACTIVE {
444 self.flash
445 }
446 fn state(&mut self) -> &mut Self::DFU {
447 self.flash
425 } 448 }
426} 449}
427 450
428pub struct SingleFlashConfig<'a, F, const ERASE_VALUE: u8 = 0xFF> 451/// A flash wrapper implementing the Flash and embedded_storage traits.
452pub struct BootFlash<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF>
429where 453where
430 F: NorFlash + ReadNorFlash, 454 F: NorFlash + ReadNorFlash,
431{ 455{
432 flash: &'a mut F, 456 flash: &'a mut F,
433} 457}
434 458
435impl<'a, F> FlashProvider for SingleFlashProvider<'a, F> 459impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
436where 460where
437 F: NorFlash + ReadNorFlash, 461 F: NorFlash + ReadNorFlash,
438{ 462{
439 type STATE = SingleFlashConfig<'a, F>; 463 /// Create a new instance of a bootable flash
440 type ACTIVE = SingleFlashConfig<'a, F>; 464 pub fn new(flash: &'a mut F) -> Self {
441 type DFU = SingleFlashConfig<'a, F>; 465 Self { flash }
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 } 466 }
452} 467}
453 468
454impl<'a, F, const ERASE_VALUE: u8> FlashConfig for SingleFlashConfig<'a, F, ERASE_VALUE> 469impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
455where 470where
456 F: NorFlash + ReadNorFlash, 471 F: NorFlash + ReadNorFlash,
457{ 472{
458 const BLOCK_SIZE: usize = F::ERASE_SIZE; 473 const BLOCK_SIZE: usize = BLOCK_SIZE;
459 const ERASE_VALUE: u8 = ERASE_VALUE; 474 const ERASE_VALUE: u8 = ERASE_VALUE;
460 type FLASH = F; 475}
461 fn flash(&mut self) -> &mut F { 476
462 self.flash 477impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
478where
479 F: ReadNorFlash + NorFlash,
480{
481 type Error = F::Error;
482}
483
484impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
485where
486 F: ReadNorFlash + NorFlash,
487{
488 const WRITE_SIZE: usize = F::WRITE_SIZE;
489 const ERASE_SIZE: usize = F::ERASE_SIZE;
490
491 fn erase(&mut self, from: u32, to: u32) -> Result<(), Self::Error> {
492 F::erase(self.flash, from, to)
493 }
494
495 fn write(&mut self, offset: u32, bytes: &[u8]) -> Result<(), Self::Error> {
496 F::write(self.flash, offset, bytes)
463 } 497 }
464} 498}
465 499
466/// Convenience provider that uses a single flash for everything 500impl<'a, F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<'a, F, BLOCK_SIZE, ERASE_VALUE>
467pub struct MultiFlashProvider<'a, ACTIVE, STATE, DFU>
468where 501where
469 ACTIVE: NorFlash + ReadNorFlash, 502 F: ReadNorFlash + NorFlash,
470 STATE: NorFlash + ReadNorFlash,
471 DFU: NorFlash + ReadNorFlash,
472{ 503{
473 active: SingleFlashConfig<'a, ACTIVE>, 504 const READ_SIZE: usize = F::READ_SIZE;
474 state: SingleFlashConfig<'a, STATE>, 505
475 dfu: SingleFlashConfig<'a, DFU>, 506 fn read(&mut self, offset: u32, bytes: &mut [u8]) -> Result<(), Self::Error> {
507 F::read(self.flash, offset, bytes)
508 }
509
510 fn capacity(&self) -> usize {
511 F::capacity(self.flash)
512 }
476} 513}
477 514
478impl<'a, ACTIVE, STATE, DFU> MultiFlashProvider<'a, ACTIVE, STATE, DFU> 515/// Convenience flash provider that uses separate flash instances for each partition.
516pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
479where 517where
480 ACTIVE: NorFlash + ReadNorFlash, 518 ACTIVE: Flash,
481 STATE: NorFlash + ReadNorFlash, 519 STATE: Flash,
482 DFU: NorFlash + ReadNorFlash, 520 DFU: Flash,
483{ 521{
522 active: &'a mut ACTIVE,
523 state: &'a mut STATE,
524 dfu: &'a mut DFU,
525}
526
527impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
528where
529 ACTIVE: Flash,
530 STATE: Flash,
531 DFU: Flash,
532{
533 /// Create a new flash provider with separate configuration for all three partitions.
484 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { 534 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
485 Self { 535 Self { active, state, dfu }
486 active: SingleFlashConfig { flash: active },
487 state: SingleFlashConfig { flash: state },
488 dfu: SingleFlashConfig { flash: dfu },
489 }
490 } 536 }
491} 537}
492 538
493impl<'a, ACTIVE, STATE, DFU> FlashProvider for MultiFlashProvider<'a, ACTIVE, STATE, DFU> 539impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
494where 540where
495 ACTIVE: NorFlash + ReadNorFlash, 541 ACTIVE: Flash,
496 STATE: NorFlash + ReadNorFlash, 542 STATE: Flash,
497 DFU: NorFlash + ReadNorFlash, 543 DFU: Flash,
498{ 544{
499 type STATE = SingleFlashConfig<'a, STATE>; 545 type STATE = STATE;
500 type ACTIVE = SingleFlashConfig<'a, ACTIVE>; 546 type ACTIVE = ACTIVE;
501 type DFU = SingleFlashConfig<'a, DFU>; 547 type DFU = DFU;
502 548
503 fn active(&mut self) -> &mut Self::ACTIVE { 549 fn active(&mut self) -> &mut Self::ACTIVE {
504 &mut self.active 550 self.active
505 } 551 }
506 fn dfu(&mut self) -> &mut Self::DFU { 552 fn dfu(&mut self) -> &mut Self::DFU {
507 &mut self.dfu 553 self.dfu
508 } 554 }
509 fn state(&mut self) -> &mut Self::STATE { 555 fn state(&mut self) -> &mut Self::STATE {
510 &mut self.state 556 self.state
511 } 557 }
512} 558}
513 559
@@ -518,10 +564,6 @@ pub struct FirmwareUpdater {
518 dfu: Partition, 564 dfu: Partition,
519} 565}
520 566
521// NOTE: Aligned to the largest write size supported by flash
522#[repr(align(32))]
523pub struct Aligned<const N: usize>([u8; N]);
524
525impl Default for FirmwareUpdater { 567impl Default for FirmwareUpdater {
526 fn default() -> Self { 568 fn default() -> Self {
527 extern "C" { 569 extern "C" {
@@ -551,6 +593,7 @@ impl Default for FirmwareUpdater {
551} 593}
552 594
553impl FirmwareUpdater { 595impl FirmwareUpdater {
596 /// Create a firmware updater instance with partition ranges for the update and state partitions.
554 pub const fn new(dfu: Partition, state: Partition) -> Self { 597 pub const fn new(dfu: Partition, state: Partition) -> Self {
555 Self { dfu, state } 598 Self { dfu, state }
556 } 599 }
@@ -560,23 +603,24 @@ impl FirmwareUpdater {
560 self.dfu.len() 603 self.dfu.len()
561 } 604 }
562 605
563 /// Instruct bootloader that DFU should commence at next boot. 606 /// Mark to trigger firmware swap on next boot.
564 /// Must be provided with an aligned buffer to use for reading and writing magic; 607 ///
565 pub async fn update<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> 608 /// # Safety
566 where 609 ///
567 [(); F::WRITE_SIZE]:, 610 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
568 { 611 pub async fn mark_updated<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
569 let mut aligned = Aligned([0; { F::WRITE_SIZE }]); 612 assert_eq!(aligned.len(), F::WRITE_SIZE);
570 self.set_magic(&mut aligned.0, SWAP_MAGIC, flash).await 613 self.set_magic(aligned, SWAP_MAGIC, flash).await
571 } 614 }
572 615
573 /// Mark firmware boot successfully 616 /// Mark firmware boot successful and stop rollback on reset.
574 pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F) -> Result<(), F::Error> 617 ///
575 where 618 /// # Safety
576 [(); F::WRITE_SIZE]:, 619 ///
577 { 620 /// The `aligned` buffer must have a size of F::WRITE_SIZE, and follow the alignment rules for the flash being written to.
578 let mut aligned = Aligned([0; { F::WRITE_SIZE }]); 621 pub async fn mark_booted<F: AsyncNorFlash>(&mut self, flash: &mut F, aligned: &mut [u8]) -> Result<(), F::Error> {
579 self.set_magic(&mut aligned.0, BOOT_MAGIC, flash).await 622 assert_eq!(aligned.len(), F::WRITE_SIZE);
623 self.set_magic(aligned, BOOT_MAGIC, flash).await
580 } 624 }
581 625
582 async fn set_magic<F: AsyncNorFlash>( 626 async fn set_magic<F: AsyncNorFlash>(
@@ -587,7 +631,7 @@ impl FirmwareUpdater {
587 ) -> Result<(), F::Error> { 631 ) -> Result<(), F::Error> {
588 flash.read(self.state.from as u32, aligned).await?; 632 flash.read(self.state.from as u32, aligned).await?;
589 633
590 if aligned.iter().find(|&&b| b != magic).is_some() { 634 if aligned.iter().any(|&b| b != magic) {
591 aligned.fill(0); 635 aligned.fill(0);
592 636
593 flash.write(self.state.from as u32, aligned).await?; 637 flash.write(self.state.from as u32, aligned).await?;
@@ -599,7 +643,13 @@ impl FirmwareUpdater {
599 Ok(()) 643 Ok(())
600 } 644 }
601 645
602 // Write to a region of the DFU page 646 /// Write data to a flash page.
647 ///
648 /// The buffer must follow alignment requirements of the target flash and a multiple of page size big.
649 ///
650 /// # Safety
651 ///
652 /// Failing to meet alignment and size requirements may result in a panic.
603 pub async fn write_firmware<F: AsyncNorFlash>( 653 pub async fn write_firmware<F: AsyncNorFlash>(
604 &mut self, 654 &mut self,
605 offset: usize, 655 offset: usize,
@@ -668,7 +718,7 @@ mod tests {
668 #[test] 718 #[test]
669 fn test_bad_magic() { 719 fn test_bad_magic() {
670 let mut flash = MemFlash([0xff; 131072]); 720 let mut flash = MemFlash([0xff; 131072]);
671 let mut flash = SingleFlashProvider::new(&mut flash); 721 let mut flash = SingleFlashConfig::new(&mut flash);
672 722
673 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE); 723 let mut bootloader = BootLoader::<4096>::new(ACTIVE, DFU, STATE);
674 724
@@ -687,11 +737,16 @@ mod tests {
687 737
688 let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]); 738 let mut flash = MemFlash::<131072, 4096, 4>([0xff; 131072]);
689 flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]); 739 flash.0[0..4].copy_from_slice(&[BOOT_MAGIC; 4]);
690 let mut flash = SingleFlashProvider::new(&mut flash); 740 let mut flash = SingleFlashConfig::new(&mut flash);
691 741
692 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 742 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
693 743
694 assert_eq!(State::Boot, bootloader.prepare_boot(&mut flash).unwrap()); 744 let mut magic = [0; 4];
745 let mut page = [0; 4096];
746 assert_eq!(
747 State::Boot,
748 bootloader.prepare_boot(&mut flash, &mut magic, &mut page).unwrap()
749 );
695 } 750 }
696 751
697 #[test] 752 #[test]
@@ -703,24 +758,27 @@ mod tests {
703 758
704 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 759 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
705 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 760 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
761 let mut aligned = [0; 4];
706 762
707 for i in ACTIVE.from..ACTIVE.to { 763 for i in ACTIVE.from..ACTIVE.to {
708 flash.0[i] = original[i - ACTIVE.from]; 764 flash.0[i] = original[i - ACTIVE.from];
709 } 765 }
710 766
711 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 767 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
712 let mut updater = FirmwareUpdater::new(DFU, STATE); 768 let mut updater = FirmwareUpdater::new(DFU, STATE);
713 let mut offset = 0; 769 let mut offset = 0;
714 for chunk in update.chunks(4096) { 770 for chunk in update.chunks(4096) {
715 block_on(updater.write_firmware(offset, &chunk, &mut flash, 4096)).unwrap(); 771 block_on(updater.write_firmware(offset, chunk, &mut flash, 4096)).unwrap();
716 offset += chunk.len(); 772 offset += chunk.len();
717 } 773 }
718 block_on(updater.update(&mut flash)).unwrap(); 774 block_on(updater.mark_updated(&mut flash, &mut aligned)).unwrap();
719 775
776 let mut magic = [0; 4];
777 let mut page = [0; 4096];
720 assert_eq!( 778 assert_eq!(
721 State::Swap, 779 State::Swap,
722 bootloader 780 bootloader
723 .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) 781 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page)
724 .unwrap() 782 .unwrap()
725 ); 783 );
726 784
@@ -737,7 +795,7 @@ mod tests {
737 assert_eq!( 795 assert_eq!(
738 State::Swap, 796 State::Swap,
739 bootloader 797 bootloader
740 .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) 798 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page)
741 .unwrap() 799 .unwrap()
742 ); 800 );
743 801
@@ -751,11 +809,11 @@ mod tests {
751 } 809 }
752 810
753 // Mark as booted 811 // Mark as booted
754 block_on(updater.mark_booted(&mut flash)).unwrap(); 812 block_on(updater.mark_booted(&mut flash, &mut aligned)).unwrap();
755 assert_eq!( 813 assert_eq!(
756 State::Boot, 814 State::Boot,
757 bootloader 815 bootloader
758 .prepare_boot(&mut SingleFlashProvider::new(&mut flash)) 816 .prepare_boot(&mut SingleFlashConfig::new(&mut flash), &mut magic, &mut page)
759 .unwrap() 817 .unwrap()
760 ); 818 );
761 } 819 }
@@ -769,6 +827,7 @@ mod tests {
769 let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]); 827 let mut active = MemFlash::<16384, 4096, 8>([0xff; 16384]);
770 let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]); 828 let mut dfu = MemFlash::<16384, 2048, 8>([0xff; 16384]);
771 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); 829 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
830 let mut aligned = [0; 4];
772 831
773 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()]; 832 let original: [u8; ACTIVE.len()] = [rand::random::<u8>(); ACTIVE.len()];
774 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()]; 833 let update: [u8; DFU.len()] = [rand::random::<u8>(); DFU.len()];
@@ -781,16 +840,23 @@ mod tests {
781 840
782 let mut offset = 0; 841 let mut offset = 0;
783 for chunk in update.chunks(2048) { 842 for chunk in update.chunks(2048) {
784 block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); 843 block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap();
785 offset += chunk.len(); 844 offset += chunk.len();
786 } 845 }
787 block_on(updater.update(&mut state)).unwrap(); 846 block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
847
848 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
849 let mut magic = [0; 4];
850 let mut page = [0; 4096];
788 851
789 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE);
790 assert_eq!( 852 assert_eq!(
791 State::Swap, 853 State::Swap,
792 bootloader 854 bootloader
793 .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) 855 .prepare_boot(
856 &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu),
857 &mut magic,
858 &mut page
859 )
794 .unwrap() 860 .unwrap()
795 ); 861 );
796 862
@@ -810,6 +876,7 @@ mod tests {
810 const ACTIVE: Partition = Partition::new(4096, 16384); 876 const ACTIVE: Partition = Partition::new(4096, 16384);
811 const DFU: Partition = Partition::new(0, 16384); 877 const DFU: Partition = Partition::new(0, 16384);
812 878
879 let mut aligned = [0; 4];
813 let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]); 880 let mut active = MemFlash::<16384, 2048, 4>([0xff; 16384]);
814 let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]); 881 let mut dfu = MemFlash::<16384, 4096, 8>([0xff; 16384]);
815 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]); 882 let mut state = MemFlash::<4096, 128, 4>([0xff; 4096]);
@@ -825,16 +892,22 @@ mod tests {
825 892
826 let mut offset = 0; 893 let mut offset = 0;
827 for chunk in update.chunks(4096) { 894 for chunk in update.chunks(4096) {
828 block_on(updater.write_firmware(offset, &chunk, &mut dfu, chunk.len())).unwrap(); 895 block_on(updater.write_firmware(offset, chunk, &mut dfu, chunk.len())).unwrap();
829 offset += chunk.len(); 896 offset += chunk.len();
830 } 897 }
831 block_on(updater.update(&mut state)).unwrap(); 898 block_on(updater.mark_updated(&mut state, &mut aligned)).unwrap();
832 899
833 let mut bootloader: BootLoader<4096> = BootLoader::new(ACTIVE, DFU, STATE); 900 let mut bootloader: BootLoader = BootLoader::new(ACTIVE, DFU, STATE);
901 let mut magic = [0; 4];
902 let mut page = [0; 4096];
834 assert_eq!( 903 assert_eq!(
835 State::Swap, 904 State::Swap,
836 bootloader 905 bootloader
837 .prepare_boot(&mut MultiFlashProvider::new(&mut active, &mut state, &mut dfu,)) 906 .prepare_boot(
907 &mut MultiFlashConfig::new(&mut active, &mut state, &mut dfu,),
908 &mut magic,
909 &mut page
910 )
838 .unwrap() 911 .unwrap()
839 ); 912 );
840 913
@@ -899,6 +972,13 @@ mod tests {
899 } 972 }
900 } 973 }
901 974
975 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> super::Flash
976 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
977 {
978 const BLOCK_SIZE: usize = ERASE_SIZE;
979 const ERASE_VALUE: u8 = 0xFF;
980 }
981
902 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash 982 impl<const SIZE: usize, const ERASE_SIZE: usize, const WRITE_SIZE: usize> AsyncReadNorFlash
903 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE> 983 for MemFlash<SIZE, ERASE_SIZE, WRITE_SIZE>
904 { 984 {