1#![allow(clippy::needless_doctest_main)]
2use crate::ArtifactError;
35
36use std::{fs::File, io::Write, path::Path, path::PathBuf};
37
38use byte_unit::Byte;
39use clap;
40use probe_rs::{
41 config::{RegistryError, TargetSelector},
42 flashing::{FileDownloadError, FlashError, FlashLoader},
43 DebugProbeError, DebugProbeSelector, FakeProbe, Permissions, Probe, Session, Target,
44 WireProtocol,
45};
46
47#[derive(Debug, clap::Parser)]
49pub struct FlashOptions {
50 #[clap(name = "list-chips", long = "list-chips")]
51 pub list_chips: bool,
52 #[clap(
53 name = "list-probes",
54 long = "list-probes",
55 help = "Lists all the connected probes that can be seen.\n\
56 If udev rules or permissions are wrong, some probes might not be listed."
57 )]
58 pub list_probes: bool,
59 #[clap(name = "disable-progressbars", long = "disable-progressbars")]
60 pub disable_progressbars: bool,
61 #[clap(
62 long = "disable-double-buffering",
63 help = "Use this flag to disable double-buffering when downloading flash data. If download fails during\
64 programming with timeout errors, try this option."
65 )]
66 pub disable_double_buffering: bool,
67 #[clap(
68 name = "reset-halt",
69 long = "reset-halt",
70 help = "Use this flag to reset and halt (instead of just a reset) the attached core after flashing the target."
71 )]
72 pub reset_halt: bool,
73 #[clap(
74 name = "level",
75 long = "log",
76 help = "Use this flag to set the log level.\n\
77 Default is `warning`. Possible choices are [error, warning, info, debug, trace]."
78 )]
79 pub log: Option<log::Level>,
80 #[clap(
81 name = "restore-unwritten",
82 long = "restore-unwritten",
83 help = "Enable this flag to restore all bytes erased in the sector erase but not overwritten by any page."
84 )]
85 pub restore_unwritten: bool,
86 #[clap(
87 name = "filename",
88 long = "flash-layout",
89 help = "Requests the flash builder to output the layout into the given file in SVG format."
90 )]
91 pub flash_layout_output_path: Option<String>,
92 #[clap(
93 name = "elf file",
94 long = "elf",
95 help = "The path to the ELF file to be flashed."
96 )]
97 pub elf: Option<PathBuf>,
98 #[clap(
99 name = "directory",
100 long = "work-dir",
101 help = "The work directory from which cargo-flash should operate from."
102 )]
103 pub work_dir: Option<PathBuf>,
104 #[clap(flatten)]
105 pub cargo_options: CargoOptions,
107 #[clap(flatten)]
108 pub probe_options: ProbeOptions,
110}
111
112impl FlashOptions {
113 pub fn early_exit(&self, f: impl Write) -> Result<bool, OperationError> {
120 if self.list_probes {
121 list_connected_probes(f)?;
122 return Ok(true);
123 }
124
125 if self.list_chips {
126 print_families(f)?;
127 return Ok(true);
128 }
129
130 Ok(false)
131 }
132}
133
134#[derive(clap::Parser, Debug)]
136pub struct ProbeOptions {
137 #[structopt(long)]
138 pub chip: Option<String>,
139 #[structopt(name = "chip description file path", long = "chip-description-path")]
140 pub chip_description_path: Option<PathBuf>,
141
142 #[structopt(long, help_heading = "PROBE CONFIGURATION")]
144 pub protocol: Option<WireProtocol>,
145
146 #[structopt(long = "probe", help_heading = "PROBE CONFIGURATION")]
150 pub probe_selector: Option<DebugProbeSelector>,
151 #[clap(
152 long,
153 help = "The protocol speed in kHz.",
154 help_heading = "PROBE CONFIGURATION"
155 )]
156 pub speed: Option<u32>,
157 #[structopt(
158 long = "connect-under-reset",
159 help = "Use this flag to assert the nreset & ntrst pins during attaching the probe to the chip."
160 )]
161 pub connect_under_reset: bool,
162 #[structopt(long = "dry-run")]
163 pub dry_run: bool,
164 #[structopt(
165 long = "allow-erase-all",
166 help = "Use this flag to allow all memory, including security keys and 3rd party firmware, to be erased \
167 even when it has read-only protection."
168 )]
169 pub allow_erase_all: bool,
170}
171
172impl ProbeOptions {
173 pub fn maybe_load_chip_desc(&self) -> Result<(), OperationError> {
178 if let Some(ref cdp) = self.chip_description_path {
179 let file = File::open(Path::new(cdp))?;
180 probe_rs::config::add_target_from_yaml(file).map_err(|error| {
181 OperationError::FailedChipDescriptionParsing {
182 source: error,
183 path: cdp.clone(),
184 }
185 })
186 } else {
187 Ok(())
188 }
189 }
190
191 pub fn get_target_selector(&self) -> Result<TargetSelector, OperationError> {
193 let target = if let Some(chip_name) = &self.chip {
194 let target = probe_rs::config::get_target_by_name(chip_name).map_err(|error| {
195 OperationError::ChipNotFound {
196 source: error,
197 name: chip_name.clone(),
198 }
199 })?;
200
201 TargetSelector::Specified(target)
202 } else {
203 TargetSelector::Auto
204 };
205
206 Ok(target)
207 }
208
209 pub fn attach_probe(&self) -> Result<Probe, OperationError> {
211 let mut probe = {
212 if self.dry_run {
213 Probe::from_specific_probe(Box::new(FakeProbe::new()));
214 }
215
216 match &self.probe_selector {
219 Some(selector) => {
220 Probe::open(selector.clone()).map_err(OperationError::FailedToOpenProbe)
221 }
222 None => {
223 let list = Probe::list_all();
226 if list.len() > 1 {
227 return Err(OperationError::MultipleProbesFound { number: list.len() });
228 }
229
230 if let Some(info) = list.first() {
231 Probe::open(info).map_err(OperationError::FailedToOpenProbe)
232 } else {
233 Err(OperationError::NoProbesFound)
234 }
235 }
236 }
237 }?;
238
239 if let Some(protocol) = self.protocol {
240 probe.select_protocol(protocol).map_err(|error| {
242 OperationError::FailedToSelectProtocol {
243 source: error,
244 protocol,
245 }
246 })?;
247 }
248
249 if let Some(speed) = self.speed {
250 let _actual_speed = probe.set_speed(speed).map_err(|error| {
251 OperationError::FailedToSelectProtocolSpeed {
252 source: error,
253 speed,
254 }
255 })?;
256 }
257
258 Ok(probe)
259 }
260
261 pub fn attach_session(
264 &self,
265 probe: Probe,
266 target: TargetSelector,
267 ) -> Result<Session, OperationError> {
268 let mut permissions = Permissions::new();
269 if self.allow_erase_all {
270 permissions = permissions.allow_erase_all();
271 }
272
273 let session = if self.connect_under_reset {
274 probe.attach_under_reset(target, permissions)
275 } else {
276 probe.attach(target, permissions)
277 }
278 .map_err(|error| OperationError::AttachingFailed {
279 source: error,
280 connect_under_reset: self.connect_under_reset,
281 })?;
282
283 Ok(session)
284 }
285
286 pub fn simple_attach(&self) -> Result<Session, OperationError> {
289 let target = self.get_target_selector()?;
290 let probe = self.attach_probe()?;
291 let session = self.attach_session(probe, target)?;
292
293 Ok(session)
294 }
295
296 pub fn build_flashloader(
300 &self,
301 session: &mut Session,
302 elf_path: &Path,
303 ) -> Result<FlashLoader, OperationError> {
304 let target = session.target();
305
306 let mut loader = FlashLoader::new(target.memory_map.to_vec(), target.source().clone());
308
309 let mut file = File::open(elf_path).map_err(|error| OperationError::FailedToOpenElf {
311 source: error,
312 path: elf_path.to_path_buf(),
313 })?;
314
315 loader
317 .load_elf_data(&mut file)
318 .map_err(OperationError::FailedToLoadElfData)?;
319
320 Ok(loader)
321 }
322}
323
324#[derive(clap::Parser, Debug, Default)]
326pub struct CargoOptions {
327 #[clap(name = "binary", long = "bin", hide = true)]
328 pub bin: Option<String>,
329 #[clap(name = "example", long = "example", hide = true)]
330 pub example: Option<String>,
331 #[clap(name = "package", short = 'p', long = "package", hide = true)]
332 pub package: Option<String>,
333 #[clap(name = "release", long = "release", hide = true)]
334 pub release: bool,
335 #[clap(name = "target", long = "target", hide = true)]
336 pub target: Option<String>,
337 #[clap(name = "PATH", long = "manifest-path", hide = true)]
338 pub manifest_path: Option<PathBuf>,
339 #[clap(long, hide = true)]
340 pub no_default_features: bool,
341 #[clap(long, hide = true)]
342 pub all_features: bool,
343 #[clap(long, hide = true)]
344 pub features: Vec<String>,
345 #[clap(hide = true)]
346 pub trailing_opts: Vec<String>,
350}
351
352impl CargoOptions {
353 pub fn help_message(bin: &str) -> String {
368 format!(
369 r#"
370CARGO BUILD OPTIONS:
371
372 The following options are forwarded to 'cargo build':
373
374 --bin
375 --example
376 -p, --package
377 --release
378 --target
379 --manifest-path
380 --no-default-features
381 --all-features
382 --features
383
384 Additionally, all options passed after a sentinel '--'
385 are also forwarded.
386
387 For example, if you run the command '{bin} --release -- \
388 --some-cargo-flag', this means that 'cargo build \
389 --release --some-cargo-flag' will be called.
390"#
391 )
392 }
393
394 pub fn to_cargo_options(&self) -> Vec<String> {
398 let mut args: Vec<String> = vec![];
400 macro_rules! maybe_push_str_opt {
401 ($field:expr, $name:expr) => {{
402 if let Some(value) = $field {
403 args.push(format!("--{}", stringify!($name)));
404 args.push(value.clone());
405 }
406 }};
407 }
408
409 maybe_push_str_opt!(&self.bin, bin);
410 maybe_push_str_opt!(&self.example, example);
411 maybe_push_str_opt!(&self.package, package);
412 if self.release {
413 args.push("--release".to_string());
414 }
415 maybe_push_str_opt!(&self.target, target);
416 if let Some(path) = &self.manifest_path {
417 args.push("--manifest-path".to_string());
418 args.push(path.display().to_string());
419 }
420 if self.no_default_features {
421 args.push("--no-default-features".to_string());
422 }
423 if self.all_features {
424 args.push("--all-features".to_string());
425 }
426 if !self.features.is_empty() {
427 args.push("--features".to_string());
428 args.push(self.features.join(","));
429 }
430
431 args.append(&mut self.trailing_opts.clone());
433
434 args
435 }
436}
437
438#[derive(Debug, thiserror::Error)]
439pub enum OperationError {
440 #[error("No connected probes were found.")]
441 NoProbesFound,
442 #[error("Failed to list the target descriptions.")]
443 FailedToReadFamilies(#[source] RegistryError),
444 #[error("Failed to open the ELF file '{path}' for flashing.")]
445 FailedToOpenElf {
446 #[source]
447 source: std::io::Error,
448 path: PathBuf,
449 },
450 #[error("Failed to load the ELF data.")]
451 FailedToLoadElfData(#[source] FileDownloadError),
452 #[error("Failed to open the debug probe.")]
453 FailedToOpenProbe(#[source] DebugProbeError),
454 #[error("{number} probes were found.")]
455 MultipleProbesFound { number: usize },
456 #[error("The flashing procedure failed for '{path}'.")]
457 FlashingFailed {
458 #[source]
459 source: FlashError,
460 target: Box<Target>, target_spec: Option<String>,
462 path: PathBuf,
463 },
464 #[error("Failed to parse the chip description '{path}'.")]
465 FailedChipDescriptionParsing {
466 #[source]
467 source: RegistryError,
468 path: PathBuf,
469 },
470 #[error("Failed to change the working directory to '{path}'.")]
471 FailedToChangeWorkingDirectory {
472 #[source]
473 source: std::io::Error,
474 path: PathBuf,
475 },
476 #[error("Failed to build the cargo project at '{path}'.")]
477 FailedToBuildExternalCargoProject {
478 #[source]
479 source: ArtifactError,
480 path: PathBuf,
481 },
482 #[error("Failed to build the cargo project.")]
483 FailedToBuildCargoProject(#[source] ArtifactError),
484 #[error("The chip '{name}' was not found in the database.")]
485 ChipNotFound {
486 #[source]
487 source: RegistryError,
488 name: String,
489 },
490 #[error("The protocol '{protocol}' could not be selected.")]
491 FailedToSelectProtocol {
492 #[source]
493 source: DebugProbeError,
494 protocol: WireProtocol,
495 },
496 #[error("The protocol speed could not be set to '{speed}' kHz.")]
497 FailedToSelectProtocolSpeed {
498 #[source]
499 source: DebugProbeError,
500 speed: u32,
501 },
502 #[error("Connecting to the chip was unsuccessful.")]
503 AttachingFailed {
504 #[source]
505 source: probe_rs::Error,
506 connect_under_reset: bool,
507 },
508 #[error("Failed to get a handle to the first core.")]
509 AttachingToCoreFailed(#[source] probe_rs::Error),
510 #[error("The reset of the target failed.")]
511 TargetResetFailed(#[source] probe_rs::Error),
512 #[error("The target could not be reset and halted.")]
513 TargetResetHaltFailed(#[source] probe_rs::Error),
514 #[error("Failed to write to file")]
515 IOError(#[source] std::io::Error),
516 #[error("probe-rs API was called in the wrong order.")]
517 InvalidAPIOrder,
518 #[error("Failed to parse CLI arguments.")]
519 CliArgument(#[from] clap::Error),
520}
521
522impl From<std::io::Error> for OperationError {
523 fn from(e: std::io::Error) -> Self {
524 OperationError::IOError(e)
525 }
526}
527
528pub fn list_connected_probes(mut f: impl Write) -> Result<(), std::io::Error> {
530 let probes = Probe::list_all();
531
532 if !probes.is_empty() {
533 writeln!(f, "The following debug probes were found:")?;
534 for (num, link) in probes.iter().enumerate() {
535 writeln!(f, "[{num}]: {link:?}")?;
536 }
537 } else {
538 writeln!(f, "No debug probes were found.")?;
539 }
540
541 Ok(())
542}
543
544pub fn print_families(mut f: impl Write) -> Result<(), OperationError> {
547 writeln!(f, "Available chips:")?;
548 for family in probe_rs::config::families().map_err(OperationError::FailedToReadFamilies)? {
549 writeln!(f, "{}", &family.name)?;
550 writeln!(f, " Variants:")?;
551 for variant in family.variants() {
552 writeln!(f, " {}", variant.name)?;
553 }
554 }
555 Ok(())
556}
557
558fn get_range_len(range: &std::ops::Range<u64>) -> u64 {
559 range.end - range.start
560}
561
562pub fn print_chip_info(name: impl AsRef<str>, mut f: impl Write) -> anyhow::Result<()> {
565 writeln!(f, "{}", name.as_ref())?;
566 let target = probe_rs::config::get_target_by_name(name)?;
567 writeln!(f, "Cores ({}):", target.cores.len())?;
568 for core in target.cores {
569 writeln!(
570 f,
571 " - {} ({:?})",
572 core.name.to_ascii_lowercase(),
573 core.core_type
574 )?;
575 }
576 for memory in target.memory_map {
577 match memory {
578 probe_rs::config::MemoryRegion::Ram(region) => writeln!(
579 f,
580 "RAM: {:#010x?} ({})",
581 ®ion.range,
582 Byte::from_bytes(get_range_len(®ion.range) as u128).get_appropriate_unit(true)
583 )?,
584 probe_rs::config::MemoryRegion::Generic(region) => writeln!(
585 f,
586 "Generic: {:#010x?} ({})",
587 ®ion.range,
588 Byte::from_bytes(get_range_len(®ion.range) as u128).get_appropriate_unit(true)
589 )?,
590 probe_rs::config::MemoryRegion::Nvm(region) => writeln!(
591 f,
592 "NVM: {:#010x?} ({})",
593 ®ion.range,
594 Byte::from_bytes(get_range_len(®ion.range) as u128).get_appropriate_unit(true)
595 )?,
596 };
597 }
598 Ok(())
599}
600
601#[cfg(test)]
602mod tests {
603 use super::*;
604
605 #[test]
606 fn to_cargo_options() {
607 assert_eq!(
608 CargoOptions {
609 bin: Some("foobar".into()),
610 example: Some("foobar".into()),
611 package: Some("foobar".into()),
612 release: true,
613 target: Some("foobar".into()),
614 manifest_path: Some("/tmp/Cargo.toml".into()),
615 no_default_features: true,
616 all_features: true,
617 features: vec!["feat1".into(), "feat2".into()],
618 trailing_opts: vec!["--some-cargo-option".into()],
619 }
620 .to_cargo_options(),
621 [
622 "--bin",
623 "foobar",
624 "--example",
625 "foobar",
626 "--package",
627 "foobar",
628 "--release",
629 "--target",
630 "foobar",
631 "--manifest-path",
632 "/tmp/Cargo.toml",
633 "--no-default-features",
634 "--all-features",
635 "--features",
636 "feat1,feat2",
637 "--some-cargo-option",
638 ]
639 );
640 }
641}