Repository: probe-rs/probe-rs
Pull request #3079 (2025-02-07 22:50:23)

Report flash verification progress


Patch:
diff --git a/changelog/added-verify-progressbar.md b/changelog/added-verify-progressbar.md
new file mode 100644
index 00000000..3da20f93
--- /dev/null
+++ b/changelog/added-verify-progressbar.md
@@ -0,0 +1,1 @@
+Flasher now reports progress for flash content verification
\ No newline at end of file
diff --git a/changelog/changed-flasher-progress-reporting.md b/changelog/changed-flasher-progress-reporting.md
new file mode 100644
index 00000000..8f3fa4d3
--- /dev/null
+++ b/changelog/changed-flasher-progress-reporting.md
@@ -0,0 +1,1 @@
+The `ProgressEvent` type has been reworked, different phases have now been unified.
\ No newline at end of file
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs
index 61cf775a..0d568217 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/dap_server/server/debugger.rs
@@ -24,7 +24,9 @@ use crate::{
 };
 use anyhow::{anyhow, Context};
 use probe_rs::{
-    flashing::{DownloadOptions, FileDownloadError, FlashProgress, ProgressEvent},
+    flashing::{
+        DownloadOptions, FileDownloadError, FlashProgress, ProgressEvent, ProgressOperation,
+    },
     probe::list::Lister,
     Architecture, CoreStatus,
 };
