Repository: probe-rs/probe-rs
Pull request #3459 (2025-07-10 16:53:42)

flashing: change erase, blank_check to work with address ranges.
The previous code operated with sector indices, which is ambiguous
if the chip has multiple algos. What's worse, it'd erase that
sector index range on *every* algo, which is never what you want.

Changing it to use addresses makes it non-ambiguous and consistent
with FlashLoader.



Patch:
diff --git a/changelog/changed-erase-blankcheck.md b/changelog/changed-erase-blankcheck.md
new file mode 100644
index 00000000..61695fb2
--- /dev/null
+++ b/changelog/changed-erase-blankcheck.md
@@ -0,0 +1,1 @@
+Changed `flashing::{erase,blank_check}` to work with address ranges instead of sector numbers. Renamed `erase_sectors` to `erase`.
\ No newline at end of file
diff --git a/probe-rs/src/flashing/erase.rs b/probe-rs/src/flashing/erase.rs
index d1449b93..477d9243 100644
--- a/probe-rs/src/flashing/erase.rs
+++ b/probe-rs/src/flashing/erase.rs
@@ -157,18 +157,17 @@ pub fn erase_all(session: &mut Session, progress: FlashProgress) -> Result<(), F
     Ok(())
 }
 
-/// Erases `sectors` sectors starting from `start_sector` from flash.
+/// Erases flash address range `address_start..address_end`.
 // TODO: currently no progress is reported by anything in this function.
