Skip to main content

bmputil/
flasher.rs

1// SPDX-License-Identifier: MIT OR Apache-2.0
2// SPDX-FileCopyrightText: 2022-2025 1BitSquared <info@1bitsquared.com>
3// SPDX-FileContributor: Written by Mikaela Szekely <mikaela.szekely@qyriad.me>
4// SPDX-FileContributor: Modified by Rachel Mant <git@dragonmux.network>
5
6use std::io::Write;
7use std::path::PathBuf;
8use std::rc::Rc;
9use std::thread;
10use std::time::Duration;
11
12use color_eyre::eyre::{Context, Result, eyre};
13use color_eyre::owo_colors::OwoColorize;
14use indicatif::{ProgressBar, ProgressStyle};
15use log::{debug, error, info, warn};
16
17use crate::bmp::{self, BmpDevice};
18use crate::firmware_file::FirmwareFile;
19use crate::firmware_type::FirmwareType;
20use crate::usb::PortId;
21use crate::{AllowDangerous, BmpParams, FlashParams};
22
23pub struct Firmware
24{
25	firmware_type: FirmwareType,
26	firmware_file: FirmwareFile,
27}
28
29impl Firmware
30{
31	pub fn new<Params>(params: &Params, device: &BmpDevice, firmware_file: FirmwareFile) -> Result<Self>
32	where
33		Params: BmpParams + FlashParams,
34	{
35		Ok(Self {
36			firmware_type: Self::determine_firmware_type(params, device, &firmware_file)?,
37			firmware_file,
38		})
39	}
40
41	// XXX: Move me to FirmwareFile?
42	fn determine_firmware_type<Params>(
43		params: &Params,
44		device: &BmpDevice,
45		firmware_file: &FirmwareFile,
46	) -> Result<FirmwareType>
47	where
48		Params: BmpParams + FlashParams,
49	{
50		// Figure out what kind of firmware we're being asked to work with here
51		// Using the platform to determine the link address.
52		let platform = device.platform();
53		let firmware_type =
54			FirmwareType::detect_from_firmware(platform, firmware_file).wrap_err("detecting firmware type")?;
55
56		debug!("Firmware file was detected as {}", firmware_type);
57
58		// But allow the user to override that type, if they *really* know what they are doing.
59		let firmware_type = match params.override_firmware_type() {
60			Some(override_firmware_type) => {
61				match params.allow_dangerous_options() {
62					AllowDangerous::Really => warn!(
63						"Overriding firmware-type detection and flashing to user-specified location ({}) instead!",
64						override_firmware_type
65					),
66					AllowDangerous::Never => {
67						eprintln!(
68							"{} --override-firmware-type is used to override the firmware type detection and flash a \
69							 firmware binary to a location other than the one that it seems to be designed for.\nThis \
70							 is a potentially destructive operation and can result in an unbootable device! (can \
71							 require a second, external JTAG debugger and manual wiring to fix!)\n\nDo not use this \
72							 option unless you are a firmware developer and really know what you are doing!\n\nIf you \
73							 are sure this is really what you want to do, run again with \
74							 --allow-dangerous-options=really",
75							"WARNING:".red()
76						);
77						std::process::exit(1);
78					},
79				}
80				override_firmware_type
81			},
82			None => firmware_type,
83		};
84
85		Ok(firmware_type)
86	}
87
88	pub fn program_firmware(&self, device: &mut BmpDevice) -> Result<()>
89	{
90		// Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda
91		let firmware_type = self.firmware_type;
92		// Pull out the length of the firmware image and make it a u64
93		let firmware_length = self.firmware_file.len() as u64;
94
95		// We need an Rc<T> as [`dfu_core::sync::DfuSync`] requires `progress` to be 'static,
96		// so it must be moved into the closure. However, since we need to call .finish() here,
97		// it must be owned by both. Hence: Rc<T>.
98		// Default template: `{wide_bar} {pos}/{len}`.
99		let progress_bar = ProgressBar::new(firmware_length).with_style(
100			ProgressStyle::default_bar()
101				.template(" {percent:>3}% |{bar:50}| {bytes}/{total_bytes} [{binary_bytes_per_sec} {elapsed}]")
102				.unwrap(),
103		);
104		let progress_bar = Rc::new(progress_bar);
105		let enclosed = Rc::clone(&progress_bar);
106
107		let result = device.download(&self.firmware_file, firmware_type, move |flash_pos_delta| {
108			// Don't actually print flashing until the erasing has finished.
109			if enclosed.position() == 0 {
110				if firmware_type == FirmwareType::Application {
111					enclosed.println("Flashing...");
112				} else {
113					enclosed.println("Flashing bootloader...");
114				}
115			}
116			enclosed.inc(flash_pos_delta as u64);
117		});
118		progress_bar.finish();
119		let dfu_iface = result?;
120		info!("Flash complete!");
121
122		if progress_bar.position() == firmware_length {
123			device.reboot(dfu_iface)
124		} else {
125			Err(eyre!("Failed to flash device, download incomplete"))
126		}
127	}
128}
129
130fn check_programming(port: PortId) -> Result<()>
131{
132	let dev = bmp::wait_for_probe_reboot(port, Duration::from_secs(5), "flash").inspect_err(|_| {
133		error!("Black Magic Probe did not re-enumerate after flashing! Invalid firmware?");
134	})?;
135
136	// Now the device has come back, we need to see if the firmware programming cycle succeeded.
137	// This starts by extracting the firmware identity string to check
138	let identity = dev.firmware_identity().inspect_err(|_| {
139		error!("Error reading firmware version after flash! Invalid firmware?");
140	})?;
141
142	println!(
143		"Black Magic Probe successfully rebooted into firmware version {}",
144		identity.version
145	);
146
147	Ok(())
148}
149
150pub fn flash_probe<Params>(params: &Params, mut device: BmpDevice, file_name: PathBuf) -> Result<()>
151where
152	Params: BmpParams + FlashParams,
153{
154	let firmware_file = FirmwareFile::from_path(&file_name)?;
155
156	// Grab the the port the probe can be found on, which we need to re-find the probe after rebooting.
157	let port = device.port();
158
159	let firmware = Firmware::new(params, &device, firmware_file)?;
160
161	// If we can't get the string descriptors, try to go ahead with flashing anyway.
162	// It's unlikely that other control requests will succeed, but the OS might be messing with
163	// the string descriptor stuff.
164	let _ = writeln!(std::io::stdout(), "Found: {}", device).map_err(|e| {
165		error!(
166			"Failed to read string data from Black Magic Probe: {}\nTrying to continue anyway...",
167			e
168		);
169	});
170
171	firmware.program_firmware(&mut device)?;
172
173	// Programming triggers a probe reboot, so after this we have to get libusb to
174	// drop the device, wait a little for the probe to go away and then wait on the probe to come back.
175	drop(device);
176	thread::sleep(Duration::from_millis(250));
177
178	check_programming(port)
179}