@@ -641,12 +643,12 @@ impl Debugger {
         let rc_debug_adapter_clone = rc_debug_adapter.clone();
 
         struct ProgressState {
-            total_page_size: usize,
-            total_sector_size: usize,
-            total_fill_size: usize,
-            page_size_done: usize,
-            sector_size_done: usize,
-            fill_size_done: usize,
+            total_page_size: u64,
+            total_sector_size: u64,
+            total_fill_size: u64,
+            page_size_done: u64,
+            sector_size_done: u64,
+            fill_size_done: u64,
         }
 
         let progress_state = Rc::new(RefCell::new(ProgressState {
@@ -663,34 +665,27 @@ impl Debugger {
                 let mut flash_progress = progress_state.borrow_mut();
                 let mut debug_adapter = rc_debug_adapter_clone.borrow_mut();
                 match event {
-                    ProgressEvent::Initialized { phases, .. } => {
-                        for phase_layout in phases {
-                            flash_progress.total_page_size += phase_layout
-                                .pages()
-                                .iter()
-                                .map(|s| s.size() as usize)
-                                .sum::<usize>();
-
-                            flash_progress.total_sector_size += phase_layout
-                                .sectors()
-                                .iter()
-                                .map(|s| s.size() as usize)
-                                .sum::<usize>();
-
-                            flash_progress.total_fill_size += phase_layout
-                                .fills()
-                                .iter()
-                                .map(|s| s.size() as usize)
-                                .sum::<usize>();
-                        }
-                    }
-                    ProgressEvent::StartedFilling => {
+                    ProgressEvent::AddProgressBar {
+                        operation,
+                        total: Some(total),
+                    } => match operation {
+                        ProgressOperation::Fill => flash_progress.total_fill_size += total,
+                        ProgressOperation::Erase => flash_progress.total_sector_size += total,
+                        ProgressOperation::Program => flash_progress.total_page_size += total,
+
+                        _ => {}
+                    },
+                    ProgressEvent::Started(ProgressOperation::Fill) => {
                         debug_adapter
                             .update_progress(None, Some("Reading Old Pages"), id)
                             .ok();
                     }
-                    ProgressEvent::PageFilled { size, .. } => {
-                        flash_progress.fill_size_done += size as usize;
+                    ProgressEvent::Progress {
+                        operation: ProgressOperation::Fill,
+                        size,
+                        ..
+                    } => {
+                        flash_progress.fill_size_done += size;
                         let progress = flash_progress.fill_size_done as f64
                             / flash_progress.total_fill_size as f64;
 
@@ -698,64 +693,71 @@ impl Debugger {
                             .update_progress(Some(progress), Some("Reading Old Pages"), id)
                             .ok();
                     }
-                    ProgressEvent::FailedFilling => {
+                    ProgressEvent::Failed(ProgressOperation::Fill) => {
                         debug_adapter
                             .update_progress(Some(1.0), Some("Reading Old Pages Failed!"), id)
                             .ok();
                     }
-                    ProgressEvent::FinishedFilling => {
+                    ProgressEvent::Finished(ProgressOperation::Fill) => {
                         debug_adapter
                             .update_progress(Some(1.0), Some("Reading Old Pages Complete!"), id)
                             .ok();
                     }
-                    ProgressEvent::StartedErasing => {
+                    ProgressEvent::Started(ProgressOperation::Erase) => {
                         debug_adapter
                             .update_progress(None, Some("Erasing Sectors"), id)
                             .ok();
                     }
-                    ProgressEvent::SectorErased { size, .. } => {
-                        flash_progress.sector_size_done += size as usize;
+                    ProgressEvent::Progress {
+                        operation: ProgressOperation::Erase,
+                        size,
+                        ..
+                    } => {
+                        flash_progress.sector_size_done += size;
                         let progress = flash_progress.sector_size_done as f64
                             / flash_progress.total_sector_size as f64;
                         debug_adapter
                             .update_progress(Some(progress), Some("Erasing Sectors"), id)
                             .ok();
                     }
-                    ProgressEvent::FailedErasing => {
+                    ProgressEvent::Failed(ProgressOperation::Erase) => {
                         debug_adapter
                             .update_progress(Some(1.0), Some("Erasing Sectors Failed!"), id)
                             .ok();
                     }
-                    ProgressEvent::FinishedErasing => {
+                    ProgressEvent::Finished(ProgressOperation::Erase) => {
                         debug_adapter
                             .update_progress(Some(1.0), Some("Erasing Sectors Complete!"), id)
                             .ok();
                     }
-                    ProgressEvent::StartedProgramming { length } => {
-                        flash_progress.total_page_size = length as usize;
+                    ProgressEvent::Started(ProgressOperation::Program) => {
                         debug_adapter
                             .update_progress(None, Some("Programming Pages"), id)
                             .ok();
                     }
-                    ProgressEvent::PageProgrammed { size, .. } => {
-                        flash_progress.page_size_done += size as usize;
+                    ProgressEvent::Progress {
+                        operation: ProgressOperation::Program,
+                        size,
+                        ..
+                    } => {
+                        flash_progress.page_size_done += size;
                         let progress = flash_progress.page_size_done as f64
                             / flash_progress.total_page_size as f64;
                         debug_adapter
                             .update_progress(Some(progress), Some("Programming Pages"), id)
                             .ok();
                     }
-                    ProgressEvent::FailedProgramming => {
+                    ProgressEvent::Failed(ProgressOperation::Program) => {
                         debug_adapter
                             .update_progress(Some(1.0), Some("Flashing Pages Failed!"), id)
                             .ok();
                     }
-                    ProgressEvent::FinishedProgramming => {
+                    ProgressEvent::Finished(ProgressOperation::Program) => {
                         debug_adapter
                             .update_progress(Some(1.0), Some("Flashing Pages Complete!"), id)
                             .ok();
                     }
-                    ProgressEvent::DiagnosticMessage { .. } => (),
+                    _ => {}
                 }
             })
         });
diff --git a/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs b/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs
index d144de57..7e5253a9 100644
--- a/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs
+++ b/probe-rs-tools/src/bin/probe-rs/rpc/functions/flash.rs
@@ -178,6 +178,17 @@ pub enum Operation {
     Verify,
 }
 
+impl From<flashing::ProgressOperation> for Operation {
+    fn from(operation: flashing::ProgressOperation) -> Self {
+        match operation {
+            flashing::ProgressOperation::Fill => Operation::Fill,
+            flashing::ProgressOperation::Erase => Operation::Erase,
+            flashing::ProgressOperation::Program => Operation::Program,
+            flashing::ProgressOperation::Verify => Operation::Verify,
+        }
+    }
+}
+
 #[derive(Clone, Serialize, Deserialize, Schema)]
 pub enum ProgressEvent {
     FlashLayoutReady {
@@ -191,10 +202,7 @@ pub enum ProgressEvent {
     },
 
     /// Started an operation with the given total size.
-    Started {
-        operation: Operation,
-        total: u64,
-    }, // TODO this total should be part of AddProgressBar for Program, too
+    Started(Operation),
 
     /// An operation has made progress.
     Progress {
@@ -216,110 +224,31 @@ pub enum ProgressEvent {
     },
 }
 impl ProgressEvent {
-    pub fn from_library_event(
-        event: probe_rs::flashing::ProgressEvent,
-        mut cb: impl FnMut(ProgressEvent),
-    ) {
+    pub fn from_library_event(event: flashing::ProgressEvent, mut cb: impl FnMut(ProgressEvent)) {
         let event = match event {
-            probe_rs::flashing::ProgressEvent::Initialized {
-                chip_erase,
-                phases,
-                restore_unwritten,
-            } => {
-                if chip_erase {
-                    // TODO: indeterminate because ESP32s don't yet report their actual flash size while erasing.
-                    // Otherwise a determinate progress bar would look better, even if it would jump
-                    // from 0 to 100% in one step.
-                    cb(ProgressEvent::AddProgressBar {
-                        operation: Operation::Erase,
-                        total: None,
-                    });
-                }
-
-                for phase in phases.iter() {
-                    if restore_unwritten {
-                        let fill_size = phase.fills().iter().map(|s| s.size()).sum::<u64>();
-
-                        cb(ProgressEvent::AddProgressBar {
-                            operation: Operation::Fill,
-                            total: Some(fill_size),
-                        });
-                    }
-
-                    if !chip_erase {
-                        let sector_size = phase.sectors().iter().map(|s| s.size()).sum::<u64>();
-
-                        cb(ProgressEvent::AddProgressBar {
-                            operation: Operation::Erase,
-                            total: Some(sector_size),
-                        });
-                    }
-
-                    cb(ProgressEvent::AddProgressBar {
-                        operation: Operation::Program,
-                        total: Some(0),
-                    });
-                }
-
+            flashing::ProgressEvent::FlashLayoutReady { flash_layout } => {
                 ProgressEvent::FlashLayoutReady {
-                    flash_layout: phases.iter().map(FlashLayout::from).collect(),
-                }
-            }
-
-            // Fill
-            probe_rs::flashing::ProgressEvent::StartedFilling => ProgressEvent::Started {
-                operation: Operation::Fill,
-                total: 0,
-            },
-            probe_rs::flashing::ProgressEvent::PageFilled { size, .. } => ProgressEvent::Progress {
-                operation: Operation::Fill,
-                size,
-            },
-            probe_rs::flashing::ProgressEvent::FailedFilling => {
-                ProgressEvent::Failed(Operation::Fill)
-            }
-            probe_rs::flashing::ProgressEvent::FinishedFilling => {
-                ProgressEvent::Finished(Operation::Fill)
-            }
-
-            // Erase
-            probe_rs::flashing::ProgressEvent::StartedErasing => ProgressEvent::Started {
-                operation: Operation::Erase,
-                total: 0,
-            },
-            probe_rs::flashing::ProgressEvent::SectorErased { size, .. } => {
-                ProgressEvent::Progress {
-                    operation: Operation::Erase,
-                    size,
+                    flash_layout: flash_layout.iter().map(Into::into).collect(),
                 }
             }
-            probe_rs::flashing::ProgressEvent::FailedErasing => {
-                ProgressEvent::Failed(Operation::Erase)
-            }
-            probe_rs::flashing::ProgressEvent::FinishedErasing => {
-                ProgressEvent::Finished(Operation::Erase)
-            }
-
-            // Program
-            probe_rs::flashing::ProgressEvent::StartedProgramming { length } => {
-                ProgressEvent::Started {
-                    operation: Operation::Program,
-                    total: length,
-                }
-            }
-            probe_rs::flashing::ProgressEvent::PageProgrammed { size, .. } => {
-                ProgressEvent::Progress {
-                    operation: Operation::Program,
-                    size: size as u64,
+            flashing::ProgressEvent::AddProgressBar { operation, total } => {
+                ProgressEvent::AddProgressBar {
+                    operation: operation.into(),
+                    total,
                 }
             }
-            probe_rs::flashing::ProgressEvent::FailedProgramming => {
-                ProgressEvent::Failed(Operation::Program)
-            }
-            probe_rs::flashing::ProgressEvent::FinishedProgramming => {
-                ProgressEvent::Finished(Operation::Program)
+            flashing::ProgressEvent::Started(operation) => ProgressEvent::Started(operation.into()),
+            flashing::ProgressEvent::Progress {
+                operation, size, ..
+            } => ProgressEvent::Progress {
+                operation: operation.into(),
+                size,
+            },
+            flashing::ProgressEvent::Failed(operation) => ProgressEvent::Failed(operation.into()),
+            flashing::ProgressEvent::Finished(operation) => {
+                ProgressEvent::Finished(operation.into())
             }
-            probe_rs::flashing::ProgressEvent::DiagnosticMessage { message } => {
+            flashing::ProgressEvent::DiagnosticMessage { message } => {
                 ProgressEvent::DiagnosticMessage { message }
             }
         };
@@ -453,10 +382,7 @@ fn erase_impl(
                 operation: Operation::Erase,
                 ..
             }
-            | ProgressEvent::Started {
-                operation: Operation::Erase,
-                ..
-            }
+            | ProgressEvent::Started(Operation::Erase)
             | ProgressEvent::Progress {
                 operation: Operation::Erase,
                 ..
@@ -514,10 +440,7 @@ fn verify_impl(
                 operation: Operation::Verify,
                 ..
             }
-            | ProgressEvent::Started {
-                operation: Operation::Verify,
-                ..
-            }
+            | ProgressEvent::Started(Operation::Verify)
             | ProgressEvent::Progress {
                 operation: Operation::Verify,
                 ..
diff --git a/probe-rs-tools/src/bin/probe-rs/util/cli.rs b/probe-rs-tools/src/bin/probe-rs/util/cli.rs
index 3100c7af..5607cb11 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/cli.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/cli.rs
@@ -201,13 +201,6 @@ pub async fn flash(
     // Start timer.
     let flash_timer = Instant::now();
 
-    //let flash_layout_output_path = download_options.flash_layout_output_path.clone();
-    let pb = if download_options.disable_progressbars {
-        None
-    } else {
-        Some(CliProgressBars::new())
-    };
-
     let options = DownloadOptions {
         keep_unwritten_bytes: download_options.restore_unwritten,
         do_chip_erase: chip_erase,
@@ -221,21 +214,25 @@ pub async fn flash(
         .await?;
 
     let mut flash_layout = None;
-    let mut handle_progress_events = |event| {
-        if let ProgressEvent::FlashLayoutReady {
-            flash_layout: layout,
-        } = &event
-        {
-            flash_layout = Some(layout.clone());
-        }
-        if let Some(ref pb) = pb {
-            pb.handle(event);
-        }
-    };
 
     let run_flash = if download_options.preverify {
+        let pb = if download_options.disable_progressbars {
+            None
+        } else {
+            Some(CliProgressBars::new())
+        };
         let result = session
-            .verify(loader.loader, &mut handle_progress_events)
+            .verify(loader.loader, |event| {
+                if let ProgressEvent::FlashLayoutReady {
+                    flash_layout: layout,
+                } = &event
+                {
+                    flash_layout = Some(layout.clone());
+                }
+                if let Some(ref pb) = pb {
+                    pb.handle(event);
+                }
+            })
             .await?;
 
         result == VerifyResult::Mismatch
@@ -244,19 +241,31 @@ pub async fn flash(
     };
 
     if run_flash {
+        let pb = if download_options.disable_progressbars {
+            None
+        } else {
+            Some(CliProgressBars::new())
+        };
         session
             .flash(
                 options,
                 loader.loader,
                 rtt_client.as_ref().map(|c| c.handle),
-                &mut handle_progress_events,
+                |event| {
+                    if let ProgressEvent::FlashLayoutReady {
+                        flash_layout: layout,
+                    } = &event
+                    {
+                        flash_layout = Some(layout.clone());
+                    }
+                    if let Some(ref pb) = pb {
+                        pb.handle(event);
+                    }
+                },
             )
             .await?;
     }
 
-    // Dropping the progress bar so that it stops placing itself below log output.
-    std::mem::drop(pb);
-
     // Visualise flash layout to file if requested.
     if let Some(visualizer_output) = download_options.flash_layout_output_path {
         if let Some(phases) = flash_layout {
diff --git a/probe-rs-tools/src/bin/probe-rs/util/flash.rs b/probe-rs-tools/src/bin/probe-rs/util/flash.rs
index 5d5d08b9..aea149ce 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/flash.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/flash.rs
@@ -46,7 +46,10 @@ pub fn run_flash_download(
 
     options.progress = Some(FlashProgress::new(move |event| {
         if let Some(ref path) = flash_layout_output_path {
-            if let probe_rs::flashing::ProgressEvent::Initialized { ref phases, .. } = event {
+            if let probe_rs::flashing::ProgressEvent::FlashLayoutReady {
+                flash_layout: ref phases,
+            } = event
+            {
                 let mut flash_layout = FlashLayout::default();
                 for phase_layout in phases {
                     flash_layout.merge_from(phase_layout.into());
@@ -188,12 +191,6 @@ impl ProgressBarGroup {
         self.bars.push(bar);
     }
 
-    pub fn set_length(&mut self, length: u64) {
-        if let Some(bar) = self.bars.get(self.selected) {
-            bar.set_length(length);
-        }
-    }
-
     pub fn inc(&mut self, size: u64) {
         if let Some(bar) = self.bars.get(self.selected) {
             bar.set_style(Self::active(bar.length().is_some()));
@@ -278,17 +275,11 @@ impl CliProgressBars {
                 }
             }
 
-            ProgressEvent::Started { operation, total } => match operation {
+            ProgressEvent::Started(operation) => match operation {
                 Operation::Fill => progress_bars.fill.mark_start_now(),
                 Operation::Erase => progress_bars.erase.mark_start_now(),
-                Operation::Program => {
-                    progress_bars.program.mark_start_now();
-                    progress_bars.program.set_length(total);
-                }
-                Operation::Verify => {
-                    progress_bars.verify.mark_start_now();
-                    progress_bars.verify.set_length(total);
-                }
+                Operation::Program => progress_bars.program.mark_start_now(),
+                Operation::Verify => progress_bars.verify.mark_start_now(),
             },
 
             ProgressEvent::Progress { operation, size } => match operation {
diff --git a/probe-rs/src/flashing/encoder.rs b/probe-rs/src/flashing/encoder.rs
index c539d9b0..cc4ae7c2 100644
--- a/probe-rs/src/flashing/encoder.rs
+++ b/probe-rs/src/flashing/encoder.rs
@@ -48,7 +48,7 @@ struct ZlibEncoder {
 }
 
 impl ZlibEncoder {
-    fn new(flash: FlashLayout) -> Self {
+    fn new(flash: FlashLayout, ignore_fills: bool) -> Self {
         let mut compressed_pages = vec![];
 
         let page_size = flash.pages()[0].data().len();
@@ -61,6 +61,8 @@ impl ZlibEncoder {
             use flate2::write::ZlibEncoder;
             use flate2::Compression;
 
+            tracing::debug!("Image length: {} @ {:#010x}", image.len(), start_addr);
+
             // This page is not contiguous with the previous one, finish the previous image.
             let mut e = ZlibEncoder::new(Vec::new(), Compression::best());
             // These unwraps are okay because we are writing to a Vec and that is infallible.
@@ -78,6 +80,8 @@ impl ZlibEncoder {
                 .chain(first.iter().copied())
                 .collect::<Vec<u8>>();
 
+            tracing::debug!("Compressed length: {}", image_len + 4);
+
             // We add each page with `start_addr`. The address identifies the image and the
             // flash loader is responsible for tracking the write offset in the current image.
             compressed_pages.push(FlashPage {
@@ -97,15 +101,57 @@ impl ZlibEncoder {
         let mut previous_image = vec![];
         let mut previous_start_addr = 0;
 
-        for page in flash.pages() {
-            if page.address() != previous_start_addr + previous_image.len() as u64 {
-                compress_image(&previous_image, previous_start_addr);
-
-                previous_image.clear();
-                previous_start_addr = page.address();
+        for (page_idx, page) in flash.pages().iter().enumerate() {
+            let mut pieces_vec = Vec::new();
+            let pieces = if ignore_fills {
+                // Remove filled ranges from the data before we compress it.
+
+                // First, collect the relevant fills (the ones that touch the current page).
+                let mut relevant_fills = vec![];
+                for fill in flash.fills() {
+                    if fill.page_index() != page_idx {
+                        continue;
+                    }
+
+                    relevant_fills.push(fill);
+                }
+
+                // Sort them by address, just in case.
+                relevant_fills.sort_by_key(|fill| fill.address());
+
+                // Now break the page into pieces, separated by the fills.
+                let mut last_offset = 0;
+                for fill in relevant_fills {
+                    let offset = (fill.address() - page.address()) as usize;
+
+                    let data = &page.data()[last_offset..offset];
+                    if !data.is_empty() {
+                        pieces_vec.push((page.address() + last_offset as u64, data));
+                    }
+
+                    last_offset = offset + fill.size() as usize;
+                }
+
+                // Handle the remainder
+                let data = &page.data()[last_offset..];
+                if !data.is_empty() {
+                    pieces_vec.push((page.address() + last_offset as u64, data));
+                }
+
+                pieces_vec.as_slice()
+            } else {
+                &[(page.address(), page.data())]
+            };
+            for &(address, data) in pieces {
+                if address != previous_start_addr + previous_image.len() as u64 {
+                    compress_image(&previous_image, previous_start_addr);
+
+                    previous_image.clear();
+                    previous_start_addr = address;
+                }
+
+                previous_image.extend_from_slice(data);
             }
-
-            previous_image.extend_from_slice(page.data());
         }
 
         compress_image(&previous_image, previous_start_addr);
@@ -142,11 +188,11 @@ pub struct FlashEncoder {
 }
 
 impl FlashEncoder {
-    pub fn new(encoding: TransferEncoding, flash: FlashLayout) -> Self {
+    pub fn new(encoding: TransferEncoding, flash: FlashLayout, ignore_fills: bool) -> Self {
         Self {
             encoder: match encoding {
                 TransferEncoding::Raw => Box::new(RawEncoder::new(flash)),
-                TransferEncoding::Miniz => Box::new(ZlibEncoder::new(flash)),
+                TransferEncoding::Miniz => Box::new(ZlibEncoder::new(flash, ignore_fills)),
             },
         }
     }
diff --git a/probe-rs/src/flashing/erase.rs b/probe-rs/src/flashing/erase.rs
index 68512865..c46583d5 100644
--- a/probe-rs/src/flashing/erase.rs
+++ b/probe-rs/src/flashing/erase.rs
@@ -2,12 +2,18 @@ use std::collections::HashMap;
 
 use probe_rs_target::{MemoryRange, MemoryRegion, NvmRegion};
 
+use crate::flashing::progress::ProgressOperation;
 use crate::flashing::{flasher::Flasher, FlashError, FlashLoader};
-use crate::flashing::{FlashLayout, FlashSector, FlasherWithRegions};
+use crate::flashing::{FlashLayout, FlashSector};
 use crate::Session;
 
 use super::FlashProgress;
 
+struct FlasherWithRegions {
+    flasher: Flasher,
+    regions: Vec<NvmRegion>,
+}
+
 /// Mass-erase all nonvolatile memory.
 ///
 /// The optional progress will only be used to emit RTT messages.
@@ -70,11 +76,9 @@ pub fn erase_all(session: &mut Session, progress: FlashProgress) -> Result<(), F
     for el in algos.iter() {
         let flash_algorithm = &el.flasher.flash_algorithm;
 
-        let chip_erase_supported =
-            session.has_sequence_erase_all() || flash_algorithm.pc_erase_all.is_some();
         // If the first flash algo doesn't support erase all, disable chip erase.
         // TODO: we could sort by support but it's unlikely to make a difference.
-        if do_chip_erase && !chip_erase_supported {
+        if do_chip_erase && !el.flasher.is_chip_erase_supported(session) {
             do_chip_erase = false;
         }
 
@@ -95,7 +99,16 @@ pub fn erase_all(session: &mut Session, progress: FlashProgress) -> Result<(), F
         phases.push(layout);
     }
 
-    progress.initialized(do_chip_erase, false, phases);
+    if do_chip_erase {
+        progress.add_progress_bar(ProgressOperation::Erase, None);
+    } else {
+        for phase in phases.iter() {
+            let sector_size = phase.sectors().iter().map(|s| s.size()).sum::<u64>();
+
+            progress.add_progress_bar(ProgressOperation::Erase, Some(sector_size));
+        }
+    }
+    progress.initialized(phases);
 
     for el in algos {
         let mut flasher = el.flasher;
@@ -114,11 +127,14 @@ pub fn erase_all(session: &mut Session, progress: FlashProgress) -> Result<(), F
                 .iter_sectors()
                 .filter(|info| {
                     let range = info.base_address..info.base_address + info.size;
-                    el.regions.iter().any(|r| r.range.contains_range(&range))
+                    flasher
+                        .regions
+                        .iter()
+                        .any(|r| r.region.range.contains_range(&range))
                 })
                 .collect::<Vec<_>>();
 
-            flasher.run_erase(session, &progress, |active| {
+            flasher.run_erase(session, &progress, |active, _| {
                 for info in sectors {
                     tracing::debug!(
                         "    sector: {:#010x}-{:#010x} ({} bytes)",
@@ -209,7 +225,7 @@ pub fn erase_sectors(
             })
             .collect::<Vec<_>>();
 
-        flasher.run_erase(session, &progress, |active| {
+        flasher.run_erase(session, &progress, |active, _| {
             for info in sectors {
                 tracing::debug!(
                     "    sector: {:#010x}-{:#010x} ({} bytes)",
diff --git a/probe-rs/src/flashing/flasher.rs b/probe-rs/src/flashing/flasher.rs
index a50c7770..e5be0afa 100644
--- a/probe-rs/src/flashing/flasher.rs
+++ b/probe-rs/src/flashing/flasher.rs
@@ -1,4 +1,4 @@
-use probe_rs_target::RawFlashAlgorithm;
+use probe_rs_target::{RawFlashAlgorithm, TransferEncoding};
 use tracing::Level;
 
 use super::{FlashAlgorithm, FlashBuilder, FlashError, FlashPage, FlashProgress};
@@ -45,6 +45,72 @@ impl Operation for Verify {
     const NAME: &'static str = "Verify";
 }
 
+pub(super) enum FlashData {
+    Raw(FlashLayout),
+    Loaded {
+        encoder: FlashEncoder,
+        ignore_fills: bool,
+    },
+}
+
+impl FlashData {
+    pub fn layout(&self) -> &FlashLayout {
+        match self {
+            FlashData::Raw(layout) => layout,
+            FlashData::Loaded { encoder, .. } => encoder.flash_layout(),
+        }
+    }
+
+    pub fn layout_mut(&mut self) -> &mut FlashLayout {
+        // We're mutating the data, let's invalidate the encoder
+        if let FlashData::Loaded { encoder, .. } = self {
+            *self = FlashData::Raw(encoder.flash_layout().clone());
+        }
+
+        match self {
+            FlashData::Raw(layout) => layout,
+            FlashData::Loaded { .. } => unreachable!(),
+        }
+    }
+
+    pub fn encoder(&mut self, encoding: TransferEncoding, ignore_fills: bool) -> &FlashEncoder {
+        if let FlashData::Loaded {
+            encoder,
+            ignore_fills: was_ignore_fills,
+        } = self
+        {
+            if *was_ignore_fills != ignore_fills {
+                // Fill handling changed, invalidate the encoder
+                *self = FlashData::Raw(encoder.flash_layout().clone());
+            }
+        }
+        if let FlashData::Raw(layout) = self {
+            let layout = std::mem::take(layout);
+            let encoder = FlashEncoder::new(encoding, layout, ignore_fills);
+            *self = FlashData::Loaded {
+                encoder,
+                ignore_fills,
+            };
+        }
+
+        match self {
+            FlashData::Loaded { encoder, .. } => encoder,
+            FlashData::Raw(_) => unreachable!(),
+        }
+    }
+}
+
+pub(super) struct LoadedRegion {
+    pub region: NvmRegion,
+    pub data: FlashData,
+}
+
+impl LoadedRegion {
+    pub fn flash_layout(&self) -> &FlashLayout {
+        self.data.layout()
+    }
+}
+
 /// A structure to control the flash of an attached microchip.
 ///
 /// Once constructed it can be used to program date to the flash.
@@ -52,6 +118,7 @@ pub(super) struct Flasher {
     pub(super) core_index: usize,
     pub(super) flash_algorithm: FlashAlgorithm,
     pub(super) loaded: bool,
+    pub(super) regions: Vec<LoadedRegion>,
 }
 
 /// The byte used to fill the stack when checking for stack overflows.
@@ -73,6 +140,7 @@ impl Flasher {
             core_index,
             flash_algorithm,
             loaded: false,
+            regions: Vec::new(),
         })
     }
 
@@ -154,7 +222,7 @@ impl Flasher {
         session: &'s mut Session,
         progress: &'s FlashProgress,
         clock: Option<u32>,
-    ) -> Result<ActiveFlasher<'s, O>, FlashError> {
+    ) -> Result<(ActiveFlasher<'s, O>, &'s mut [LoadedRegion]), FlashError> {
         self.ensure_loaded(session)?;
 
         // Attach to memory and core.
@@ -174,7 +242,7 @@ impl Flasher {
 
         flasher.init(clock)?;
 
-        Ok(flasher)
+        Ok((flasher, &mut self.regions))
     }
 
     pub(super) fn run_erase_all(
@@ -193,7 +261,7 @@ impl Flasher {
             // may have invalidated any previously invalid state
             self.load(session)
         } else {
-            self.run_erase(session, progress, |active| active.erase_all())
+            self.run_erase(session, progress, |active, _| active.erase_all())
         };
 
         match result.is_ok() {
@@ -211,10 +279,10 @@ impl Flasher {
         f: F,
     ) -> Result<T, FlashError>
     where
-        F: FnOnce(&mut ActiveFlasher<'_, Erase>) -> Result<T, FlashError> + Sized,
+        F: FnOnce(&mut ActiveFlasher<'_, Erase>, &mut [LoadedRegion]) -> Result<T, FlashError>,
     {
-        let mut active = self.init(session, progress, None)?;
-        let r = f(&mut active)?;
+        let (mut active, data) = self.init(session, progress, None)?;
+        let r = f(&mut active, data)?;
         active.uninit()?;
         Ok(r)
     }
@@ -226,10 +294,10 @@ impl Flasher {
         f: F,
     ) -> Result<T, FlashError>
     where
-        F: FnOnce(&mut ActiveFlasher<'_, Program>) -> Result<T, FlashError> + Sized,
+        F: FnOnce(&mut ActiveFlasher<'_, Program>, &mut [LoadedRegion]) -> Result<T, FlashError>,
     {
-        let mut active = self.init(session, progress, None)?;
-        let r = f(&mut active)?;
+        let (mut active, data) = self.init(session, progress, None)?;
+        let r = f(&mut active, data)?;
         active.uninit()?;
         Ok(r)
     }
@@ -241,10 +309,10 @@ impl Flasher {
         f: F,
     ) -> Result<T, FlashError>
     where
-        F: FnOnce(&mut ActiveFlasher<'_, Verify>) -> Result<T, FlashError> + Sized,
+        F: FnOnce(&mut ActiveFlasher<'_, Verify>, &mut [LoadedRegion]) -> Result<T, FlashError>,
     {
-        let mut active = self.init(session, progress, None)?;
-        let r = f(&mut active)?;
+        let (mut active, data) = self.init(session, progress, None)?;
+        let r = f(&mut active, data)?;
         active.uninit()?;
         Ok(r)
     }
@@ -258,21 +326,16 @@ impl Flasher {
     /// If `restore_unwritten_bytes` is `true`, all bytes of a sector,
     /// that are not to be written during flashing will be read from the flash first
     /// and written again once the sector is erased.
-    #[allow(clippy::too_many_arguments)] // The plan is to remove at least `verify` in the future.
     pub(super) fn program(
         &mut self,
         session: &mut Session,
         progress: &FlashProgress,
-        region: &NvmRegion,
-        flash_builder: &FlashBuilder,
         restore_unwritten_bytes: bool,
         enable_double_buffering: bool,
         skip_erasing: bool,
         verify: bool,
     ) -> Result<(), FlashError> {
         tracing::debug!("Starting program procedure.");
-        // Convert the list of flash operations into flash sectors and pages.
-        let mut flash_layout = self.flash_layout(region, flash_builder, restore_unwritten_bytes)?;
 
         tracing::debug!("Double Buffering enabled: {:?}", enable_double_buffering);
         tracing::debug!(
@@ -281,32 +344,19 @@ impl Flasher {
         );
 
         if restore_unwritten_bytes {
-            self.fill_unwritten(session, progress, &mut flash_layout)?;
+            self.fill_unwritten(session, progress)?;
         }
 
-        let flash_encoder = FlashEncoder::new(self.flash_algorithm.transfer_encoding, flash_layout);
-
         // Skip erase if necessary (i.e. chip erase was done before)
         if !skip_erasing {
             // Erase all necessary sectors
-            self.sector_erase(session, progress, &flash_encoder)?;
+            self.sector_erase(session, progress)?;
         }
 
         // Flash all necessary pages.
-        if self.double_buffering_supported() && enable_double_buffering {
-            self.program_double_buffer(session, progress, &flash_encoder)?;
-        } else {
-            self.program_simple(session, progress, &flash_encoder)?;
-        };
+        self.do_program(session, progress, enable_double_buffering)?;
 
-        if verify
-            && !self.verify(
-                session,
-                progress,
-                flash_encoder.flash_layout(),
-                !restore_unwritten_bytes,
-            )?
-        {
+        if verify && !self.verify(session, progress, !restore_unwritten_bytes)? {
             return Err(FlashError::Verify);
         }
 
@@ -322,21 +372,23 @@ impl Flasher {
         &mut self,
         session: &mut Session,
         progress: &FlashProgress,
-        layout: &mut FlashLayout,
     ) -> Result<(), FlashError> {
         progress.started_filling();
 
-        let result = self.run_verify(session, progress, |active| {
-            for fill in layout.fills.iter() {
-                let t = Instant::now();
-                let page = &mut layout.pages[fill.page_index()];
+        let result = self.run_verify(session, progress, |active, data| {
+            for region in data.iter_mut() {
+                let layout = region.data.layout_mut();
+                for fill in layout.fills.iter() {
+                    let t = Instant::now();
+                    let page = &mut layout.pages[fill.page_index()];
 
-                let page_offset = (fill.address() - page.address()) as usize;
-                let page_slice = &mut page.data_mut()[page_offset..][..fill.size() as usize];
+                    let page_offset = (fill.address() - page.address()) as usize;
+                    let page_slice = &mut page.data_mut()[page_offset..][..fill.size() as usize];
 
-                active.read_flash(fill.address(), page_slice)?;
+                    active.read_flash(fill.address(), page_slice)?;
 
-                progress.page_filled(fill.size(), t.elapsed());
+                    progress.page_filled(fill.size(), t.elapsed());
+                }
             }
 
             Ok(())
@@ -350,151 +402,210 @@ impl Flasher {
         result
     }
 
-    /// Verifies all the to-be-written bytes of `layout`.
+    /// Verifies all the to-be-written bytes of this flasher.
     pub(super) fn verify(
         &mut self,
         session: &mut Session,
         progress: &FlashProgress,
-        layout: &FlashLayout,
         ignore_filled: bool,
     ) -> Result<bool, FlashError> {
-        self.run_verify(session, progress, |active| {
-            if let Some(verify) = active.flash_algorithm.pc_verify {
-                tracing::debug!("Verify using CMSIS function");
-                // Prefer Verify as we may use compression
-                // FIXME: avoid compressing multiple times
-                let flash_encoder =
-                    FlashEncoder::new(active.flash_algorithm.transfer_encoding, layout.clone());
+        progress.started_verifying();
 
-                for page in flash_encoder.pages() {
-                    let address = page.address();
-                    let bytes = page.data();
+        let result = self.do_verify(session, progress, ignore_filled);
 
-                    tracing::debug!(
-                        "Verifying page at address {:#010x} with size: {}",
-                        address,
-                        bytes.len()
-                    );
+        match result.is_ok() {
+            true => progress.finished_verifying(),
+            false => progress.failed_verifying(),
+        }
 
-                    // Transfer the bytes to RAM.
-                    let buffer_address = active.load_page_buffer(bytes, 0)?;
+        result
+    }
 
-                    let result = active.call_function_and_wait(
-                        &Registers {
-                            pc: into_reg(verify)?,
-                            r0: Some(into_reg(address)?),
-                            r1: Some(into_reg(bytes.len() as u64)?),
-                            r2: Some(into_reg(buffer_address)?),
-                            r3: None,
-                        },
-                        false,
-                        Duration::from_secs(30),
-                    )?;
+    fn do_verify(
+        &mut self,
+        session: &mut Session,
+        progress: &FlashProgress,
+        ignore_filled: bool,
+    ) -> Result<bool, FlashError> {
+        let encoding = self.flash_algorithm.transfer_encoding;
+        if let Some(verify) = self.flash_algorithm.pc_verify {
+            // Try to use the verify function if available.
+            self.run_verify(session, progress, |active, data| {
+                for region in data {
+                    tracing::debug!("Verify using CMSIS function");
 
-                    // Returns
-                    // status information:
-                    // the sum of (adr+sz) - on success.
-                    // any other number - on failure, and represents the failing address.
-                    if result as u64 != address + bytes.len() as u64 {
-                        tracing::debug!("Verification failed for page at address {:#010x}", result);
-                        return Ok(false);
-                    }
-                }
-            } else {
-                tracing::debug!("Verify using manual comparison");
-                for (idx, page) in layout.pages.iter().enumerate() {
-                    let address = page.address();
-                    let data = page.data();
-
-                    let mut read_back = vec![0; data.len()];
-                    active.read_flash(address, &mut read_back)?;
-
-                    if ignore_filled {
-                        // "Unfill" fill regions. These don't get flashed, so their contents are
-                        // allowed to differ. We mask these bytes with default flash content here,
-                        // just for the verification process.
-                        for fill in layout.fills() {
-                            if fill.page_index() != idx {
-                                continue;
-                            }
+                    // Prefer Verify as we may use compression
+                    let flash_encoder = region.data.encoder(encoding, ignore_filled);
+
+                    for page in flash_encoder.pages() {
+                        let start = Instant::now();
+                        let address = page.address();
+                        let bytes = page.data();
 
-                            let fill_offset = (fill.address() - address) as usize;
-                            let fill_size = fill.size() as usize;
+                        tracing::debug!(
+                            "Verifying page at address {:#010x} with size: {}",
+                            address,
+                            bytes.len()
+                        );
 
-                            let default_bytes = &data[fill_offset..][..fill_size];
-                            read_back[fill_offset..][..fill_size].copy_from_slice(default_bytes);
+                        // Transfer the bytes to RAM.
+                        let buffer_address = active.load_page_buffer(bytes, 0)?;
+
+                        let result = active.call_function_and_wait(
+                            &Registers {
+                                pc: into_reg(verify)?,
+                                r0: Some(into_reg(address)?),
+                                r1: Some(into_reg(bytes.len() as u64)?),
+                                r2: Some(into_reg(buffer_address)?),
+                                r3: None,
+                            },
+                            false,
+                            Duration::from_secs(30),
+                        )?;
+
+                        // Returns
+                        // status information:
+                        // the sum of (adr+sz) - on success.
+                        // any other number - on failure, and represents the failing address.
+                        if result as u64 != address + bytes.len() as u64 {
+                            tracing::debug!(
+                                "Verification failed for page at address {:#010x}",
+                                result
+                            );
+                            return Ok(false);
                         }
+
+                        progress.page_verified(bytes.len() as u64, start.elapsed());
                     }
+                }
+                Ok(true)
+            })
+        } else {
+            tracing::debug!("Verify by reading back flash contents");
+
+            self.run_verify(session, progress, |active, data| {
+                for region in data {
+                    let layout = region.data.layout();
+                    for (idx, page) in layout.pages().iter().enumerate() {
+                        let start = Instant::now();
+                        let address = page.address();
+                        let data = page.data();
+
+                        let mut read_back = vec![0; data.len()];
+                        active.read_flash(address, &mut read_back)?;
+
+                        if ignore_filled {
+                            // "Unfill" fill regions. These don't get flashed, so their contents are
+                            // allowed to differ. We mask these bytes with default flash content here,
+                            // just for the verification process.
+                            for fill in layout.fills() {
+                                if fill.page_index() != idx {
+                                    continue;
+                                }
+
+                                let fill_offset = (fill.address() - address) as usize;
+                                let fill_size = fill.size() as usize;
+
+                                let default_bytes = &data[fill_offset..][..fill_size];
+                                read_back[fill_offset..][..fill_size]
+                                    .copy_from_slice(default_bytes);
+                            }
+                        }
+                        if data != read_back {
+                            tracing::debug!(
+                                "Verification failed for page at address {:#010x}",
+                                address
+                            );
+                            return Ok(false);
+                        }
 
-                    if data != read_back.as_slice() {
-                        tracing::debug!(
-                            "Verification failed for page at address {:#010x}",
-                            address
-                        );
-                        return Ok(false);
+                        progress.page_verified(data.len() as u64, start.elapsed());
                     }
                 }
-            }
-            Ok(true)
-        })
+                Ok(true)
+            })
+        }
     }
 
-    /// Programs the pages given in `flash_layout` into the flash.
-    fn program_simple(
+    /// Perform an erase of all sectors given in `flash_layout`.
+    fn sector_erase(
         &mut self,
         session: &mut Session,
         progress: &FlashProgress,
-        flash_encoder: &FlashEncoder,
     ) -> Result<(), FlashError> {
-        progress.started_programming(flash_encoder.program_size());
-
-        let result = self.run_program(session, progress, |active| {
-            for page in flash_encoder.pages() {
-                active
-                    .program_page(page)
-                    .map_err(|error| FlashError::PageWrite {
-                        page_address: page.address(),
-                        source: Box::new(error),
-                    })?;
+        progress.started_erasing();
+
+        let encoding = self.flash_algorithm.transfer_encoding;
+
+        let result = self.run_erase(session, progress, |active, data| {
+            for region in data.iter_mut() {
+                for sector in region.data.encoder(encoding, false).sectors() {
+                    active
+                        .erase_sector(sector)
+                        .map_err(|e| FlashError::EraseFailed {
+                            sector_address: sector.address(),
+                            source: Box::new(e),
+                        })?;
+                }
             }
             Ok(())
         });
 
         match result.is_ok() {
-            true => progress.finished_programming(),
-            false => progress.failed_programming(),
+            true => progress.finished_erasing(),
+            false => progress.failed_erasing(),
         }
 
         result
     }
 
-    /// Perform an erase of all sectors given in `flash_layout`.
-    fn sector_erase(
+    fn do_program(
         &mut self,
         session: &mut Session,
         progress: &FlashProgress,
-        flash_encoder: &FlashEncoder,
+        enable_double_buffering: bool,
     ) -> Result<(), FlashError> {
-        progress.started_erasing();
-
-        let result = self.run_erase(session, progress, |active| {
-            for sector in flash_encoder.sectors() {
-                active
-                    .erase_sector(sector)
-                    .map_err(|e| FlashError::EraseFailed {
-                        sector_address: sector.address(),
-                        source: Box::new(e),
-                    })?;
-            }
-            Ok(())
-        });
+        progress.started_programming();
+        let program_result = if self.double_buffering_supported() && enable_double_buffering {
+            self.program_double_buffer(session, progress)
+        } else {
+            self.program_simple(session, progress)
+        };
 
-        match result.is_ok() {
-            true => progress.finished_erasing(),
-            false => progress.failed_erasing(),
+        match program_result.is_ok() {
+            true => progress.finished_programming(),
+            false => progress.failed_programming(),
         }
 
-        result
+        program_result
+    }
+
+    /// Programs the pages given in `flash_layout` into the flash.
+    fn program_simple(
+        &mut self,
+        session: &mut Session,
+        progress: &FlashProgress,
+    ) -> Result<(), FlashError> {
+        let encoding = self.flash_algorithm.transfer_encoding;
+        self.run_program(session, progress, |active, data| {
+            for region in data.iter_mut() {
+                tracing::debug!(
+                    "    programming region: {:#010X?} ({} bytes)",
+                    region.region.range,
+                    region.region.range.end - region.region.range.start
+                );
+                let flash_encoder = region.data.encoder(encoding, false);
+                for page in flash_encoder.pages() {
+                    active
+                        .program_page(page)
+                        .map_err(|error| FlashError::PageWrite {
+                            page_address: page.address(),
+                            source: Box::new(error),
+                        })?;
+                }
+            }
+            Ok(())
+        })
     }
 
     /// Flash a program using double buffering.
@@ -510,64 +621,70 @@ impl Flasher {
         &mut self,
         session: &mut Session,
         progress: &FlashProgress,
-        flash_encoder: &FlashEncoder,
     ) -> Result<(), FlashError> {
-        let mut current_buf = 0;
-        progress.started_programming(flash_encoder.program_size());
-
-        let result = self.run_program(session, progress, |active| {
-            let mut t = Instant::now();
-            let mut last_page_address = 0;
-            for page in flash_encoder.pages() {
-                // At the start of each loop cycle load the next page buffer into RAM.
-                let buffer_address = active.load_page_buffer(page.data(), current_buf)?;
-
-                // Then wait for the active RAM -> Flash copy process to finish.
-                // Also check if it finished properly. If it didn't, return an error.
-                active.wait_for_write_end(last_page_address)?;
-
-                last_page_address = page.address();
-                progress.page_programmed(page.size(), t.elapsed());
+        let encoding = self.flash_algorithm.transfer_encoding;
+        self.run_program(session, progress, |active, data| {
+            for region in data.iter_mut() {
+                tracing::debug!(
+                    "    programming region: {:#010X?} ({} bytes)",
+                    region.region.range,
+                    region.region.range.end - region.region.range.start
+                );
+                let flash_encoder = region.data.encoder(encoding, false);
+
+                let mut current_buf = 0;
+                let mut t = Instant::now();
+                let mut last_page_address = 0;
+                for page in flash_encoder.pages() {
+                    // At the start of each loop cycle load the next page buffer into RAM.
+                    let buffer_address = active.load_page_buffer(page.data(), current_buf)?;
 
-                t = Instant::now();
+                    // Then wait for the active RAM -> Flash copy process to finish.
+                    // Also check if it finished properly. If it didn't, return an error.
+                    active.wait_for_write_end(last_page_address)?;
 
-                // Start the next copy process.
-                active.start_program_page_with_buffer(
-                    buffer_address,
-                    page.address(),
-                    page.size() as u64,
-                )?;
+                    last_page_address = page.address();
+                    progress.page_programmed(page.size() as u64, t.elapsed());
 
-                // Swap the buffers
-                if current_buf == 1 {
-                    current_buf = 0;
-                } else {
-                    current_buf = 1;
-                }
-            }
+                    t = Instant::now();
 
-            active.wait_for_write_end(last_page_address)
-        });
+                    // Start the next copy process.
+                    active.start_program_page_with_buffer(
+                        buffer_address,
+                        page.address(),
+                        page.size() as u64,
+                    )?;
 
-        match result.is_ok() {
-            true => progress.finished_programming(),
-            false => progress.failed_programming(),
-        }
+                    // Swap the buffers
+                    if current_buf == 1 {
+                        current_buf = 0;
+                    } else {
+                        current_buf = 1;
+                    }
+                }
 
-        result
+                active.wait_for_write_end(last_page_address)?;
+            }
+            Ok(())
+        })
     }
 
-    pub(super) fn flash_layout(
-        &self,
-        region: &NvmRegion,
-        flash_builder: &FlashBuilder,
+    pub(crate) fn add_region(
+        &mut self,
+        region: NvmRegion,
+        builder: &FlashBuilder,
         restore_unwritten_bytes: bool,
-    ) -> Result<FlashLayout, FlashError> {
-        flash_builder.build_sectors_and_pages(
-            region,
+    ) -> Result<(), FlashError> {
+        let layout = builder.build_sectors_and_pages(
+            &region,
             &self.flash_algorithm,
             restore_unwritten_bytes,
-        )
+        )?;
+        self.regions.push(LoadedRegion {
+            region,
+            data: FlashData::Raw(layout),
+        });
+        Ok(())
     }
 }
 
@@ -1070,7 +1187,8 @@ impl ActiveFlasher<'_, Program> {
 
         tracing::info!("Flashing took: {:?}", t1.elapsed());
 
-        self.progress.page_programmed(page.size(), t1.elapsed());
+        self.progress
+            .page_programmed(page.size() as u64, t1.elapsed());
         Ok(())
     }
 
diff --git a/probe-rs/src/flashing/loader.rs b/probe-rs/src/flashing/loader.rs
index 894f1925..eb319906 100644
--- a/probe-rs/src/flashing/loader.rs
+++ b/probe-rs/src/flashing/loader.rs
@@ -16,6 +16,7 @@ use super::{
     IdfOptions,
 };
 use crate::config::DebugSequence;
+use crate::flashing::progress::ProgressOperation;
 use crate::flashing::{FlashLayout, FlashProgress, Format};
 use crate::memory::MemoryInterface;
 use crate::session::Session;
@@ -435,21 +436,28 @@ impl FlashLoader {
 
     /// Verifies data on the device.
     pub fn verify(&self, session: &mut Session, progress: FlashProgress) -> Result<(), FlashError> {
-        let algos = self.prepare_plan(session)?;
-
-        // TODO: actually report progress.
+        let mut algos = self.prepare_plan(session, false)?;
+
+        for flasher in algos.iter_mut() {
+            let mut program_size = 0;
+            for region in flasher.regions.iter_mut() {
+                program_size += region
+                    .data
+                    .encoder(flasher.flash_algorithm.transfer_encoding, true)
+                    .program_size();
+            }
+            progress.add_progress_bar(ProgressOperation::Verify, Some(program_size));
+        }
 
         // Iterate all flash algorithms we need to use and do the flashing.
-        for el in algos {
-            let mut flasher = el.flasher;
-            tracing::debug!("Flashing ranges for algo: {}", flasher.flash_algorithm.name);
-
-            for region in el.regions.iter() {
-                let flash_layout = flasher.flash_layout(region, &self.builder, false)?;
+        for mut flasher in algos {
+            tracing::debug!(
+                "Verifying ranges for algo: {}",
+                flasher.flash_algorithm.name
+            );
 
-                if !flasher.verify(session, &progress, &flash_layout, true)? {
-                    return Err(FlashError::Verify);
-                }
+            if !flasher.verify(session, &progress, true)? {
+                return Err(FlashError::Verify);
             }
         }
 
@@ -467,7 +475,7 @@ impl FlashLoader {
         mut options: DownloadOptions,
     ) -> Result<(), FlashError> {
         tracing::debug!("Committing FlashLoader!");
-        let algos = self.prepare_plan(session)?;
+        let mut algos = self.prepare_plan(session, options.keep_unwritten_bytes)?;
 
         if options.dry_run {
             tracing::info!("Skipping programming, dry run!");
@@ -486,23 +494,13 @@ impl FlashLoader {
             .clone()
             .unwrap_or_else(FlashProgress::empty);
 
-        self.initialize(&algos, session, &progress, &mut options)?;
+        self.initialize(&mut algos, session, &progress, &mut options)?;
 
         let mut do_chip_erase = options.do_chip_erase;
         let mut did_chip_erase = false;
 
-        if options.preverify && do_chip_erase {
-            // This is the simpler solution. We could pre-verify everything up front but it's
-            // complex and downloading flash algorithms multiple times may slow the process down.
-            tracing::warn!("Pre-verify requested but chip erase is enabled.");
-            tracing::warn!(
-                "This will erase the entire flash and make pre-verification impossible."
-            );
-        }
-
         // Iterate all flash algorithms we need to use and do the flashing.
-        for el in algos {
-            let mut flasher = el.flasher;
+        for mut flasher in algos {
             tracing::debug!("Flashing ranges for algo: {}", flasher.flash_algorithm.name);
 
             if do_chip_erase {
@@ -512,50 +510,21 @@ impl FlashLoader {
                 did_chip_erase = true;
             }
 
-            if options.preverify && !did_chip_erase {
-                tracing::info!("Pre-verifying!");
-
-                let mut contents_match = true;
-                for region in el.regions.iter() {
-                    let flash_layout = flasher.flash_layout(region, &self.builder, false)?;
-
-                    if !flasher.verify(session, &progress, &flash_layout, true)? {
-                        contents_match = false;
-                        break;
-                    }
-                }
-
-                if contents_match {
-                    tracing::info!("Contents match, skipping flashing.");
-                    continue;
-                }
-            }
-
             let mut do_use_double_buffering = flasher.double_buffering_supported();
             if do_use_double_buffering && options.disable_double_buffering {
                 tracing::info!("Disabled double-buffering support for loader via passed option, though target supports it.");
                 do_use_double_buffering = false;
             }
 
-            for region in el.regions {
-                tracing::debug!(
-                    "    programming region: {:#010X?} ({} bytes)",
-                    region.range,
-                    region.range.end - region.range.start
-                );
-
-                // Program the data.
-                flasher.program(
-                    session,
-                    &progress,
-                    &region,
-                    &self.builder,
-                    options.keep_unwritten_bytes,
-                    do_use_double_buffering,
-                    options.skip_erase || did_chip_erase,
-                    options.verify,
-                )?;
-            }
+            // Program the data.
+            flasher.program(
+                session,
+                &progress,
+                options.keep_unwritten_bytes,
+                do_use_double_buffering,
+                options.skip_erase || did_chip_erase,
+                options.verify,
+            )?;
         }
 
         tracing::debug!("Committing RAM!");
@@ -642,7 +611,11 @@ impl FlashLoader {
         Ok(())
     }
 
-    fn prepare_plan(&self, session: &mut Session) -> Result<Vec<FlasherWithRegions>, FlashError> {
+    fn prepare_plan(
+        &self,
+        session: &mut Session,
+        restore_unwritten_bytes: bool,
+    ) -> Result<Vec<Flasher>, FlashError> {
         tracing::debug!("Contents of builder:");
         for (&address, data) in &self.builder.data {
             tracing::debug!(
@@ -672,7 +645,7 @@ impl FlashLoader {
             tracing::warn!("Memory map of flash loader does not match memory map of target!");
         }
 
-        let mut algos = Vec::<FlasherWithRegions>::new();
+        let mut algos = Vec::<Flasher>::new();
 
         // Commit NVM first
 
@@ -716,15 +689,15 @@ impl FlashLoader {
 
             // We don't usually have more than a handful of regions, linear search should be fine.
             tracing::debug!("     -- using algorithm: {}", algo.name);
-            if let Some(entry) = algos.iter_mut().find(|entry| {
-                entry.flasher.flash_algorithm.name == algo.name && entry.flasher.core_index == core
-            }) {
-                entry.regions.push(region);
+            if let Some(entry) = algos
+                .iter_mut()
+                .find(|entry| entry.flash_algorithm.name == algo.name && entry.core_index == core)
+            {
+                entry.add_region(region, &self.builder, restore_unwritten_bytes)?;
             } else {
-                algos.push(FlasherWithRegions {
-                    flasher: Flasher::new(target, core, algo)?,
-                    regions: vec![region],
-                });
+                let mut flasher = Flasher::new(target, core, algo)?;
+                flasher.add_region(region, &self.builder, restore_unwritten_bytes)?;
+                algos.push(flasher);
             }
         }
 
@@ -733,35 +706,65 @@ impl FlashLoader {
 
     fn initialize(
         &self,
-        algos: &[FlasherWithRegions],
+        algos: &mut [Flasher],
         session: &mut Session,
         progress: &FlashProgress,
         options: &mut DownloadOptions,
     ) -> Result<(), FlashError> {
         let mut phases = vec![];
 
-        // Iterate all flash algorithms to initialize a few things.
-        for el in algos.iter() {
+        for flasher in algos.iter() {
             // If the first flash algo doesn't support erase all, disable chip erase.
             // TODO: we could sort by support but it's unlikely to make a difference.
-            if options.do_chip_erase && !el.flasher.is_chip_erase_supported(session) {
+            if options.do_chip_erase && !flasher.is_chip_erase_supported(session) {
                 options.do_chip_erase = false;
                 tracing::warn!("Chip erase was the selected method to erase the sectors but this chip does not support chip erases (yet).");
                 tracing::warn!("A manual sector erase will be performed.");
             }
+        }
 
+        if options.do_chip_erase {
+            progress.add_progress_bar(ProgressOperation::Erase, None);
+        }
+
+        // Iterate all flash algorithms to initialize a few things.
+        for flasher in algos.iter_mut() {
             let mut phase_layout = FlashLayout::default();
-            for region in el.regions.iter() {
-                let layout =
-                    el.flasher
-                        .flash_layout(region, &self.builder, options.keep_unwritten_bytes)?;
 
-                phase_layout.merge_from(layout);
+            let mut fill_size = 0;
+            let mut erase_size = 0;
+            let mut program_size = 0;
+
+            for region in flasher.regions.iter_mut() {
+                let layout = region.flash_layout();
+                phase_layout.merge_from(layout.clone());
+
+                erase_size += layout.sectors().iter().map(|s| s.size()).sum::<u64>();
+                fill_size += layout.fills().iter().map(|s| s.size()).sum::<u64>();
+                program_size += region
+                    .data
+                    .encoder(
+                        flasher.flash_algorithm.transfer_encoding,
+                        !options.keep_unwritten_bytes,
+                    )
+                    .program_size();
+            }
+
+            if options.keep_unwritten_bytes {
+                progress.add_progress_bar(ProgressOperation::Fill, Some(fill_size));
             }
+            if !options.do_chip_erase {
+                progress.add_progress_bar(ProgressOperation::Erase, Some(erase_size));
+            }
+            progress.add_progress_bar(ProgressOperation::Program, Some(program_size));
+            if options.verify {
+                progress.add_progress_bar(ProgressOperation::Verify, Some(program_size));
+            }
+
             phases.push(phase_layout);
         }
 
-        progress.initialized(options.do_chip_erase, options.keep_unwritten_bytes, phases);
+        progress.initialized(phases);
 
         Ok(())
     }
@@ -853,8 +856,3 @@ impl FlashLoader {
             .map(|(address, data)| (*address, data.as_slice()))
     }
 }
-
-pub(super) struct FlasherWithRegions {
-    pub(super) regions: Vec<NvmRegion>,
-    pub(super) flasher: Flasher,
-}
diff --git a/probe-rs/src/flashing/progress.rs b/probe-rs/src/flashing/progress.rs
index 0d5186f4..213dee48 100644
--- a/probe-rs/src/flashing/progress.rs
+++ b/probe-rs/src/flashing/progress.rs
@@ -39,78 +39,110 @@ impl FlashProgress {
         (self.handler)(event);
     }
 
-    /// Signalize that the flashing algorithm was set up and is initialized.
-    pub(super) fn initialized(
-        &self,
-        chip_erase: bool,
-        restore_unwritten: bool,
-        phases: Vec<FlashLayout>,
-    ) {
-        self.emit(ProgressEvent::Initialized {
-            chip_erase,
-            restore_unwritten,
-            phases,
+    /// Signal that the flashing algorithm was set up and is initialized.
+    pub(super) fn initialized(&self, phases: Vec<FlashLayout>) {
+        self.emit(ProgressEvent::FlashLayoutReady {
+            flash_layout: phases,
         });
     }
 
-    /// Signalize that the erasing procedure started.
+    pub(super) fn add_progress_bar(&self, operation: ProgressOperation, total: Option<u64>) {
+        self.emit(ProgressEvent::AddProgressBar { operation, total });
+    }
+
+    /// Signal that the erasing procedure started.
     pub(super) fn started_erasing(&self) {
-        self.emit(ProgressEvent::StartedErasing);
+        self.emit(ProgressEvent::Started(ProgressOperation::Erase));
     }
 
-    /// Signalize that the filling procedure started.
+    /// Signal that the filling procedure started.
     pub(super) fn started_filling(&self) {
-        self.emit(ProgressEvent::StartedFilling);
+        self.emit(ProgressEvent::Started(ProgressOperation::Fill));
+    }
+
+    /// Signal that the programming procedure started.
+    pub(super) fn started_programming(&self) {
+        self.emit(ProgressEvent::Started(ProgressOperation::Program));
     }
 
-    /// Signalize that the programming procedure started.
-    pub(super) fn started_programming(&self, length: u64) {
-        self.emit(ProgressEvent::StartedProgramming { length });
+    pub(crate) fn started_verifying(&self) {
+        self.emit(ProgressEvent::Started(ProgressOperation::Verify));
     }
 
-    /// Signalize that the page programming procedure has made progress.
-    pub(super) fn page_programmed(&self, size: u32, time: Duration) {
-        self.emit(ProgressEvent::PageProgrammed { size, time });
+    /// Signal that the page programming procedure has made progress.
+    pub(super) fn page_programmed(&self, size: u64, time: Duration) {
+        self.emit(ProgressEvent::Progress {
+            operation: ProgressOperation::Program,
+            size,
+            time,
+        });
     }
 
-    /// Signalize that the sector erasing procedure has made progress.
+    /// Signal that the sector erasing procedure has made progress.
     pub(super) fn sector_erased(&self, size: u64, time: Duration) {
-        self.emit(ProgressEvent::SectorErased { size, time });
+        self.emit(ProgressEvent::Progress {
+            operation: ProgressOperation::Erase,
+            size,
+            time,
+        });
     }
 
-    /// Signalize that the page filling procedure has made progress.
+    /// Signal that the page filling procedure has made progress.
     pub(super) fn page_filled(&self, size: u64, time: Duration) {
-        self.emit(ProgressEvent::PageFilled { size, time });
+        self.emit(ProgressEvent::Progress {
+            operation: ProgressOperation::Fill,
+            size,
+            time,
+        });
+    }
+
+    /// Signal that the page filling procedure has made progress.
+    pub(super) fn page_verified(&self, size: u64, time: Duration) {
+        self.emit(ProgressEvent::Progress {
+            operation: ProgressOperation::Verify,
+            size,
+            time,
+        });
     }
 
-    /// Signalize that the programming procedure failed.
+    /// Signal that the programming procedure failed.
     pub(super) fn failed_programming(&self) {
-        self.emit(ProgressEvent::FailedProgramming);
+        self.emit(ProgressEvent::Failed(ProgressOperation::Program));
     }
 
-    /// Signalize that the programming procedure completed successfully.
+    /// Signal that the programming procedure completed successfully.
     pub(super) fn finished_programming(&self) {
-        self.emit(ProgressEvent::FinishedProgramming);
+        self.emit(ProgressEvent::Finished(ProgressOperation::Program));
     }
 
-    /// Signalize that the erasing procedure failed.
+    /// Signal that the erasing procedure failed.
     pub(super) fn failed_erasing(&self) {
-        self.emit(ProgressEvent::FailedErasing);
+        self.emit(ProgressEvent::Failed(ProgressOperation::Erase));
+    }
+
+    /// Signal that the verifying procedure failed.
+    pub(super) fn failed_verifying(&self) {
+        self.emit(ProgressEvent::Failed(ProgressOperation::Verify));
     }
 
-    /// Signalize that the erasing procedure completed successfully.
+    /// Signal that the erasing procedure completed successfully.
     pub(super) fn finished_erasing(&self) {
-        self.emit(ProgressEvent::FinishedErasing);
+        self.emit(ProgressEvent::Finished(ProgressOperation::Erase));
     }
 
-    /// Signalize that the filling procedure failed.
+    /// Signal that the filling procedure failed.
     pub(super) fn failed_filling(&self) {
-        self.emit(ProgressEvent::FailedFilling);
+        self.emit(ProgressEvent::Failed(ProgressOperation::Fill));
     }
 
-    /// Signalize that the filling procedure completed successfully.
+    /// Signal that the filling procedure completed successfully.
     pub(super) fn finished_filling(&self) {
-        self.emit(ProgressEvent::FinishedFilling);
+        self.emit(ProgressEvent::Finished(ProgressOperation::Fill));
+    }
+
+    /// Signal that the verifying procedure completed successfully.
+    pub(super) fn finished_verifying(&self) {
+        self.emit(ProgressEvent::Finished(ProgressOperation::Verify));
     }
 
     pub(super) fn message(&self, message: String) {
@@ -118,86 +150,71 @@ impl FlashProgress {
     }
 }
 
+/// The operation that is currently in progress.
+#[derive(Clone, Copy, Debug)]
+pub enum ProgressOperation {
+    /// Reading back flash contents to restore erased regions that should be kept unchanged.
+    Fill,
+
+    /// Erasing flash sectors.
+    Erase,
+
+    /// Writing data to flash.
+    Program,
+
+    /// Checking flash contents.
+    Verify,
+}
+
 /// Possible events during the flashing process.
 ///
 /// If flashing works without problems, the events will arrive in the
 /// following order:
 ///
-/// * `Initialized`
-/// * `StartedFilling`
-/// * `PageFilled` for every page
-/// * `FinishedFilling`
-/// * `StartedErasing`
-/// * `SectorErased` for every sector
-/// * `FinishedErasing`
-/// * `StartedProgramming`
-/// * `PageProgrammed` for every page
-/// * `FinishedProgramming`
+/// * `FlashLayoutReady`
+/// * A number of `AddProgressBar` events
+/// * `Started`, `Progress`, and `Finished` events for each operation
 ///
-/// If an error occurs in any stage, one of the `Failed*` event will be returned,
+/// If an error occurs in any stage, the `Failed` event will be returned,
 /// and no further events will be returned.
 #[derive(Debug)]
 pub enum ProgressEvent {
-    /// The flash layout has been built and the flashing procedure was initialized.
-    Initialized {
-        /// Whether the chip erase feature is enabled.
-        /// If this is true, the chip will be erased before any other operation. No separate erase
-        /// progress bars are necessary in this case.
-        chip_erase: bool,
-
-        /// The layout of the flash contents as it will be used by the flash procedure, grouped by
-        /// phases (fill, erase, program sequences).
-        /// This is an exact report of what the flashing procedure will do during the flashing process.
-        phases: Vec<FlashLayout>,
-
-        /// Whether the unwritten flash contents will be restored after erasing.
-        restore_unwritten: bool,
+    /// The flash layout is ready.
+    FlashLayoutReady {
+        /// The flash layout.
+        flash_layout: Vec<FlashLayout>,
     },
-    /// Filling of flash pages has started.
-    StartedFilling,
-    /// A page has been filled successfully.
-    /// This does not mean the page has been programmed yet.
-    /// Only its contents are determined at this point!
-    PageFilled {
-        /// The size of the page in bytes.
-        size: u64,
-        /// The time it took to fill this flash page.
-        time: Duration,
+
+    /// Display a new progress bar to the user.
+    AddProgressBar {
+        /// The operation that the progress bar is for.
+        operation: ProgressOperation,
+        /// The total size of the operation, if known.
+        ///
+        /// If `None`, the total size is indeterminate.
+        total: Option<u64>,
     },
-    /// Filling of the pages has failed.
-    FailedFilling,
-    /// Filling of the pages has finished successfully.
-    FinishedFilling,
-    /// Erasing of flash has started.
-    StartedErasing,
-    /// A sector has been erased successfully.
-    SectorErased {
-        /// The size of the sector in bytes.
+
+    /// Started an operation with the given total size.
+    Started(ProgressOperation),
+
+    /// An operation has made progress.
+    Progress {
+        /// The operation that made progress.
+        operation: ProgressOperation,
+        /// The size of the page in bytes.
         size: u64,
-        /// The time it took to erase this sector.
-        time: Duration,
-    },
-    /// Erasing of the flash has failed.
-    FailedErasing,
-    /// Erasing of the flash has finished successfully.
-    FinishedErasing,
-    /// Programming of the flash has started.
-    StartedProgramming {
-        /// The total length of the data to be programmed in bytes.
-        length: u64,
-    },
-    /// A flash page has been programmed successfully.
-    PageProgrammed {
-        /// The size of this page in bytes.
-        size: u32,
-        /// The time it took to program this page.
+        /// The time it took to perform the operation.
         time: Duration,
     },
-    /// Programming of the flash failed.
-    FailedProgramming,
-    /// Programming of the flash has finished successfully.
-    FinishedProgramming,
-    /// a message was received from the algo.
+
+    /// An operation has failed.
+    Failed(ProgressOperation),
+
+    /// An operation has finished successfully.
+    Finished(ProgressOperation),
+
+    /// A message was received from the algo.
     DiagnosticMessage {
         /// The message that was emitted.
         message: String,


Test patch:
diff --git a/target-gen/src/commands/test.rs b/target-gen/src/commands/test.rs
index d374fb1c..cd2ecb46 100644
--- a/target-gen/src/commands/test.rs
+++ b/target-gen/src/commands/test.rs
@@ -9,6 +9,7 @@ use colored::Colorize;
 use probe_rs::{
     flashing::{
         erase_all, erase_sectors, DownloadOptions, FlashLoader, FlashProgress, ProgressEvent,
+        ProgressOperation,
     },
     MemoryInterface, Permissions, Session,
 };
@@ -88,24 +89,24 @@ pub fn cmd_test(
     // Register callback to update the progress.
     let t = Rc::new(RefCell::new(Instant::now()));
     let progress = FlashProgress::new(move |event| match event {
-        ProgressEvent::StartedProgramming { .. } => {
+        ProgressEvent::Started(ProgressOperation::Program) => {
             let mut t = t.borrow_mut();
             *t = Instant::now();
         }
-        ProgressEvent::StartedErasing => {
+        ProgressEvent::Started(ProgressOperation::Erase) => {
             let mut t = t.borrow_mut();
             *t = Instant::now();
         }
-        ProgressEvent::FailedErasing => {
+        ProgressEvent::Failed(ProgressOperation::Erase) => {
             println!("Failed erasing in {:?}", t.borrow().elapsed());
         }
-        ProgressEvent::FinishedErasing => {
+        ProgressEvent::Finished(ProgressOperation::Erase) => {
             println!("Finished erasing in {:?}", t.borrow().elapsed());
         }
-        ProgressEvent::FailedProgramming => {
+        ProgressEvent::Failed(ProgressOperation::Program) => {
             println!("Failed programming in {:?}", t.borrow().elapsed());
         }
-        ProgressEvent::FinishedProgramming => {
+        ProgressEvent::Finished(ProgressOperation::Program) => {
             println!("Finished programming in {:?}", t.borrow().elapsed());
         }
         ProgressEvent::DiagnosticMessage { message } => {