use std::io::{Read, Write};
use std::path::PathBuf;
use std::rc::Rc;
use std::thread;
use std::time::Duration;
use clap::ArgMatches;
use color_eyre::eyre::{eyre, Context, Result};
use dfu_nusb::Error as DfuNusbError;
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, error, warn};
use nusb::transfer::TransferError;
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};
use crate::bmp::{self, BmpDevice, FirmwareFormat, FirmwareType};
use crate::elf;
use crate::usb::PortId;
pub struct Firmware
{
firmware_type: FirmwareType,
data: Vec<u8>,
length: u32,
}
impl Firmware
{
pub fn new(matches: &ArgMatches, device: &BmpDevice, firmware_data: Vec<u8>) -> Result<Self>
{
let firmware_length = firmware_data.len();
let firmware_length = u32::try_from(firmware_length)
.expect("firmware filesize exceeded 32 bits! Firmware binary must be invalid (too big)");
Ok(Self {
firmware_type: Self::determine_firmware_type(matches, device, &firmware_data)?,
data: firmware_data,
length: firmware_length,
})
}
fn determine_firmware_type(
matches: &ArgMatches, device: &BmpDevice, firmware_data: &Vec<u8>
) -> Result<FirmwareType>
{
let platform = device.platform();
let firmware_type = FirmwareType::detect_from_firmware(platform, &firmware_data)
.wrap_err("detecting firmware type")?;
debug!("Firmware file was detected as {}", firmware_type);
let firmware_type = if let Some(location) = matches
.get_one::<String>("override-firmware-type").map(|s| s.as_str()) {
if let Some("really") = matches.get_one::<String>("allow-dangerous-options").map(|s| s.as_str()) {
warn!("Overriding firmware-type detection and flashing to user-specified location ({}) instead!", location);
} else {
let mut stderr = StandardStream::stderr(ColorChoice::Auto);
let _res = stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)));
write!(&mut stderr, "WARNING: ").expect("failed to write to stderr");
let _res = stderr.reset();
writeln!(
&mut stderr,
"--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.\n\
This 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"
).expect("failed to write to stderr");
std::process::exit(1);
};
if location == "bootloader" {
FirmwareType::Bootloader
} else if location == "application" {
FirmwareType::Application
} else {
unreachable!("Clap ensures invalid option cannot be passed to --override-firmware-type");
}
} else {
firmware_type
};
Ok(firmware_type)
}
pub fn program_firmware(&self, device: &mut BmpDevice) -> Result<()>
{
let progress_bar = ProgressBar::new(self.length as u64)
.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 firmware_type = self.firmware_type;
let result = device.download(&*self.data, self.length, 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?;
if progress_bar.position() == (self.length as u64) {
match device.reboot(dfu_iface) {
Err(err) => {
let err = err.downcast::<DfuNusbError>()?;
match err {
DfuNusbError::Transfer(error) => match error {
#[cfg(any(target_os = "linux", target_os = "android"))]
TransferError::Disconnected => Ok(()),
#[cfg(any(target_os = "windows", target_os = "macos"))]
TransferError::Stall => Ok(()),
#[cfg(target_os = "macos")]
TransferError::Unknown => Ok(()),
_ => {
warn!("Possibly spurious error from OS at the very end of flashing: {}", err);
Err(err.into())
}
},
_ => {
warn!("Possibly spurious error from OS at the very end of flashing: {}", err);
Err(err.into())
}
}
},
result => result,
}
} else {
Err(eyre!("Failed to flash device, download incomplete"))
}
}
}
fn intel_hex_error() -> !
{
let mut stderr = StandardStream::stderr(ColorChoice::Auto);
let _res = stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)));
write!(&mut stderr, "Error: ")
.expect("failed to write to stderr");
let _res = stderr.reset();
writeln!(
&mut stderr,
"The specified firmware file appears to be an Intel HEX file, but Intel HEX files are not \
currently supported. Please use a binary file (e.g. blackmagic.bin), \
or an ELF (e.g. blackmagic.elf) to flash.",
)
.expect("failed to write to stderr");
std::process::exit(1);
}
fn read_firmware(file_name: PathBuf) -> Result<Vec<u8>>
{
let firmware_file = std::fs::File::open(file_name.as_path())
.wrap_err_with(|| eyre!("Failed to read firmware file {} to Flash", file_name.display()))?;
let mut firmware_file = std::io::BufReader::new(firmware_file);
let mut firmware_data = Vec::new();
firmware_file.read_to_end(&mut firmware_data).unwrap();
if firmware_data.len() < 8 {
return Err(eyre!("Firmware file appears invalid: less than 8 bytes long"));
}
let firmware_data = match FirmwareFormat::detect_from_firmware(&firmware_data) {
FirmwareFormat::Binary => firmware_data,
FirmwareFormat::Elf => elf::extract_binary(&firmware_data)?,
FirmwareFormat::IntelHex => intel_hex_error(), };
Ok(firmware_data)
}
fn check_programming(port: PortId) -> Result<()>
{
let dev = bmp::wait_for_probe_reboot(port, Duration::from_secs(5), "flash")
.map_err(|e| {
error!("Black Magic Probe did not re-enumerate after flashing! Invalid firmware?");
e
})?;
let product_string = dev
.firmware_identity()
.map_err(|e| {
error!("Error reading firmware version after flash! Invalid firmware?");
e
})?;
let version_string = product_string
.chars()
.skip("Black Magic Probe ".len())
.collect::<String>();
println!("Black Magic Probe successfully rebooted into firmware version {}", version_string);
Ok(())
}
pub fn flash_probe(matches: &ArgMatches, mut device: BmpDevice, file_name: PathBuf) -> Result<()>
{
let firmware_data = read_firmware(file_name)?;
let port = device.port();
let firmware = Firmware::new(matches, &device, firmware_data)?;
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)
}