-pub fn erase_sectors(
+pub fn erase(
     session: &mut Session,
     progress: FlashProgress,
-    start_sector: usize,
-    sectors: usize,
+    address_start: u64,
+    address_end: u64,
 ) -> Result<(), FlashError> {
-    tracing::debug!(
-        "Erasing sectors {start_sector} trough {}",
-        start_sector + sectors
-    );
+    tracing::debug!("Erasing {address_start:08x}..{address_end:08x}");
+
+    let address_range = address_start..address_end;
 
     let mut algos: HashMap<(String, String), Vec<NvmRegion>> = HashMap::new();
     tracing::debug!("Regions:");
@@ -178,16 +177,21 @@ pub fn erase_sectors(
         .iter()
         .filter_map(MemoryRegion::as_nvm_region)
     {
-        if region.is_alias {
-            tracing::debug!("Skipping alias memory region {:#010x?}", region.range);
-            continue;
-        }
         tracing::debug!(
             "    region: {:#010x?} ({} bytes)",
             region.range,
             region.range.end - region.range.start
         );
 
+        // If we have nothing to do in this region, ignore it.
+        // This avoids uselessly initializing and deinitializing its flash algorithm.
+        // We do not check for alias regions here, as we'll work with them if the range explicitly
+        // targets them.
+        if !region.range.intersects_range(&address_range) {
+            tracing::debug!("     -- doesn't overlap, ignoring!");
+            continue;
+        }
+
         // Get the first core that can access the region
         let core_name = region
             .cores
@@ -218,8 +222,7 @@ pub fn erase_sectors(
         let sectors = flasher
             .flash_algorithm()
             .iter_sectors()
-            .skip(start_sector)
-            .take(sectors)
+            .filter(|info| address_range.contains_range(&info.address_range()))
             .filter(|info| {
                 let range = info.base_address..info.base_address + info.size;
                 regions.iter().any(|r| r.range.contains_range(&range))
@@ -253,10 +256,12 @@ pub fn erase_sectors(
 pub fn run_blank_check(
     session: &mut Session,
     progress: FlashProgress,
-    start_sector: usize,
-    sectors: usize,
+    address_start: u64,
+    address_end: u64,
 ) -> Result<(), FlashError> {
-    tracing::debug!("Performing blank check...");
+    tracing::debug!("Blank-checking {address_start:08x}..{address_end:08x}");
+
+    let address_range = address_start..address_end;
 
     let mut algos: HashMap<(String, String), Vec<NvmRegion>> = HashMap::new();
     tracing::debug!("Regions:");
@@ -266,16 +271,21 @@ pub fn run_blank_check(
         .iter()
         .filter_map(MemoryRegion::as_nvm_region)
     {
-        if region.is_alias {
-            tracing::debug!("Skipping alias memory region {:#010x?}", region.range);
-            continue;
-        }
         tracing::debug!(
             "    region: {:#010x?} ({} bytes)",
             region.range,
             region.range.end - region.range.start
         );
 
+        // If we have nothing to do in this region, ignore it.
+        // This avoids uselessly initializing and deinitializing its flash algorithm.
+        // We do not check for alias regions here, as we'll work with them if the range explicitly
+        // targets them.
+        if !region.range.intersects_range(&address_range) {
+            tracing::debug!("     -- doesn't overlap, ignoring!");
+            continue;
+        }
+
         // Get the first core that can access the region
         let core_name = region
             .cores
@@ -294,7 +304,7 @@ pub fn run_blank_check(
     }
 
     for ((algo_name, core_name), regions) in algos {
-        tracing::debug!("Checking for blank sector with algorithm: {}", algo_name);
+        tracing::debug!("Blank-checking with algorithm: {}", algo_name);
 
         // This can't fail, algo_name comes from the target.
         let algo = session.target().flash_algorithm_by_name(&algo_name);
@@ -306,8 +316,7 @@ pub fn run_blank_check(
         let sectors = flasher
             .flash_algorithm()
             .iter_sectors()
-            .skip(start_sector)
-            .take(sectors)
+            .filter(|info| address_range.contains_range(&info.address_range()))
             .filter(|info| {
                 let range = info.base_address..info.base_address + info.size;
                 regions.iter().any(|r| r.range.contains_range(&range))


Test patch:
diff --git a/target-gen/src/commands/test.rs b/target-gen/src/commands/test.rs
index 54842caf..3707cc4f 100644
--- a/target-gen/src/commands/test.rs
+++ b/target-gen/src/commands/test.rs
@@ -9,8 +9,8 @@ use probe_rs::{
     MemoryInterface, Permissions, Session, SessionConfig,
     config::Registry,
     flashing::{
-        DownloadOptions, FlashLoader, FlashProgress, ProgressEvent, ProgressOperation, erase_all,
-        erase_sectors, run_blank_check,
+        DownloadOptions, FlashLoader, FlashProgress, ProgressEvent, ProgressOperation, erase,
+        erase_all, run_blank_check,
     },
     probe::{DebugProbeSelector, WireProtocol, list::Lister},
 };
@@ -187,20 +187,26 @@ pub fn cmd_test(
             "test_start_sector_address must be sector aligned address pointing flash range"
         ));
     }
-    let test_start_sector_index =
-        ((test_start_sector_address - start_address) / sector_size) as usize;
 
     let test = "Test".green();
     println!("{test}: Erasing sectorwise and writing two pages ...");
     run_flash_erase(
         &mut session,
         progress.clone(),
-        EraseType::EraseSectors(test_start_sector_index, 2),
+        EraseType::EraseRange(
+            test_start_sector_address,
+            test_start_sector_address + sector_size * 2,
+        ),
     )?;
 
     println!("{test}: Erase done");
 
-    run_blank_check(&mut session, progress.clone(), test_start_sector_index, 2)?;
+    run_blank_check(
+        &mut session,
+        progress.clone(),
+        test_start_sector_address,
+        test_start_sector_address + sector_size * 2,
+    )?;
 
     println!("{test}: Writing two pages ...");
 
@@ -220,7 +226,12 @@ pub fn cmd_test(
     println!("{test}: Erasing the entire chip and writing two pages ...");
     run_flash_erase(&mut session, progress.clone(), EraseType::EraseAll)?;
     println!("{test}: Erase done");
-    run_blank_check(&mut session, progress.clone(), test_start_sector_index, 2)?;
+    run_blank_check(
+        &mut session,
+        progress.clone(),
+        test_start_sector_address,
+        test_start_sector_address + sector_size * 2,
+    )?;
 
     println!("{test}: Writing two pages ...");
     let mut loader = session.target().flash_loader();
@@ -240,11 +251,19 @@ pub fn cmd_test(
     run_flash_erase(
         &mut session,
         progress.clone(),
-        EraseType::EraseSectors(test_start_sector_index, 2),
+        EraseType::EraseRange(
+            test_start_sector_address,
+            test_start_sector_address + sector_size * 2,
+        ),
     )?;
     println!("{test}: Erase done");
 
-    run_blank_check(&mut session, progress.clone(), test_start_sector_index, 2)?;
+    run_blank_check(
+        &mut session,
+        progress.clone(),
+        test_start_sector_address,
+        test_start_sector_address + sector_size * 2,
+    )?;
 
     println!("{test}: Writing two pages ...");
     let mut loader = session.target().flash_loader();
@@ -294,7 +313,7 @@ pub fn run_flash_download(
 
 pub enum EraseType {
     EraseAll,
-    EraseSectors(usize, usize),
+    EraseRange(u64, u64),
 }
 
 /// Erases the entire flash or just the sectors specified.
@@ -303,8 +322,8 @@ pub fn run_flash_erase(
     progress: FlashProgress,
     erase_type: EraseType,
 ) -> Result<()> {
-    if let EraseType::EraseSectors(start_sector, sectors) = erase_type {
-        erase_sectors(session, progress, start_sector, sectors)?;
+    if let EraseType::EraseRange(start, end) = erase_type {
+        erase(session, progress, start, end)?;
     } else {
         erase_all(session, progress)?;
     }