use std::io::Write;
use std::path::PathBuf;
use std::rc::Rc;
use std::thread;
use std::time::Duration;
use color_eyre::eyre::{Context, Result, eyre};
use color_eyre::owo_colors::OwoColorize;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, info, warn};
use crate::bmp::{self, BmpDevice};
use crate::firmware_file::FirmwareFile;
use crate::firmware_type::FirmwareType;
use crate::usb::PortId;
use crate::{AllowDangerous, BmpParams, FlashParams};
pub struct Firmware
{
firmware_type: FirmwareType,
firmware_file: FirmwareFile,
}
impl Firmware
{
pub fn new<Params>(params: &Params, device: &BmpDevice, firmware_file: FirmwareFile) -> Result<Self>
where
Params: BmpParams + FlashParams,
{
Ok(Self {
firmware_type: Self::determine_firmware_type(params, device, &firmware_file)?,
firmware_file,
})
}
fn determine_firmware_type<Params>(
params: &Params,
device: &BmpDevice,
firmware_file: &FirmwareFile,
) -> Result<FirmwareType>
where
Params: BmpParams + FlashParams,
{
let platform = device.platform();
let firmware_type =
FirmwareType::detect_from_firmware(platform, firmware_file).wrap_err("detecting firmware type")?;
debug!("Firmware file was detected as {}", firmware_type);
let firmware_type = match params.override_firmware_type() {
Some(override_firmware_type) => {
match params.allow_dangerous_options() {
AllowDangerous::Really => warn!(
"Overriding firmware-type detection and flashing to user-specified location ({}) instead!",
override_firmware_type
),
AllowDangerous::Never => {
eprintln!(
"{} --override-firmware-type is used to override the firmware type detection and flash a \
firmware binary to a location other than the one that it seems to be designed for.\nThis \
is a potentially destructive operation and can result in an unbootable device! (can \
require a second, external JTAG debugger and manual wiring to fix!)\n\nDo not use this \
option unless you are a firmware developer and really know what you are doing!\n\nIf you \
are sure this is really what you want to do, run again with \
--allow-dangerous-options=really",
"WARNING:".red()
);
std::process::exit(1);
},
}
override_firmware_type
},
None => firmware_type,
};
Ok(firmware_type)
}
pub fn program_firmware(&self, device: &mut BmpDevice) -> Result<()>
{
let firmware_type = self.firmware_type;
let firmware_length = self.firmware_file.len() as u64;
let progress_bar = ProgressBar::new(firmware_length).with_style(
ProgressStyle::default_bar()
.template(" {percent:>3}% |{bar:50}| {bytes}/{total_bytes} [{binary_bytes_per_sec} {elapsed}]")
.unwrap(),
);
let progress_bar = Rc::new(progress_bar);
let enclosed = Rc::clone(&progress_bar);
let result = device.download(&self.firmware_file, firmware_type, move |flash_pos_delta| {
if enclosed.position() == 0 {
if firmware_type == FirmwareType::Application {
enclosed.println("Flashing...");
} else {
enclosed.println("Flashing bootloader...");
}
}
enclosed.inc(flash_pos_delta as u64);
});
progress_bar.finish();
let dfu_iface = result?;
info!("Flash complete!");
if progress_bar.position() == firmware_length {
device.reboot(dfu_iface)
} else {
Err(eyre!("Failed to flash device, download incomplete"))
}
}
}
fn check_programming(port: PortId) -> Result<()>
{
let dev = bmp::wait_for_probe_reboot(port, Duration::from_secs(5), "flash").inspect_err(|_| {
error!("Black Magic Probe did not re-enumerate after flashing! Invalid firmware?");
})?;
let identity = dev.firmware_identity().inspect_err(|_| {
error!("Error reading firmware version after flash! Invalid firmware?");
})?;
println!(
"Black Magic Probe successfully rebooted into firmware version {}",
identity.version
);
Ok(())
}
pub fn flash_probe<Params>(params: &Params, mut device: BmpDevice, file_name: PathBuf) -> Result<()>
where
Params: BmpParams + FlashParams,
{
let firmware_file = FirmwareFile::from_path(&file_name)?;
let port = device.port();
let firmware = Firmware::new(params, &device, firmware_file)?;
let _ = writeln!(std::io::stdout(), "Found: {}", device).map_err(|e| {
error!(
"Failed to read string data from Black Magic Probe: {}\nTrying to continue anyway...",
e
);
});
firmware.program_firmware(&mut device)?;
drop(device);
thread::sleep(Duration::from_millis(250));
check_programming(port)
}