Skip to main content

probe_rs/flashing/
flash_algorithm.rs

1use super::FlashError;
2use crate::{Target, architecture::riscv, core::Architecture};
3use probe_rs_target::{
4    CoreType, Endian, FlashProperties, MemoryRegion, PageInfo, RamRegion, RawFlashAlgorithm,
5    RegionMergeIterator, SectorInfo, TransferEncoding,
6};
7use std::collections::BTreeMap;
8use std::mem::size_of_val;
9
10/// A flash algorithm, which has been assembled for a specific
11/// chip.
12///
13/// To create a [FlashAlgorithm], call the [`assemble_from_raw`] function.
14///
15/// [`assemble_from_raw`]: FlashAlgorithm::assemble_from_raw
16#[derive(Debug, Default, Clone)]
17pub struct FlashAlgorithm {
18    /// The name of the flash algorithm.
19    pub name: String,
20    /// Whether this flash algorithm is the default one or not.
21    pub default: bool,
22    /// Memory address where the flash algo instructions will be loaded to.
23    pub load_address: u64,
24    /// List of 32-bit words containing the position-independent code for the algo.
25    pub instructions: Vec<u32>,
26    /// Address of the `Init()` entry point. Optional.
27    pub pc_init: Option<u64>,
28    /// Address of the `UnInit()` entry point. Optional.
29    pub pc_uninit: Option<u64>,
30    /// Address of the `ProgramPage()` entry point.
31    pub pc_program_page: u64,
32    /// Address of the `EraseSector()` entry point.
33    pub pc_erase_sector: u64,
34    /// Address of the `EraseAll()` entry point. Optional.
35    pub pc_erase_all: Option<u64>,
36    /// Address of the `Verify()` entry point. Optional.
37    pub pc_verify: Option<u64>,
38    /// Address of the `BlankCheck()` entry point. Optional.
39    pub pc_blank_check: Option<u64>,
40    /// Address of the (non-standard) `ReadFlash()` entry point. Optional.
41    pub pc_read: Option<u64>,
42    /// Names and absolute addresses of optional, vendor-specific entry points.
43    pub vendor_functions: BTreeMap<String, u64>,
44    /// Initial value of the R9 register for calling flash algo entry points, which
45    /// determines where the position-independent data resides.
46    pub static_base: u64,
47    /// Initial value of the stack pointer when calling any flash algo API.
48    pub stack_top: u64,
49    /// The size of the stack in bytes.
50    pub stack_size: u64,
51    /// Whether to check for stack overflows.
52    pub stack_overflow_check: bool,
53    /// A list of base addresses for page buffers. The buffers must be at
54    /// least as large as the region's `page_size` attribute. If at least 2 buffers are included in
55    /// the list, then double buffered programming will be enabled.
56    pub page_buffers: Vec<u64>,
57
58    /// Location of optional RTT control block.
59    ///
60    /// If this is present, the flash algorithm supports debug output over RTT.
61    pub rtt_control_block: Option<u64>,
62
63    /// Milliseconds between RTT polls.
64    pub rtt_poll_interval: u64,
65
66    /// The properties of the flash on the device.
67    pub flash_properties: FlashProperties,
68
69    /// The encoding format accepted by the flash algorithm.
70    pub transfer_encoding: TransferEncoding,
71}
72
73impl FlashAlgorithm {
74    /// Try to retrieve the information about the flash sector
75    /// which contains `address`.
76    ///
77    /// If the `address` is not part of the flash, None will
78    /// be returned.
79    pub fn sector_info(&self, address: u64) -> Option<SectorInfo> {
80        if !self.flash_properties.address_range.contains(&address) {
81            tracing::trace!("Address {:08x} not contained in this flash device", address);
82            return None;
83        }
84
85        let offset_address = address - self.flash_properties.address_range.start;
86
87        let containing_sector = self
88            .flash_properties
89            .sectors
90            .iter()
91            .rfind(|s| s.address <= offset_address)?;
92
93        let sector_index = (offset_address - containing_sector.address) / containing_sector.size;
94
95        let sector_address = self.flash_properties.address_range.start
96            + containing_sector.address
97            + sector_index * containing_sector.size;
98
99        Some(SectorInfo {
100            base_address: sector_address,
101            size: containing_sector.size,
102        })
103    }
104
105    /// Returns the necessary information about the page which `address` resides in
106    /// if the address is inside the flash region.
107    pub fn page_info(&self, address: u64) -> Option<PageInfo> {
108        if !self.flash_properties.address_range.contains(&address) {
109            return None;
110        }
111
112        Some(PageInfo {
113            base_address: address - (address % self.flash_properties.page_size as u64),
114            size: self.flash_properties.page_size,
115        })
116    }
117
118    /// Iterate over all the sectors of the flash.
119    pub fn iter_sectors(&self) -> impl Iterator<Item = SectorInfo> + '_ {
120        let props = &self.flash_properties;
121
122        assert!(!props.sectors.is_empty());
123        assert!(props.sectors[0].address == 0);
124
125        let mut addr = props.address_range.start;
126        let mut desc_idx = 0;
127        std::iter::from_fn(move || {
128            if addr >= props.address_range.end {
129                return None;
130            }
131
132            // Advance desc_idx if needed
133            if let Some(next_desc) = props.sectors.get(desc_idx + 1)
134                && props.address_range.start + next_desc.address <= addr
135            {
136                desc_idx += 1;
137            }
138
139            let size = props.sectors[desc_idx].size;
140            let sector = SectorInfo {
141                base_address: addr,
142                size,
143            };
144            addr += size;
145
146            Some(sector)
147        })
148    }
149
150    /// Iterate over all the pages of the flash.
151    pub fn iter_pages(&self) -> impl Iterator<Item = PageInfo> + '_ {
152        let props = &self.flash_properties;
153
154        let mut addr = props.address_range.start;
155        std::iter::from_fn(move || {
156            if addr >= props.address_range.end {
157                return None;
158            }
159
160            let page = PageInfo {
161                base_address: addr,
162                size: props.page_size,
163            };
164            addr += props.page_size as u64;
165
166            Some(page)
167        })
168    }
169
170    /// Returns true if the entire contents of the argument array equal the erased byte value.
171    pub fn is_erased(&self, data: &[u8]) -> bool {
172        for b in data {
173            if *b != self.flash_properties.erased_byte_value {
174                return false;
175            }
176        }
177        true
178    }
179
180    const FLASH_ALGO_STACK_SIZE: u32 = 512;
181
182    // Header for RISC-V Flash Algorithms
183    const RISCV_FLASH_BLOB_HEADER: [u32; 2] = [riscv::assembly::EBREAK, riscv::assembly::EBREAK];
184
185    /// ARM breakpoint instruction (x2)
186    const ARM_ASSEMBLY_BKPT_T32: u32 = 0xBE00_BE00;
187    const ARM_ASSEMBLY_BKPT_A32: u32 = 0xE1200070;
188    /// ARM hlt instruction, Thumb2 (x2)
189    const ARM_ASSEMBLY_HLT: u32 = 0xBA80_BA80;
190
191    // On ARMv8-A and -R, `BKPT` does not enter debug state, but the debug exception,
192    // so use `HLT`.
193    // For ARMv7 and ARMv8-M, `HLT` does not exist.
194    const ARM_FLASH_BLOB_HEADER_BKPT_T32_LE: [u32; 1] = [Self::ARM_ASSEMBLY_BKPT_T32];
195    const ARM_FLASH_BLOB_HEADER_BKPT_T32_BE: [u32; 1] = [Self::ARM_ASSEMBLY_BKPT_T32.swap_bytes()];
196    const ARM_FLASH_BLOB_HEADER_BKPT_A32_LE: [u32; 1] = [Self::ARM_ASSEMBLY_BKPT_A32];
197    const ARM_FLASH_BLOB_HEADER_BKPT_A32_BE: [u32; 1] = [Self::ARM_ASSEMBLY_BKPT_A32.swap_bytes()];
198    const ARM_FLASH_BLOB_HEADER_HLT_LE: [u32; 1] = [Self::ARM_ASSEMBLY_HLT];
199    const ARM_FLASH_BLOB_HEADER_HLT_BE: [u32; 1] = [Self::ARM_ASSEMBLY_HLT.swap_bytes()];
200
201    const XTENSA_FLASH_BLOB_HEADER: [u32; 0] = [];
202
203    /// When the target architecture is not known, and we need to allocate space for the header,
204    /// this function returns the maximum size of the header of supported architectures.
205    pub fn get_max_algorithm_header_size() -> u64 {
206        let algos = [
207            Self::algorithm_header(CoreType::Armv6m, Endian::Big),
208            Self::algorithm_header(CoreType::Armv6m, Endian::Little),
209            Self::algorithm_header(CoreType::Armv7a, Endian::Big),
210            Self::algorithm_header(CoreType::Armv7a, Endian::Little),
211            Self::algorithm_header(CoreType::Armv7m, Endian::Big),
212            Self::algorithm_header(CoreType::Armv7m, Endian::Little),
213            Self::algorithm_header(CoreType::Armv7em, Endian::Big),
214            Self::algorithm_header(CoreType::Armv7em, Endian::Little),
215            Self::algorithm_header(CoreType::Armv8a, Endian::Big),
216            Self::algorithm_header(CoreType::Armv8a, Endian::Little),
217            Self::algorithm_header(CoreType::Armv8a, Endian::Big),
218            Self::algorithm_header(CoreType::Armv8a, Endian::Little),
219            Self::algorithm_header(CoreType::Armv8m, Endian::Big),
220            Self::algorithm_header(CoreType::Armv8m, Endian::Little),
221            Self::algorithm_header(CoreType::Riscv, Endian::Little),
222            Self::algorithm_header(CoreType::Riscv64, Endian::Little),
223            Self::algorithm_header(CoreType::Xtensa, Endian::Big),
224            Self::algorithm_header(CoreType::Xtensa, Endian::Little),
225        ];
226
227        algos.iter().copied().map(size_of_val).max().unwrap() as u64
228    }
229
230    fn algorithm_header(core_type: CoreType, endian: Endian) -> &'static [u32] {
231        match core_type {
232            CoreType::Armv6m | CoreType::Armv7m | CoreType::Armv7em | CoreType::Armv8m => {
233                match endian {
234                    Endian::Little => &Self::ARM_FLASH_BLOB_HEADER_BKPT_T32_LE,
235                    Endian::Big => &Self::ARM_FLASH_BLOB_HEADER_BKPT_T32_BE,
236                }
237            }
238            CoreType::Armv7a | CoreType::Armv7r => match endian {
239                Endian::Little => &Self::ARM_FLASH_BLOB_HEADER_BKPT_A32_LE,
240                Endian::Big => &Self::ARM_FLASH_BLOB_HEADER_BKPT_A32_BE,
241            },
242            CoreType::Armv8a => match endian {
243                Endian::Little => &Self::ARM_FLASH_BLOB_HEADER_HLT_LE,
244                Endian::Big => &Self::ARM_FLASH_BLOB_HEADER_HLT_BE,
245            },
246            CoreType::Riscv | CoreType::Riscv64 => &Self::RISCV_FLASH_BLOB_HEADER,
247            CoreType::Xtensa => &Self::XTENSA_FLASH_BLOB_HEADER,
248        }
249    }
250
251    fn required_stack_alignment(architecture: Architecture) -> u64 {
252        match architecture {
253            Architecture::Arm => 8,
254            Architecture::Riscv => 16,
255            Architecture::Xtensa => 16,
256        }
257    }
258
259    /// Constructs a complete flash algorithm, tailored to the flash and RAM sizes given.
260    pub fn assemble_from_raw(
261        raw: &RawFlashAlgorithm,
262        ram_region: &RamRegion,
263        target: &Target,
264    ) -> Result<Self, FlashError> {
265        Self::assemble_from_raw_with_data(raw, ram_region, ram_region, target)
266    }
267
268    /// Constructs a complete flash algorithm, tailored to the flash and RAM sizes given.
269    pub fn assemble_from_raw_with_data(
270        raw: &RawFlashAlgorithm,
271        ram_region: &RamRegion,
272        data_ram_region: &RamRegion,
273        target: &Target,
274    ) -> Result<Self, FlashError> {
275        use std::mem::size_of;
276
277        let assembled_instructions = raw.instructions.chunks_exact(size_of::<u32>());
278
279        let remainder = assembled_instructions.remainder();
280        let last_elem = if !remainder.is_empty() {
281            let word = u32::from_le_bytes(
282                remainder
283                    .iter()
284                    .cloned()
285                    // Pad with up to three bytes
286                    .chain([0u8, 0u8, 0u8])
287                    .take(4)
288                    .collect::<Vec<u8>>()
289                    .try_into()
290                    .unwrap(),
291            );
292            Some(word)
293        } else {
294            None
295        };
296
297        let header = Self::algorithm_header(
298            target.default_core().core_type,
299            if raw.big_endian {
300                Endian::Big
301            } else {
302                Endian::Little
303            },
304        );
305
306        let instructions: Vec<u32> = header
307            .iter()
308            .copied()
309            .chain(
310                assembled_instructions.map(|bytes| u32::from_le_bytes(bytes.try_into().unwrap())),
311            )
312            .chain(last_elem)
313            .collect();
314
315        let header_size = size_of_val(header) as u64;
316
317        // The start address where we try to load the flash algorithm.
318        let addr_load = match raw.load_address {
319            Some(address) => {
320                // adjust the raw load address to account for the algo header
321                address
322                    .checked_sub(header_size)
323                    .ok_or(FlashError::InvalidFlashAlgorithmLoadAddress { address })?
324            }
325
326            None => {
327                // assume position independent code
328                ram_region.range.start
329            }
330        };
331
332        if addr_load < ram_region.range.start {
333            return Err(FlashError::InvalidFlashAlgorithmLoadAddress { address: addr_load });
334        }
335
336        // Memory layout:
337        // - Header
338        // - Code
339        // - Data
340        // - Stack
341        // Stack placement depends on the optional `data_load_address` field. If the stack fits
342        // between the code and the data, it will be placed there. Otherwise, it will be placed
343        // after the data.
344
345        let code_start = addr_load + header_size;
346        let code_size_bytes = (instructions.len() * size_of::<u32>()) as u64;
347
348        let stack_align = Self::required_stack_alignment(target.architecture());
349        // Round up to align the stack (possibly placed immediately after the code blob).
350        let code_end = (code_start + code_size_bytes).next_multiple_of(stack_align);
351
352        let buffer_page_size = raw.flash_properties.page_size as u64;
353
354        let stack_size = raw.stack_size.unwrap_or(Self::FLASH_ALGO_STACK_SIZE) as u64;
355        tracing::info!("The flash algorithm will be configured with {stack_size} bytes of stack");
356
357        let data_load_addr = if let Some(data_load_addr) = raw.data_load_address {
358            data_load_addr
359        } else if ram_region == data_ram_region {
360            // The data is not placed explicitly. We can place it after the code.
361            code_end
362        } else {
363            // The data is not placed explicitly. We can place it to the start of the memory region.
364            data_ram_region.range.start
365        };
366
367        // Available memory for data depends on where the stack needs to be placed.
368        if data_ram_region.range.end < data_load_addr {
369            return Err(FlashError::InvalidDataAddress {
370                data_load_addr,
371                data_ram: data_ram_region.range.clone(),
372            });
373        }
374        let mut ram_for_data = data_ram_region.range.end - data_load_addr;
375        if code_end + stack_size > data_load_addr && ram_region == data_ram_region {
376            // Stack can only go after the data, so let's reduce the available size.
377            if stack_size > ram_for_data {
378                return Err(FlashError::InvalidFlashAlgorithmStackSize { size: stack_size });
379            }
380            ram_for_data -= stack_size;
381        }
382
383        // To determine the stack bottom, we need to know if the data is double buffered.
384        let double_buffering = if ram_for_data >= 2 * buffer_page_size {
385            // The data may be double buffered
386            // TODO: maybe allow disabling in the target description?
387            true
388        } else if ram_for_data >= buffer_page_size {
389            // The data is not double buffered. Place the stack at the end of the RAM region.
390            false
391        } else {
392            // We can't place data and stack.
393            // TODO: this should probably be done in the target validation.
394            // TODO: make the errors a bit more meaningful.
395            return Err(FlashError::InvalidFlashAlgorithmStackSize { size: stack_size });
396        };
397
398        // We need to make sure the blocks don't overlap and we have enough memory.
399        let stack_bottom =
400            if code_end + stack_size <= data_load_addr || ram_region != data_ram_region {
401                // Two cases:
402                // - The stack fits between the code and the data.
403                // - The data is in a different region, so we can place
404                //   the stack at the end of the code region.
405                code_end // already a multiple of stack_align
406            } else {
407                // The data and the stack are in the same region. There is not enough space
408                // for the stack below the data. Place the stack after the data.
409                let page_count = if double_buffering { 2 } else { 1 };
410                (data_load_addr + page_count * buffer_page_size).next_multiple_of(stack_align)
411            };
412
413        // Now we can place the stack.
414        let stack_top = stack_bottom + stack_size;
415        tracing::info!("Stack top: {:#010x}", stack_top);
416
417        if stack_top > ram_region.range.end {
418            return Err(FlashError::InvalidFlashAlgorithmStackSize { size: stack_size });
419        }
420
421        // Determine whether we can use double buffering or not by the remaining RAM region size.
422        let page_buffers = if double_buffering {
423            let second_buffer_start = data_load_addr + buffer_page_size;
424            vec![data_load_addr, second_buffer_start]
425        } else {
426            vec![data_load_addr]
427        };
428
429        tracing::debug!("Page buffers: {:#010x?}", page_buffers);
430
431        let name = raw.name.clone();
432
433        Ok(FlashAlgorithm {
434            name,
435            default: raw.default,
436            load_address: addr_load,
437            instructions,
438            pc_init: raw.pc_init.map(|v| code_start + v),
439            pc_uninit: raw.pc_uninit.map(|v| code_start + v),
440            pc_program_page: code_start + raw.pc_program_page,
441            pc_erase_sector: code_start + raw.pc_erase_sector,
442            pc_erase_all: raw.pc_erase_all.map(|v| code_start + v),
443            pc_verify: raw.pc_verify.map(|v| code_start + v),
444            pc_blank_check: raw.pc_blank_check.map(|v| code_start + v),
445            pc_read: raw.pc_read.map(|v| code_start + v),
446            vendor_functions: raw
447                .vendor_functions
448                .iter()
449                .map(|(name, addr)| (name.clone(), code_start + addr))
450                .collect(),
451            static_base: code_start + raw.data_section_offset,
452            stack_top,
453            stack_size,
454            page_buffers,
455            rtt_control_block: raw.rtt_location,
456            rtt_poll_interval: raw.rtt_poll_interval,
457            flash_properties: raw.flash_properties.clone(),
458            transfer_encoding: raw.transfer_encoding.unwrap_or_default(),
459            stack_overflow_check: raw.stack_overflow_check(),
460        })
461    }
462
463    /// Constructs a complete flash algorithm, choosing a suitable RAM region to run the algorithm.
464    pub fn assemble_from_raw_with_core(
465        algo: &RawFlashAlgorithm,
466        core_name: &str,
467        target: &Target,
468    ) -> Result<FlashAlgorithm, FlashError> {
469        // Find a RAM region from which we can run the algo.
470        let mm = &target.memory_map;
471
472        let ram_regions = mm
473            .iter()
474            .filter_map(MemoryRegion::as_ram_region)
475            .filter(|ram| ram.accessible_by(core_name))
476            .merge_consecutive();
477
478        let ram = ram_regions
479            .clone()
480            .filter(|ram| is_ram_suitable_for_algo(ram, algo.load_address))
481            .max_by_key(|region| region.range.end - region.range.start)
482            .ok_or(FlashError::NoRamDefined {
483                name: target.name.clone(),
484            })?;
485        tracing::info!("Chosen RAM to run the algo: {:x?}", ram);
486
487        let data_ram;
488        let data_ram = if let Some(data_load_address) = algo.data_load_address {
489            data_ram = ram_regions
490                .clone()
491                .find(|ram| is_ram_suitable_for_data(ram, data_load_address))
492                .ok_or(FlashError::NoRamDefined {
493                    name: target.name.clone(),
494                })?;
495
496            &data_ram
497        } else {
498            // If not specified, use the same region as the flash algo.
499            &ram
500        };
501        tracing::info!("Data will be loaded to: {:x?}", data_ram);
502
503        Self::assemble_from_raw_with_data(algo, &ram, data_ram, target)
504    }
505}
506
507/// Returns whether the given RAM region is usable for downloading the flash algorithm.
508fn is_ram_suitable_for_algo(ram: &RamRegion, load_address: Option<u64>) -> bool {
509    if !ram.is_executable() {
510        return false;
511    }
512
513    // If the algorithm has a forced load address, we try to use it.
514    // If not, then follow the CMSIS-Pack spec and use first available RAM region.
515    // In theory, it should be the "first listed in the pack", but the process of
516    // reading from the pack files obfuscates the list order, so we will use the first
517    // one in the target spec, which is the qualifying region with the lowest start saddress.
518    // - See https://open-cmsis-pack.github.io/Open-CMSIS-Pack-Spec/main/html/pdsc_family_pg.html#element_memory .
519    if let Some(load_addr) = load_address {
520        // The RAM must contain the forced load address _and_
521        // be accessible from the core we're going to run the
522        // algorithm on.
523        ram.range.contains(&load_addr)
524    } else {
525        true
526    }
527}
528
529/// Returns whether the given RAM region is usable for downloading the flash algorithm data.
530fn is_ram_suitable_for_data(ram: &RamRegion, load_address: u64) -> bool {
531    // The RAM must contain the forced load address _and_
532    // be accessible from the core we're going to run the
533    // algorithm on.
534    ram.range.contains(&load_address)
535}
536
537#[cfg(test)]
538mod test {
539    use probe_rs_target::{FlashProperties, SectorDescription, SectorInfo};
540
541    use crate::flashing::FlashAlgorithm;
542
543    #[test]
544    fn flash_sector_single_size() {
545        let config = FlashAlgorithm {
546            flash_properties: FlashProperties {
547                sectors: vec![SectorDescription {
548                    size: 0x100,
549                    address: 0x0,
550                }],
551                address_range: 0x1000..0x1000 + 0x1000,
552                page_size: 0x10,
553                ..Default::default()
554            },
555            ..Default::default()
556        };
557
558        let expected_first = SectorInfo {
559            base_address: 0x1000,
560            size: 0x100,
561        };
562
563        assert!(config.sector_info(0x1000 - 1).is_none());
564
565        assert_eq!(Some(expected_first), config.sector_info(0x1000));
566        assert_eq!(Some(expected_first), config.sector_info(0x10ff));
567
568        assert_eq!(Some(expected_first), config.sector_info(0x100b));
569        assert_eq!(Some(expected_first), config.sector_info(0x10ea));
570    }
571
572    #[test]
573    fn flash_sector_single_size_weird_sector_size() {
574        let config = FlashAlgorithm {
575            flash_properties: FlashProperties {
576                sectors: vec![SectorDescription {
577                    size: 258,
578                    address: 0x0,
579                }],
580                address_range: 0x800_0000..0x800_0000 + 258 * 10,
581                page_size: 0x10,
582                ..Default::default()
583            },
584            ..Default::default()
585        };
586
587        let expected_first = SectorInfo {
588            base_address: 0x800_0000,
589            size: 258,
590        };
591
592        assert!(config.sector_info(0x800_0000 - 1).is_none());
593
594        assert_eq!(Some(expected_first), config.sector_info(0x800_0000));
595        assert_eq!(Some(expected_first), config.sector_info(0x800_0000 + 257));
596
597        assert_eq!(Some(expected_first), config.sector_info(0x800_000b));
598        assert_eq!(Some(expected_first), config.sector_info(0x800_00e0));
599    }
600
601    #[test]
602    fn flash_sector_multiple_sizes() {
603        let config = FlashAlgorithm {
604            flash_properties: FlashProperties {
605                sectors: vec![
606                    SectorDescription {
607                        size: 0x4000,
608                        address: 0x0,
609                    },
610                    SectorDescription {
611                        size: 0x1_0000,
612                        address: 0x1_0000,
613                    },
614                    SectorDescription {
615                        size: 0x2_0000,
616                        address: 0x2_0000,
617                    },
618                ],
619                address_range: 0x800_0000..0x800_0000 + 0x10_0000,
620                page_size: 0x10,
621                ..Default::default()
622            },
623            ..Default::default()
624        };
625
626        let expected_a = SectorInfo {
627            base_address: 0x800_4000,
628            size: 0x4000,
629        };
630
631        let expected_b = SectorInfo {
632            base_address: 0x801_0000,
633            size: 0x1_0000,
634        };
635
636        let expected_c = SectorInfo {
637            base_address: 0x80A_0000,
638            size: 0x2_0000,
639        };
640
641        assert_eq!(Some(expected_a), config.sector_info(0x800_4000));
642        assert_eq!(Some(expected_b), config.sector_info(0x801_0000));
643        assert_eq!(Some(expected_c), config.sector_info(0x80A_0000));
644    }
645
646    #[test]
647    fn flash_sector_multiple_sizes_iter() {
648        let config = FlashAlgorithm {
649            flash_properties: FlashProperties {
650                sectors: vec![
651                    SectorDescription {
652                        size: 0x4000,
653                        address: 0x0,
654                    },
655                    SectorDescription {
656                        size: 0x1_0000,
657                        address: 0x1_0000,
658                    },
659                    SectorDescription {
660                        size: 0x2_0000,
661                        address: 0x2_0000,
662                    },
663                ],
664                address_range: 0x800_0000..0x800_0000 + 0x8_0000,
665                page_size: 0x10,
666                ..Default::default()
667            },
668            ..Default::default()
669        };
670
671        let got: Vec<SectorInfo> = config.iter_sectors().collect();
672
673        let expected = &[
674            SectorInfo {
675                base_address: 0x800_0000,
676                size: 0x4000,
677            },
678            SectorInfo {
679                base_address: 0x800_4000,
680                size: 0x4000,
681            },
682            SectorInfo {
683                base_address: 0x800_8000,
684                size: 0x4000,
685            },
686            SectorInfo {
687                base_address: 0x800_c000,
688                size: 0x4000,
689            },
690            SectorInfo {
691                base_address: 0x801_0000,
692                size: 0x1_0000,
693            },
694            SectorInfo {
695                base_address: 0x802_0000,
696                size: 0x2_0000,
697            },
698            SectorInfo {
699                base_address: 0x804_0000,
700                size: 0x2_0000,
701            },
702            SectorInfo {
703                base_address: 0x806_0000,
704                size: 0x2_0000,
705            },
706        ];
707        assert_eq!(&got, expected);
708    }
709}