aboutsummaryrefslogtreecommitdiff
path: root/embassy-boot/boot/src/boot_loader.rs
diff options
context:
space:
mode:
Diffstat (limited to 'embassy-boot/boot/src/boot_loader.rs')
-rw-r--r--embassy-boot/boot/src/boot_loader.rs239
1 files changed, 119 insertions, 120 deletions
diff --git a/embassy-boot/boot/src/boot_loader.rs b/embassy-boot/boot/src/boot_loader.rs
index 9d047f778..2412427c0 100644
--- a/embassy-boot/boot/src/boot_loader.rs
+++ b/embassy-boot/boot/src/boot_loader.rs
@@ -30,23 +30,16 @@ where
30 } 30 }
31} 31}
32 32
33/// Extension of the embedded-storage flash type information with block size and erase value. 33/// Trait defining the flash handles used for active and DFU partition.
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
36 /// size of the flash, but for external QSPI flash modules, this can be lower.
37 const BLOCK_SIZE: usize;
38 /// The erase value of the flash. Typically the default of 0xFF is used, but some flashes use a different value.
39 const ERASE_VALUE: u8 = 0xFF;
40}
41
42/// Trait defining the flash handles used for active and DFU partition
43pub trait FlashConfig { 34pub trait FlashConfig {
35 /// The erase value of the state flash. Typically the default of 0xFF is used, but some flashes use a different value.
36 const STATE_ERASE_VALUE: u8 = 0xFF;
44 /// Flash type used for the state partition. 37 /// Flash type used for the state partition.
45 type STATE: Flash; 38 type STATE: NorFlash;
46 /// Flash type used for the active partition. 39 /// Flash type used for the active partition.
47 type ACTIVE: Flash; 40 type ACTIVE: NorFlash;
48 /// Flash type used for the dfu partition. 41 /// Flash type used for the dfu partition.
49 type DFU: Flash; 42 type DFU: NorFlash;
50 43
51 /// Return flash instance used to write/read to/from active partition. 44 /// Return flash instance used to write/read to/from active partition.
52 fn active(&mut self) -> &mut Self::ACTIVE; 45 fn active(&mut self) -> &mut Self::ACTIVE;
@@ -56,8 +49,18 @@ pub trait FlashConfig {
56 fn state(&mut self) -> &mut Self::STATE; 49 fn state(&mut self) -> &mut Self::STATE;
57} 50}
58 51
59/// BootLoader works with any flash implementing embedded_storage and can also work with 52trait FlashConfigEx {
60/// different page sizes and flash write sizes. 53 fn page_size() -> usize;
54}
55
56impl<T: FlashConfig> FlashConfigEx for T {
57 /// Get the page size which is the "unit of operation" within the bootloader.
58 fn page_size() -> usize {
59 core::cmp::max(T::ACTIVE::ERASE_SIZE, T::DFU::ERASE_SIZE)
60 }
61}
62
63/// BootLoader works with any flash implementing embedded_storage.
61pub struct BootLoader { 64pub struct BootLoader {
62 // Page with current state of bootloader. The state partition has the following format: 65 // Page with current state of bootloader. The state partition has the following format:
63 // All ranges are in multiples of WRITE_SIZE bytes. 66 // All ranges are in multiples of WRITE_SIZE bytes.
@@ -91,6 +94,9 @@ impl BootLoader {
91 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap 94 /// The DFU partition is assumed to be 1 page bigger than the active partition for the swap
92 /// algorithm to work correctly. 95 /// algorithm to work correctly.
93 /// 96 ///
97 /// The provided aligned_buf argument must satisfy any alignment requirements
98 /// given by the partition flashes. All flash operations will use this buffer.
99 ///
94 /// SWAPPING 100 /// SWAPPING
95 /// 101 ///
96 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition. 102 /// Assume a flash size of 3 pages for the active partition, and 4 pages for the DFU partition.
@@ -169,87 +175,95 @@ impl BootLoader {
169 /// | DFU | 3 | 3 | 2 | 1 | 3 | 175 /// | DFU | 3 | 3 | 2 | 1 | 3 |
170 /// +-----------+--------------+--------+--------+--------+--------+ 176 /// +-----------+--------------+--------+--------+--------+--------+
171 /// 177 ///
172 pub fn prepare_boot<P: FlashConfig>( 178 pub fn prepare_boot<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
173 &mut self,
174 p: &mut P,
175 magic: &mut [u8],
176 page: &mut [u8],
177 ) -> Result<State, BootError> {
178 // Ensure we have enough progress pages to store copy progress 179 // Ensure we have enough progress pages to store copy progress
179 assert_partitions(self.active, self.dfu, self.state, page.len(), P::STATE::WRITE_SIZE); 180 assert_eq!(0, P::page_size() % aligned_buf.len());
180 assert_eq!(magic.len(), P::STATE::WRITE_SIZE); 181 assert_eq!(0, P::page_size() % P::ACTIVE::WRITE_SIZE);
182 assert_eq!(0, P::page_size() % P::ACTIVE::ERASE_SIZE);
183 assert_eq!(0, P::page_size() % P::DFU::WRITE_SIZE);
184 assert_eq!(0, P::page_size() % P::DFU::ERASE_SIZE);
185 assert!(aligned_buf.len() >= P::STATE::WRITE_SIZE);
186 assert_eq!(0, aligned_buf.len() % P::ACTIVE::WRITE_SIZE);
187 assert_eq!(0, aligned_buf.len() % P::DFU::WRITE_SIZE);
188 assert_partitions(self.active, self.dfu, self.state, P::page_size(), P::STATE::WRITE_SIZE);
181 189
182 // Copy contents from partition N to active 190 // Copy contents from partition N to active
183 let state = self.read_state(p, magic)?; 191 let state = self.read_state(p, aligned_buf)?;
184 if state == State::Swap { 192 if state == State::Swap {
185 // 193 //
186 // Check if we already swapped. If we're in the swap state, this means we should revert 194 // Check if we already swapped. If we're in the swap state, this means we should revert
187 // since the app has failed to mark boot as successful 195 // since the app has failed to mark boot as successful
188 // 196 //
189 if !self.is_swapped(p, magic, page)? { 197 if !self.is_swapped(p, aligned_buf)? {
190 trace!("Swapping"); 198 trace!("Swapping");
191 self.swap(p, magic, page)?; 199 self.swap(p, aligned_buf)?;
192 trace!("Swapping done"); 200 trace!("Swapping done");
193 } else { 201 } else {
194 trace!("Reverting"); 202 trace!("Reverting");
195 self.revert(p, magic, page)?; 203 self.revert(p, aligned_buf)?;
196 204
197 let state_flash = p.state(); 205 let state_flash = p.state();
206 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
198 207
199 // Invalidate progress 208 // Invalidate progress
200 magic.fill(!P::STATE::ERASE_VALUE); 209 state_word.fill(!P::STATE_ERASE_VALUE);
201 self.state 210 self.state
202 .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, magic)?; 211 .write_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
203 212
204 // Clear magic and progress 213 // Clear magic and progress
205 self.state.wipe_blocking(state_flash)?; 214 self.state.wipe_blocking(state_flash)?;
206 215
207 // Set magic 216 // Set magic
208 magic.fill(BOOT_MAGIC); 217 state_word.fill(BOOT_MAGIC);
209 self.state.write_blocking(state_flash, 0, magic)?; 218 self.state.write_blocking(state_flash, 0, state_word)?;
210 } 219 }
211 } 220 }
212 Ok(state) 221 Ok(state)
213 } 222 }
214 223
215 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<bool, BootError> { 224 fn is_swapped<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<bool, BootError> {
216 let page_size = page.len(); 225 let page_count = self.active.len() / P::page_size();
217 let page_count = self.active.len() / page_size; 226 let progress = self.current_progress(p, aligned_buf)?;
218 let progress = self.current_progress(p, magic)?;
219 227
220 Ok(progress >= page_count * 2) 228 Ok(progress >= page_count * 2)
221 } 229 }
222 230
223 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned: &mut [u8]) -> Result<usize, BootError> { 231 fn current_progress<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<usize, BootError> {
224 let write_size = aligned.len(); 232 let max_index = ((self.state.len() - P::STATE::WRITE_SIZE) / P::STATE::WRITE_SIZE) - 2;
225 let max_index = ((self.state.len() - write_size) / write_size) - 2;
226 aligned.fill(!P::STATE::ERASE_VALUE);
227
228 let state_flash = config.state(); 233 let state_flash = config.state();
234 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
229 235
230 self.state 236 self.state
231 .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, aligned)?; 237 .read_blocking(state_flash, P::STATE::WRITE_SIZE as u32, state_word)?;
232 if aligned.iter().any(|&b| b != P::STATE::ERASE_VALUE) { 238 if state_word.iter().any(|&b| b != P::STATE_ERASE_VALUE) {
233 // Progress is invalid 239 // Progress is invalid
234 return Ok(max_index); 240 return Ok(max_index);
235 } 241 }
236 242
237 for index in 0..max_index { 243 for index in 0..max_index {
238 self.state 244 self.state.read_blocking(
239 .read_blocking(state_flash, (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?; 245 state_flash,
246 (2 + index) as u32 * P::STATE::WRITE_SIZE as u32,
247 state_word,
248 )?;
240 249
241 if aligned.iter().any(|&b| b == P::STATE::ERASE_VALUE) { 250 if state_word.iter().any(|&b| b == P::STATE_ERASE_VALUE) {
242 return Ok(index); 251 return Ok(index);
243 } 252 }
244 } 253 }
245 Ok(max_index) 254 Ok(max_index)
246 } 255 }
247 256
248 fn update_progress<P: FlashConfig>(&mut self, index: usize, p: &mut P, magic: &mut [u8]) -> Result<(), BootError> { 257 fn update_progress<P: FlashConfig>(
249 let aligned = magic; 258 &mut self,
250 aligned.fill(!P::STATE::ERASE_VALUE); 259 index: usize,
260 p: &mut P,
261 aligned_buf: &mut [u8],
262 ) -> Result<(), BootError> {
263 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
264 state_word.fill(!P::STATE_ERASE_VALUE);
251 self.state 265 self.state
252 .write_blocking(p.state(), (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, aligned)?; 266 .write_blocking(p.state(), (2 + index) as u32 * P::STATE::WRITE_SIZE as u32, state_word)?;
253 Ok(()) 267 Ok(())
254 } 268 }
255 269
@@ -259,26 +273,22 @@ impl BootLoader {
259 from_offset: u32, 273 from_offset: u32,
260 to_offset: u32, 274 to_offset: u32,
261 p: &mut P, 275 p: &mut P,
262 magic: &mut [u8], 276 aligned_buf: &mut [u8],
263 page: &mut [u8],
264 ) -> Result<(), BootError> { 277 ) -> Result<(), BootError> {
265 let buf = page; 278 if self.current_progress(p, aligned_buf)? <= idx {
266 if self.current_progress(p, magic)? <= idx { 279 let page_size = P::page_size() as u32;
267 let mut offset = from_offset;
268 for chunk in buf.chunks_mut(P::DFU::BLOCK_SIZE) {
269 self.dfu.read_blocking(p.dfu(), offset, chunk)?;
270 offset += chunk.len() as u32;
271 }
272 280
273 self.active 281 self.active
274 .erase_blocking(p.active(), to_offset, to_offset + buf.len() as u32)?; 282 .erase_blocking(p.active(), to_offset, to_offset + page_size)?;
275 283
276 let mut offset = to_offset; 284 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
277 for chunk in buf.chunks(P::ACTIVE::BLOCK_SIZE) { 285 self.dfu
278 self.active.write_blocking(p.active(), offset, chunk)?; 286 .read_blocking(p.dfu(), from_offset + offset_in_page as u32, aligned_buf)?;
279 offset += chunk.len() as u32; 287 self.active
288 .write_blocking(p.active(), to_offset + offset_in_page as u32, aligned_buf)?;
280 } 289 }
281 self.update_progress(idx, p, magic)?; 290
291 self.update_progress(idx, p, aligned_buf)?;
282 } 292 }
283 Ok(()) 293 Ok(())
284 } 294 }
@@ -289,32 +299,28 @@ impl BootLoader {
289 from_offset: u32, 299 from_offset: u32,
290 to_offset: u32, 300 to_offset: u32,
291 p: &mut P, 301 p: &mut P,
292 magic: &mut [u8], 302 aligned_buf: &mut [u8],
293 page: &mut [u8],
294 ) -> Result<(), BootError> { 303 ) -> Result<(), BootError> {
295 let buf = page; 304 if self.current_progress(p, aligned_buf)? <= idx {
296 if self.current_progress(p, magic)? <= idx { 305 let page_size = P::page_size() as u32;
297 let mut offset = from_offset;
298 for chunk in buf.chunks_mut(P::ACTIVE::BLOCK_SIZE) {
299 self.active.read_blocking(p.active(), offset, chunk)?;
300 offset += chunk.len() as u32;
301 }
302 306
303 self.dfu 307 self.dfu
304 .erase_blocking(p.dfu(), to_offset as u32, to_offset + buf.len() as u32)?; 308 .erase_blocking(p.dfu(), to_offset as u32, to_offset + page_size)?;
305 309
306 let mut offset = to_offset; 310 for offset_in_page in (0..page_size).step_by(aligned_buf.len()) {
307 for chunk in buf.chunks(P::DFU::BLOCK_SIZE) { 311 self.active
308 self.dfu.write_blocking(p.dfu(), offset, chunk)?; 312 .read_blocking(p.active(), from_offset + offset_in_page as u32, aligned_buf)?;
309 offset += chunk.len() as u32; 313 self.dfu
314 .write_blocking(p.dfu(), to_offset + offset_in_page as u32, aligned_buf)?;
310 } 315 }
311 self.update_progress(idx, p, magic)?; 316
317 self.update_progress(idx, p, aligned_buf)?;
312 } 318 }
313 Ok(()) 319 Ok(())
314 } 320 }
315 321
316 fn swap<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { 322 fn swap<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
317 let page_size = page.len(); 323 let page_size = P::page_size();
318 let page_count = self.active.len() / page_size; 324 let page_count = self.active.len() / page_size;
319 trace!("Page count: {}", page_count); 325 trace!("Page count: {}", page_count);
320 for page_num in 0..page_count { 326 for page_num in 0..page_count {
@@ -326,20 +332,20 @@ impl BootLoader {
326 let active_from_offset = ((page_count - 1 - page_num) * page_size) as u32; 332 let active_from_offset = ((page_count - 1 - page_num) * page_size) as u32;
327 let dfu_to_offset = ((page_count - page_num) * page_size) as u32; 333 let dfu_to_offset = ((page_count - page_num) * page_size) as u32;
328 //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset); 334 //trace!("Copy active {} to dfu {}", active_from_offset, dfu_to_offset);
329 self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, magic, page)?; 335 self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, aligned_buf)?;
330 336
331 // Copy DFU page to the active page 337 // Copy DFU page to the active page
332 let active_to_offset = ((page_count - 1 - page_num) * page_size) as u32; 338 let active_to_offset = ((page_count - 1 - page_num) * page_size) as u32;
333 let dfu_from_offset = ((page_count - 1 - page_num) * page_size) as u32; 339 let dfu_from_offset = ((page_count - 1 - page_num) * page_size) as u32;
334 //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset); 340 //trace!("Copy dfy {} to active {}", dfu_from_offset, active_to_offset);
335 self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, magic, page)?; 341 self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
336 } 342 }
337 343
338 Ok(()) 344 Ok(())
339 } 345 }
340 346
341 fn revert<P: FlashConfig>(&mut self, p: &mut P, magic: &mut [u8], page: &mut [u8]) -> Result<(), BootError> { 347 fn revert<P: FlashConfig>(&mut self, p: &mut P, aligned_buf: &mut [u8]) -> Result<(), BootError> {
342 let page_size = page.len(); 348 let page_size = P::page_size();
343 let page_count = self.active.len() / page_size; 349 let page_count = self.active.len() / page_size;
344 for page_num in 0..page_count { 350 for page_num in 0..page_count {
345 let idx = page_count * 2 + page_num * 2; 351 let idx = page_count * 2 + page_num * 2;
@@ -347,21 +353,22 @@ impl BootLoader {
347 // Copy the bad active page to the DFU page 353 // Copy the bad active page to the DFU page
348 let active_from_offset = (page_num * page_size) as u32; 354 let active_from_offset = (page_num * page_size) as u32;
349 let dfu_to_offset = (page_num * page_size) as u32; 355 let dfu_to_offset = (page_num * page_size) as u32;
350 self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, magic, page)?; 356 self.copy_page_once_to_dfu(idx, active_from_offset, dfu_to_offset, p, aligned_buf)?;
351 357
352 // Copy the DFU page back to the active page 358 // Copy the DFU page back to the active page
353 let active_to_offset = (page_num * page_size) as u32; 359 let active_to_offset = (page_num * page_size) as u32;
354 let dfu_from_offset = ((page_num + 1) * page_size) as u32; 360 let dfu_from_offset = ((page_num + 1) * page_size) as u32;
355 self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, magic, page)?; 361 self.copy_page_once_to_active(idx + 1, dfu_from_offset, active_to_offset, p, aligned_buf)?;
356 } 362 }
357 363
358 Ok(()) 364 Ok(())
359 } 365 }
360 366
361 fn read_state<P: FlashConfig>(&mut self, config: &mut P, magic: &mut [u8]) -> Result<State, BootError> { 367 fn read_state<P: FlashConfig>(&mut self, config: &mut P, aligned_buf: &mut [u8]) -> Result<State, BootError> {
362 self.state.read_blocking(config.state(), 0, magic)?; 368 let state_word = &mut aligned_buf[..P::STATE::WRITE_SIZE];
369 self.state.read_blocking(config.state(), 0, state_word)?;
363 370
364 if !magic.iter().any(|&b| b != SWAP_MAGIC) { 371 if !state_word.iter().any(|&b| b != SWAP_MAGIC) {
365 Ok(State::Swap) 372 Ok(State::Swap)
366 } else { 373 } else {
367 Ok(State::Boot) 374 Ok(State::Boot)
@@ -377,16 +384,16 @@ fn assert_partitions(active: Partition, dfu: Partition, state: Partition, page_s
377} 384}
378 385
379/// A flash wrapper implementing the Flash and embedded_storage traits. 386/// A flash wrapper implementing the Flash and embedded_storage traits.
380pub struct BootFlash<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8 = 0xFF> 387pub struct BootFlash<F>
381where 388where
382 F: NorFlash + ReadNorFlash, 389 F: NorFlash,
383{ 390{
384 flash: F, 391 flash: F,
385} 392}
386 393
387impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> BootFlash<F, BLOCK_SIZE, ERASE_VALUE> 394impl<F> BootFlash<F>
388where 395where
389 F: NorFlash + ReadNorFlash, 396 F: NorFlash,
390{ 397{
391 /// Create a new instance of a bootable flash 398 /// Create a new instance of a bootable flash
392 pub fn new(flash: F) -> Self { 399 pub fn new(flash: F) -> Self {
@@ -394,24 +401,16 @@ where
394 } 401 }
395} 402}
396 403
397impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> Flash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> 404impl<F> ErrorType for BootFlash<F>
398where
399 F: NorFlash + ReadNorFlash,
400{
401 const BLOCK_SIZE: usize = BLOCK_SIZE;
402 const ERASE_VALUE: u8 = ERASE_VALUE;
403}
404
405impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ErrorType for BootFlash<F, BLOCK_SIZE, ERASE_VALUE>
406where 405where
407 F: ReadNorFlash + NorFlash, 406 F: NorFlash,
408{ 407{
409 type Error = F::Error; 408 type Error = F::Error;
410} 409}
411 410
412impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> NorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> 411impl<F> NorFlash for BootFlash<F>
413where 412where
414 F: ReadNorFlash + NorFlash, 413 F: NorFlash,
415{ 414{
416 const WRITE_SIZE: usize = F::WRITE_SIZE; 415 const WRITE_SIZE: usize = F::WRITE_SIZE;
417 const ERASE_SIZE: usize = F::ERASE_SIZE; 416 const ERASE_SIZE: usize = F::ERASE_SIZE;
@@ -425,9 +424,9 @@ where
425 } 424 }
426} 425}
427 426
428impl<F, const BLOCK_SIZE: usize, const ERASE_VALUE: u8> ReadNorFlash for BootFlash<F, BLOCK_SIZE, ERASE_VALUE> 427impl<F> ReadNorFlash for BootFlash<F>
429where 428where
430 F: ReadNorFlash + NorFlash, 429 F: NorFlash,
431{ 430{
432 const READ_SIZE: usize = F::READ_SIZE; 431 const READ_SIZE: usize = F::READ_SIZE;
433 432
@@ -443,14 +442,14 @@ where
443/// Convenience provider that uses a single flash for all partitions. 442/// Convenience provider that uses a single flash for all partitions.
444pub struct SingleFlashConfig<'a, F> 443pub struct SingleFlashConfig<'a, F>
445where 444where
446 F: Flash, 445 F: NorFlash,
447{ 446{
448 flash: &'a mut F, 447 flash: &'a mut F,
449} 448}
450 449
451impl<'a, F> SingleFlashConfig<'a, F> 450impl<'a, F> SingleFlashConfig<'a, F>
452where 451where
453 F: Flash, 452 F: NorFlash,
454{ 453{
455 /// Create a provider for a single flash. 454 /// Create a provider for a single flash.
456 pub fn new(flash: &'a mut F) -> Self { 455 pub fn new(flash: &'a mut F) -> Self {
@@ -460,7 +459,7 @@ where
460 459
461impl<'a, F> FlashConfig for SingleFlashConfig<'a, F> 460impl<'a, F> FlashConfig for SingleFlashConfig<'a, F>
462where 461where
463 F: Flash, 462 F: NorFlash,
464{ 463{
465 type STATE = F; 464 type STATE = F;
466 type ACTIVE = F; 465 type ACTIVE = F;
@@ -480,9 +479,9 @@ where
480/// Convenience flash provider that uses separate flash instances for each partition. 479/// Convenience flash provider that uses separate flash instances for each partition.
481pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU> 480pub struct MultiFlashConfig<'a, ACTIVE, STATE, DFU>
482where 481where
483 ACTIVE: Flash, 482 ACTIVE: NorFlash,
484 STATE: Flash, 483 STATE: NorFlash,
485 DFU: Flash, 484 DFU: NorFlash,
486{ 485{
487 active: &'a mut ACTIVE, 486 active: &'a mut ACTIVE,
488 state: &'a mut STATE, 487 state: &'a mut STATE,
@@ -491,9 +490,9 @@ where
491 490
492impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU> 491impl<'a, ACTIVE, STATE, DFU> MultiFlashConfig<'a, ACTIVE, STATE, DFU>
493where 492where
494 ACTIVE: Flash, 493 ACTIVE: NorFlash,
495 STATE: Flash, 494 STATE: NorFlash,
496 DFU: Flash, 495 DFU: NorFlash,
497{ 496{
498 /// Create a new flash provider with separate configuration for all three partitions. 497 /// Create a new flash provider with separate configuration for all three partitions.
499 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self { 498 pub fn new(active: &'a mut ACTIVE, state: &'a mut STATE, dfu: &'a mut DFU) -> Self {
@@ -503,9 +502,9 @@ where
503 502
504impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU> 503impl<'a, ACTIVE, STATE, DFU> FlashConfig for MultiFlashConfig<'a, ACTIVE, STATE, DFU>
505where 504where
506 ACTIVE: Flash, 505 ACTIVE: NorFlash,
507 STATE: Flash, 506 STATE: NorFlash,
508 DFU: Flash, 507 DFU: NorFlash,
509{ 508{
510 type STATE = STATE; 509 type STATE = STATE;
511 type ACTIVE = ACTIVE; 510 type ACTIVE = ACTIVE;