Repository: probe-rs/probe-rs
Pull request #2592 (2024-06-29 13:21:38)

Some minor target-gen improvements
Added `--chip` in case the tested description contains multiple chips. Cleaned up a few unnecessary `.clone()`s.

This PR undoes https://github.com/probe-rs/probe-rs/pull/1771/files#diff-a54dca91b1c085f16d7b7e573bb96cb37cf95a32fe5716a5b8a825571d45ecf7R66 which tried to flash generic targets for some reason. The PR also adds (back?) the known targets, so `only_supported_familes` has something to compare against.


Patch:
diff --git a/changelog/added-cargo-embed-path.md b/changelog/added-cargo-embed-path.md
new file mode 100644
index 00000000..e9516f6a
--- /dev/null
+++ b/changelog/added-cargo-embed-path.md
@@ -0,0 +1,1 @@
+Added `--path` option to cargo embed.
\ No newline at end of file
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs
index b489c219..373a1f4b 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_embed/mod.rs
@@ -58,6 +58,9 @@ struct CliOptions {
     /// Work directory for the command.
     #[arg(long)]
     work_dir: Option<PathBuf>,
+    /// The path to the file to be flashed. Setting this will ignore the cargo options.
+    #[arg(value_name = "path", long)]
+    path: Option<PathBuf>,
     #[clap(flatten)]
     cargo_options: CargoOptions,
 }
@@ -126,12 +129,17 @@ fn main_try(args: &[OsString], offset: UtcOffset) -> Result<()> {
         probe_rs::config::add_target_from_yaml(file)
             .with_context(|| format!("failed to load the chip description from {cdp}"))?;
     }
+    let image_instr_set;
+    let path = if let Some(path_buf) = &opt.path {
+        image_instr_set = None;
+        path_buf.clone()
+    } else {
+        let cargo_options = opt.cargo_options.to_cargo_options();
+        image_instr_set = target_instruction_set(opt.cargo_options.target.clone());
 
-    let cargo_options = opt.cargo_options.to_cargo_options();
-
-    let artifact = build_artifact(&work_dir, &cargo_options)?;
-
-    let path = artifact.path();
+        // Build the project, and extract the path of the built artifact.
+        build_artifact(&work_dir, &cargo_options)?.path().into()
+    };
 
     // Get the binary name (without extension) from the build artifact path
     let name = path.file_stem().and_then(|f| f.to_str()).ok_or_else(|| {
@@ -222,10 +230,6 @@ fn main_try(args: &[OsString], offset: UtcOffset) -> Result<()> {
     };
 
     if config.flashing.enabled {
-        // As opposed to cargo-flash, we do not have the option of suppliying an external image.
-        // This means we can just take the cargo target, if it's set.
-        let image_instr_set = target_instruction_set(opt.cargo_options.target.clone());
-
         let download_options = BinaryDownloadOptions {
             disable_progressbars: opt.disable_progressbars,
             disable_double_buffering: config.flashing.disable_double_buffering,
@@ -234,10 +238,10 @@ fn main_try(args: &[OsString], offset: UtcOffset) -> Result<()> {
             verify: config.flashing.verify,
         };
         let format_options = FormatOptions::default();
-        let loader = build_loader(&mut session, path, format_options, image_instr_set)?;
+        let loader = build_loader(&mut session, &path, format_options, image_instr_set)?;
         run_flash_download(
             &mut session,
-            path,
+            &path,
             &download_options,
             &probe_options,
             loader,
@@ -245,7 +249,7 @@ fn main_try(args: &[OsString], offset: UtcOffset) -> Result<()> {
         )?;
     }
 
-    let core_id = rtt::get_target_core_id(&mut session, path);
+    let core_id = rtt::get_target_core_id(&mut session, &path);
     if config.reset.enabled {
         let mut core = session.core(core_id)?;
         let halt_timeout = Duration::from_millis(500);
@@ -288,7 +292,7 @@ fn main_try(args: &[OsString], offset: UtcOffset) -> Result<()> {
 
     if config.rtt.enabled {
         // GDB is also using the session, so we do not lock on the outside.
-        run_rttui_app(name, &session, core_id, config, path, offset)?;
+        run_rttui_app(name, &session, core_id, config, &path, offset)?;
     }
 
     if let Some(gdb_thread_handle) = gdb_thread_handle {
diff --git a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_flash/mod.rs b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_flash/mod.rs
index 62c1afec..76701904 100644
--- a/probe-rs-tools/src/bin/probe-rs/cmd/cargo_flash/mod.rs
+++ b/probe-rs-tools/src/bin/probe-rs/cmd/cargo_flash/mod.rs
@@ -34,7 +34,7 @@ struct CliOptions {
     /// Default is `warn`. Possible choices are [error, warn, info, debug, trace].
     #[arg(value_name = "level", long)]
     pub log: Option<LevelFilter>,
-    /// The path to the file to be flashed.
+    /// The path to the file to be flashed. Setting this will ignore the cargo options.
     #[arg(value_name = "path", long)]
     pub path: Option<PathBuf>,
     /// The work directory from which cargo-flash should operate from.
diff --git a/probe-rs-tools/src/bin/probe-rs/util/common_options.rs b/probe-rs-tools/src/bin/probe-rs/util/common_options.rs
index f1cdcc63..080601cf 100644
--- a/probe-rs-tools/src/bin/probe-rs/util/common_options.rs
+++ b/probe-rs-tools/src/bin/probe-rs/util/common_options.rs
@@ -170,10 +170,10 @@ impl LoadedProbeOptions {
                     source: error,
                     path: cdp.clone(),
                 }
-            })
-        } else {
-            Ok(())
+            })?;
         }
+
+        Ok(())
     }
 
     /// Resolves a resultant target selector from passed [ProbeOptions].
diff --git a/probe-rs/src/config/registry.rs b/probe-rs/src/config/registry.rs
index f98ae049..22e923df 100644
--- a/probe-rs/src/config/registry.rs
+++ b/probe-rs/src/config/registry.rs
@@ -310,22 +310,27 @@ impl Registry {
         Target::new(family, &chip.name)
     }
 
-    fn add_target_from_yaml<R>(&mut self, yaml_reader: R) -> Result<(), RegistryError>
+    fn add_target_from_yaml<R>(&mut self, yaml_reader: R) -> Result<String, RegistryError>
     where
         R: Read,
     {
         let family: ChipFamily = serde_yaml::from_reader(yaml_reader)?;
 
-        family
-            .validate()
-            .map_err(|e| RegistryError::InvalidChipFamilyDefinition(Box::new(family.clone()), e))?;
+        if let Err(error) = family.validate() {
+            return Err(RegistryError::InvalidChipFamilyDefinition(
+                Box::new(family),
+                error,
+            ));
+        };
+
+        let family_name = family.name.clone();
 
         self.families
-            .retain(|old_family| !old_family.name.eq_ignore_ascii_case(&family.name));
+            .retain(|old_family| !old_family.name.eq_ignore_ascii_case(&family_name));
 
         self.families.push(family);
 
-        Ok(())
+        Ok(family_name)
     }
 }
 
@@ -385,7 +390,7 @@ pub(crate) fn get_target_by_chip_info(chip_info: ChipInfo) -> Result<Target, Reg
 /// const BUILTIN_TARGET_YAML: &[u8] = include_bytes!("/path/target.yaml");
 /// probe_rs::config::add_target_from_yaml(BUILTIN_TARGET_YAML)?;
 /// ```
-pub fn add_target_from_yaml<R>(yaml_reader: R) -> Result<(), RegistryError>
+pub fn add_target_from_yaml<R>(yaml_reader: R) -> Result<String, RegistryError>
 where
     R: Read,
 {
diff --git a/target-gen/Cargo.toml b/target-gen/Cargo.toml
index 4c0b8b5f..fcf1f746 100644
--- a/target-gen/Cargo.toml
+++ b/target-gen/Cargo.toml
@@ -15,7 +15,7 @@ license.workspace = true
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-probe-rs = { path = "../probe-rs", version = "0.24.0", default-features = false }
+probe-rs = { path = "../probe-rs", version = "0.24.0" }
 probe-rs-target = { path = "../probe-rs-target", version = "0.24.0", default-features = false }
 cmsis-pack = "0.7.0"
 goblin = { version = "0.8.2", default-features = false, features = [
diff --git a/target-gen/src/generate.rs b/target-gen/src/generate.rs
index 12cec8ff..15a3939a 100644
--- a/target-gen/src/generate.rs
+++ b/target-gen/src/generate.rs
@@ -9,7 +9,6 @@ use probe_rs_target::{
     RiscvCoreAccessOptions, TargetDescriptionSource, XtensaCoreAccessOptions,
 };
 use std::collections::HashMap;
-use std::ffi::OsStr;
 use std::{fs, io::Read, path::Path};
 
 pub(crate) enum Kind<'a, T>
@@ -31,7 +30,7 @@ where
                 let reader = archive.by_name(&path.to_string_lossy())?;
                 reader.bytes().collect::<std::io::Result<Vec<u8>>>()?
             }
-            Kind::Directory(dir) => std::fs::read(dir.join(path))?,
+            Kind::Directory(dir) => fs::read(dir.join(path))?,
         };
 
         Ok(buffer)
@@ -75,14 +74,13 @@ where
     T: std::io::Seek + std::io::Read,
 {
     // Forge a definition file for each device in the .pdsc file.
-    let pack_file_release = Some(pdsc.releases.latest_release().version.clone());
     let mut devices = pdsc.devices.0.into_iter().collect::<Vec<_>>();
     devices.sort_by(|a, b| a.0.cmp(&b.0));
 
-    for (device_name, device) in devices {
-        // Only process this, if this belongs to a supported family.
-        let currently_supported_chip_families = probe_rs::config::families();
+    // Only process this, if this belongs to a supported family.
+    let currently_supported_chip_families = probe_rs::config::families();
 
+    for (device_name, device) in devices {
         if only_supported_familes
             && !currently_supported_chip_families
                 .iter()
@@ -106,7 +104,7 @@ where
                 manufacturer: None,
                 generated_from_pack: true,
                 chip_detection: vec![],
-                pack_file_release: pack_file_release.clone(),
+                pack_file_release: Some(pdsc.releases.latest_release().version.clone()),
                 variants: Vec::new(),
                 flash_algorithms: Vec::new(),
                 source: TargetDescriptionSource::BuiltIn,
@@ -116,7 +114,7 @@ where
         };
 
         // Extract the flash algorithm, block & sector size and the erased byte value from the ELF binary.
-        let variant_flash_algorithms = device
+        let flash_algorithm_names = device
             .algorithms
             .iter()
             .filter_map(|flash_algorithm| {
@@ -124,11 +122,12 @@ where
                     Ok(algo) => {
                         // We add this algo directly to the algos of the family if it's not already added.
                         // Make sure we never add an algo twice to save file size.
+                        let algo_name = algo.name.clone();
                         if !family.flash_algorithms.contains(&algo) {
-                            family.flash_algorithms.push(algo.clone());
+                            family.flash_algorithms.push(algo);
                         }
 
-                        Some(algo)
+                        Some(algo_name)
                     }
                     Err(e) => {
                         log::warn!(
@@ -142,11 +141,6 @@ where
             })
             .collect::<Vec<_>>();
 
-        let flash_algorithm_names = variant_flash_algorithms
-            .iter()
-            .map(|fa| fa.name.to_string())
-            .collect::<Vec<_>>();
-
         // Sometimes the algos are referenced twice, for example in the multicore H7s
         // Deduplicate while keeping order.
         let flash_algorithm_names = flash_algorithm_names
@@ -227,34 +221,41 @@ fn core_to_probe_core(value: &Core) -> Result<CoreType, Error> {
     })
 }
 
-// one possible implementation of walking a directory only visiting files
+// Process all `.pdsc` files in the given directory.
 pub(crate) fn visit_dirs(path: &Path, families: &mut Vec<ChipFamily>) -> Result<()> {
-    // If we get a dir, look for all .pdsc files.
+    walk_files(path, &mut |path| {
+        if has_extension(path, "pack") {
+            log::info!("Found .pdsc file: {}", path.display());
+
+            let package = Package::from_path(path)
+                .context(format!("Failed to open .pdsc file {}.", path.display()))?;
+
+            extract_families::<fs::File>(package, Kind::Directory(path), families, false)
+                .context(format!("Failed to process .pdsc file {}.", path.display()))?;
+        }
+
+        Ok(())
+    })
+}
+
+fn walk_files(path: &Path, callback: &mut impl FnMut(&Path) -> Result<()>) -> Result<()> {
     for entry in fs::read_dir(path)? {
-        let entry = entry?;
-        let entry_path = entry.path();
+        let entry_path = entry?.path();
 
         if entry_path.is_dir() {
-            visit_dirs(&entry_path, families)?;
-        } else if entry_path.extension() == Some(OsStr::new("pdsc")) {
-            log::info!("Found .pdsc file: {}", path.display());
-
-            extract_families::<std::fs::File>(
-                Package::from_path(&entry_path)?,
-                Kind::Directory(path),
-                families,
-                false,
-            )
-            .context(format!(
-                "Failed to process .pdsc file {}.",
-                entry_path.display()
-            ))?;
+            walk_files(&entry_path, callback)?;
+        } else {
+            callback(&entry_path)?;
         }
     }
 
     Ok(())
 }
 
+fn has_extension(path: &Path, ext: &str) -> bool {
+    path.extension().map_or(false, |e| e == ext)
+}
+
 pub(crate) fn visit_file(path: &Path, families: &mut Vec<ChipFamily>) -> Result<()> {
     log::info!("Trying to open pack file: {}.", path.display());
     // If we get a file, try to unpack it.
@@ -426,7 +427,7 @@ where
             )
         })?;
 
-        if outpath.extension() == Some(OsStr::new("pdsc")) {
+        if has_extension(&outpath, "pdsc") {
             // We cannot return the file directly here,
             // because this leads to lifetime problems.
 
diff --git a/target-gen/src/main.rs b/target-gen/src/main.rs
index d0469628..1d1929f2 100644
--- a/target-gen/src/main.rs
+++ b/target-gen/src/main.rs
@@ -103,6 +103,9 @@ enum TargetGen {
         /// The address used as the start of flash memory area to perform test.
         #[clap(long = "test-address", value_parser = parse_u64)]
         test_start_sector_address: Option<u64>,
+        /// The name of the chip to use for the test, if there are multiple to choose from.
+        #[clap(long = "chip")]
+        chip: Option<String>,
     },
     /// Loads and updates target description from YAML files.
     Reformat {
@@ -155,11 +158,13 @@ async fn main() -> Result<()> {
             template_path,
             definition_export_path,
             test_start_sector_address,
+            chip,
         } => cmd_test(
             target_artifact.as_path(),
             template_path.as_path(),
             definition_export_path.as_path(),
             test_start_sector_address,
+            chip,
         )?,
         TargetGen::Reformat { yaml_path } => {
             if yaml_path.is_dir() {


Test patch:
diff --git a/changelog/added-chip-to-target-gen-test.md b/changelog/added-chip-to-target-gen-test.md
new file mode 100644
index 00000000..cf001caf
--- /dev/null
+++ b/changelog/added-chip-to-target-gen-test.md
@@ -0,0 +1,1 @@
+Added `--chip` to `target-gen test`
\ No newline at end of file
diff --git a/target-gen/src/commands/test.rs b/target-gen/src/commands/test.rs
index f86fde84..c664c476 100644
--- a/target-gen/src/commands/test.rs
+++ b/target-gen/src/commands/test.rs
@@ -12,7 +12,7 @@ use probe_rs::{
     },
     MemoryInterface, Permissions, Session,
 };
-use probe_rs_target::{ChipFamily, RawFlashAlgorithm};
+use probe_rs_target::RawFlashAlgorithm;
 use xshell::{cmd, Shell};
 
 use crate::commands::elf::cmd_elf;
@@ -24,6 +24,7 @@ pub fn cmd_test(
     template_path: &Path,
     definition_export_path: &Path,
     test_start_sector_address: Option<u64>,
+    chip: Option<String>,
 ) -> Result<()> {
     ensure_is_file(target_artifact)?;
     ensure_is_file(template_path)?;
@@ -59,161 +60,171 @@ pub fn cmd_test(
 
     // Add the target to the registry from the generated YAML file
     let file = File::open(Path::new(definition_export_path))?;
-    probe_rs::config::add_target_from_yaml(file)?;
-
-    // Likely there is only one chip family in the YAML file,
-    // but still iterate over all families and then all varaiants within the family
-    let families: Vec<ChipFamily> = probe_rs::config::families();
-    for family in families.iter() {
-        for chip in family.variants.iter() {
-            let target = chip.name.clone();
-            // We need to get the chip name so that special startup procedure can be used. (matched on name)
-            let mut session =
-                probe_rs::Session::auto_attach(target, Permissions::new().allow_erase_all())?;
-
-            // Register callback to update the progress.
-            let t = Rc::new(RefCell::new(Instant::now()));
-            let progress = FlashProgress::new(move |event| match event {
-                ProgressEvent::StartedProgramming { .. } => {
-                    let mut t = t.borrow_mut();
-                    *t = Instant::now();
-                }
-                ProgressEvent::StartedErasing => {
-                    let mut t = t.borrow_mut();
-                    *t = Instant::now();
-                }
-                ProgressEvent::FailedErasing => {
-                    println!("Failed erasing in {:?}", t.borrow().elapsed());
-                }
-                ProgressEvent::FinishedErasing => {
-                    println!("Finished erasing in {:?}", t.borrow().elapsed());
-                }
-                ProgressEvent::FailedProgramming => {
-                    println!("Failed programming in {:?}", t.borrow().elapsed());
-                }
-                ProgressEvent::FinishedProgramming => {
-                    println!("Finished programming in {:?}", t.borrow().elapsed());
-                }
-                ProgressEvent::DiagnosticMessage { message } => {
-                    let prefix = "Message".yellow();
-                    if message.ends_with('\n') {
-                        print!("{prefix}: {message}");
-                    } else {
-                        println!("{prefix}: {message}");
-                    }
-                }
-                _ => (),
-            });
-
-            let flash_algorithm = if let Some(test_start_sector_address) = test_start_sector_address
-            {
-                let predicate = |x: &&RawFlashAlgorithm| {
-                    x.flash_properties.address_range.start <= test_start_sector_address
-                        && test_start_sector_address < x.flash_properties.address_range.end
-                };
-                let error_message =
-                    anyhow!("No flash algorithm matching specified address can be found");
-                session
-                    .target()
-                    .flash_algorithms
-                    .iter()
-                    .find(predicate)
-                    .ok_or(error_message)?
+    let family_name = probe_rs::config::add_target_from_yaml(file)?;
+
+    let targets = probe_rs::config::get_targets_by_family_name(&family_name)
+        .with_context(|| format!("Failed to get targets of {family_name}"))?;
+
+    let target_name = match targets.len() {
+        0 => return Err(anyhow!("No targets found for family {family_name}")),
+        1 => &targets[0],
+        count if chip.is_none() => {
+            return Err(anyhow!(
+                "{count} targets found for family {family_name}: {targets:#?}. Specify the desired target with --chip."
+            ));
+        }
+        _ => {
+            let chip = chip.as_ref().unwrap();
+            targets
+                .iter()
+                .find(|target| *target == chip)
+                .with_context(|| format!("No target found for chip {chip}"))?
+        }
+    };
+
+    // We need to get the chip name so that special startup procedure can be used. (matched on name)
+    let mut session =
+        probe_rs::Session::auto_attach(target_name, Permissions::new().allow_erase_all())?;
+
+    // Register callback to update the progress.
+    let t = Rc::new(RefCell::new(Instant::now()));
+    let progress = FlashProgress::new(move |event| match event {
+        ProgressEvent::StartedProgramming { .. } => {
+            let mut t = t.borrow_mut();
+            *t = Instant::now();
+        }
+        ProgressEvent::StartedErasing => {
+            let mut t = t.borrow_mut();
+            *t = Instant::now();
+        }
+        ProgressEvent::FailedErasing => {
+            println!("Failed erasing in {:?}", t.borrow().elapsed());
+        }
+        ProgressEvent::FinishedErasing => {
+            println!("Finished erasing in {:?}", t.borrow().elapsed());
+        }
+        ProgressEvent::FailedProgramming => {
+            println!("Failed programming in {:?}", t.borrow().elapsed());
+        }
+        ProgressEvent::FinishedProgramming => {
+            println!("Finished programming in {:?}", t.borrow().elapsed());
+        }
+        ProgressEvent::DiagnosticMessage { message } => {
+            let prefix = "Message".yellow();
+            if message.ends_with('\n') {
+                print!("{prefix}: {message}");
             } else {
-                &session.target().flash_algorithms[0]
-            };
-            let flash_properties = flash_algorithm.flash_properties.clone();
-            let start_address = flash_properties.address_range.start;
-            let end_address = flash_properties.address_range.end;
-            let data_size = flash_properties.page_size;
-            let erased_state = flash_properties.erased_byte_value;
-            let sector_size = flash_properties.sectors[0].size;
-
-            let test_start_sector_address = test_start_sector_address.unwrap_or(start_address);
-            if test_start_sector_address < start_address
-                || test_start_sector_address > start_address + end_address - sector_size * 2
-                || test_start_sector_address % sector_size != 0
-            {
-                return Err(anyhow!(
-                    "test_start_sector_address must be sector aligned address pointing flash range"
-                ));
+                println!("{prefix}: {message}");
             }
-            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(),
-                EraseSectors(test_start_sector_index, 2),
-            )?;
-            let mut readback = vec![0; (sector_size * 2) as usize];
-            session
-                .core(0)?
-                .read_8(test_start_sector_address, &mut readback)?;
-            assert!(
-                !readback.iter().any(|v| *v != erased_state),
-                "Not all sectors were erased"
-            );
-
-            let mut loader = session.target().flash_loader();
-            let data = (0..data_size).map(|n| (n % 256) as u8).collect::<Vec<_>>();
-            loader.add_data(test_start_sector_address + 1, &data)?;
-            run_flash_download(&mut session, loader, progress.clone(), true)?;
-            let mut readback = vec![0; data_size as usize];
-            session
-                .core(0)?
-                .read_8(test_start_sector_address + 1, &mut readback)?;
-            assert_eq!(readback, data);
-
-            println!("{test}: Erasing the entire chip and writing two pages ...");
-            run_flash_erase(&mut session, progress.clone(), EraseAll)?;
-            let mut readback = vec![0; (sector_size * 2) as usize];
-            session
-                .core(0)?
-                .read_8(test_start_sector_address, &mut readback)?;
-            assert!(
-                !readback.iter().any(|v| *v != erased_state),
-                "Not all sectors were erased"
-            );
-
-            let mut loader = session.target().flash_loader();
-            let data = (0..data_size).map(|n| (n % 256) as u8).collect::<Vec<_>>();
-            loader.add_data(test_start_sector_address + 1, &data)?;
-            run_flash_download(&mut session, loader, progress.clone(), true)?;
-            let mut readback = vec![0; data_size as usize];
-            session
-                .core(0)?
-                .read_8(test_start_sector_address + 1, &mut readback)?;
-            assert_eq!(readback, data);
-
-            println!("{test}: Erasing sectorwise and writing two pages double buffered ...");
-            run_flash_erase(
-                &mut session,
-                progress.clone(),
-                EraseSectors(test_start_sector_index, 2),
-            )?;
-            let mut readback = vec![0; (sector_size * 2) as usize];
-            session
-                .core(0)?
-                .read_8(test_start_sector_address, &mut readback)?;
-            assert!(
-                !readback.iter().any(|v| *v != erased_state),
-                "Not all sectors were erased"
-            );
-
-            let mut loader = session.target().flash_loader();
-            let data = (0..data_size).map(|n| (n % 256) as u8).collect::<Vec<_>>();
-            loader.add_data(test_start_sector_address + 1, &data)?;
-            run_flash_download(&mut session, loader, progress, false)?;
-            let mut readback = vec![0; data_size as usize];
-            session
-                .core(0)?
-                .read_8(test_start_sector_address + 1, &mut readback)?;
-            assert_eq!(readback, data);
         }
+        _ => (),
+    });
+
+    let flash_algorithm = if let Some(test_start_sector_address) = test_start_sector_address {
+        let predicate = |x: &&RawFlashAlgorithm| {
+            x.flash_properties.address_range.start <= test_start_sector_address
+                && test_start_sector_address < x.flash_properties.address_range.end
+        };
+        let error_message = anyhow!("No flash algorithm matching specified address can be found");
+        session
+            .target()
+            .flash_algorithms
+            .iter()
+            .find(predicate)
+            .ok_or(error_message)?
+    } else {
+        &session.target().flash_algorithms[0]
+    };
+    let flash_properties = &flash_algorithm.flash_properties;
+    let start_address = flash_properties.address_range.start;
+    let end_address = flash_properties.address_range.end;
+    let data_size = flash_properties.page_size;
+    let erased_state = flash_properties.erased_byte_value;
+    let sector_size = flash_properties.sectors[0].size;
+
+    let test_start_sector_address = test_start_sector_address.unwrap_or(start_address);
+    if test_start_sector_address < start_address
+        || test_start_sector_address > start_address + end_address - sector_size * 2
+        || test_start_sector_address % sector_size != 0
+    {
+        return Err(anyhow!(
+            "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(),
+        EraseSectors(test_start_sector_index, 2),
+    )?;
+    let mut readback = vec![0; (sector_size * 2) as usize];
+    session
+        .core(0)?
+        .read_8(test_start_sector_address, &mut readback)?;
+    assert!(
+        !readback.iter().any(|v| *v != erased_state),
+        "Not all sectors were erased"
+    );
+
+    let mut loader = session.target().flash_loader();
+    let data = (0..data_size).map(|n| (n % 256) as u8).collect::<Vec<_>>();
+    loader.add_data(test_start_sector_address + 1, &data)?;
+    run_flash_download(&mut session, loader, progress.clone(), true)?;
+    let mut readback = vec![0; data_size as usize];
+    session
+        .core(0)?
+        .read_8(test_start_sector_address + 1, &mut readback)?;
+    assert_eq!(readback, data);
+
+    println!("{test}: Erasing the entire chip and writing two pages ...");
+    run_flash_erase(&mut session, progress.clone(), EraseAll)?;
+    let mut readback = vec![0; (sector_size * 2) as usize];
+    session
+        .core(0)?
+        .read_8(test_start_sector_address, &mut readback)?;
+    assert!(
+        !readback.iter().any(|v| *v != erased_state),
+        "Not all sectors were erased"
+    );
+
+    let mut loader = session.target().flash_loader();
+    let data = (0..data_size).map(|n| (n % 256) as u8).collect::<Vec<_>>();
+    loader.add_data(test_start_sector_address + 1, &data)?;
+    run_flash_download(&mut session, loader, progress.clone(), true)?;
+    let mut readback = vec![0; data_size as usize];
+    session
+        .core(0)?
+        .read_8(test_start_sector_address + 1, &mut readback)?;
+    assert_eq!(readback, data);
+
+    println!("{test}: Erasing sectorwise and writing two pages double buffered ...");
+    run_flash_erase(
+        &mut session,
+        progress.clone(),
+        EraseSectors(test_start_sector_index, 2),
+    )?;
+    let mut readback = vec![0; (sector_size * 2) as usize];
+    session
+        .core(0)?
+        .read_8(test_start_sector_address, &mut readback)?;
+    assert!(
+        !readback.iter().any(|v| *v != erased_state),
+        "Not all sectors were erased"
+    );
+
+    let mut loader = session.target().flash_loader();
+    let data = (0..data_size).map(|n| (n % 256) as u8).collect::<Vec<_>>();
+    loader.add_data(test_start_sector_address + 1, &data)?;
+    run_flash_download(&mut session, loader, progress, false)?;
+    let mut readback = vec![0; data_size as usize];
+    session
+        .core(0)?
+        .read_8(test_start_sector_address + 1, &mut readback)?;
+    assert_eq!(readback, data);
 
     Ok(())
 }