diff --git a/.gitignore b/.gitignore index ea8c4bf..eb5a316 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1 @@ -/target +target diff --git a/Cargo.lock b/Cargo.lock index 10a2574..9d2f3aa 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -54,6 +54,7 @@ dependencies = [ "log", "rusb", "rustc_version", + "termcolor", "thiserror", ] diff --git a/Cargo.toml b/Cargo.toml index c6feaa7..f16c7f2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ const_format = "0.2" anyhow = "1.0" thiserror = "1.0" indicatif = "0.16" +termcolor = "1.1.3" [build-dependencies] rustc_version = "0.4" diff --git a/build.rs b/build.rs index 8fb05d6..fbd306f 100644 --- a/build.rs +++ b/build.rs @@ -1,3 +1,8 @@ +//! To whomever might be reading this, understandably skeptical of build scripts: +//! This build script is *optional*, and exists only to set *default* options under +//! circumstances under which they are supported. Actually, all this build script does +//! is detect if we are running nightly Rust, and enable backtrace support for errors +//! if we are. use rustc_version::{version_meta, Channel}; diff --git a/src/bmp.rs b/src/bmp.rs index 42c0aad..20944a2 100644 --- a/src/bmp.rs +++ b/src/bmp.rs @@ -1,17 +1,18 @@ use std::mem; use std::thread; -use std::io::Read; +use std::io::{Read, Seek, SeekFrom}; use std::cell::{RefCell, Ref}; use std::str::FromStr; use std::time::{Duration, Instant}; use std::fmt::{self, Display, Formatter}; +use std::array::TryFromSliceError; use clap::ArgMatches; -use log::{trace, info, warn, error}; +use log::{trace, debug, info, warn, error}; use rusb::{UsbContext, Direction, RequestType, Recipient}; use dfu_libusb::DfuLibusb; -use crate::libusb_cannot_fail; +use crate::{libusb_cannot_fail, S}; use crate::error::{Error, ErrorKind}; use crate::usb::{DfuFunctionalDescriptor, InterfaceClass, InterfaceSubClass, GenericDescriptorRef, DfuRequest}; use crate::usb::{Vid, Pid, DfuOperatingMode, DfuMatch}; @@ -297,6 +298,7 @@ impl BlackmagicProbeDevice /// Requests the device to leave DFU mode, using the DefuSe extensions. fn leave_dfu_mode(&mut self) -> Result<(), Error> { + debug!("Attempting to leave DFU mode..."); let (iface_number, _func_desc) = self.dfu_descriptors()?; self.handle.claim_interface(iface_number)?; @@ -437,7 +439,7 @@ impl BlackmagicProbeDevice /// /// `progress` is a callback of the form `fn(just_written: usize)`, for callers to keep track of /// the flashing process. - pub fn download(&mut self, firmware: R, length: u32, progress: P) -> Result<(), Error> + pub fn download(&mut self, firmware: R, length: u32, firmware_type: BmpFirmwareType, progress: P) -> Result<(), Error> where R: Read, P: Fn(usize) + 'static, @@ -454,7 +456,9 @@ impl BlackmagicProbeDevice 0, )? .with_progress(progress) - .override_address(0x0800_2000); // TODO: this should be checked against the binary. + .override_address(firmware_type.load_address()); + + debug!("Load address: 0x{:08x}", firmware_type.load_address()); info!("Performing flash..."); @@ -524,6 +528,119 @@ impl Display for BlackmagicProbeDevice } } +/// Represents a conceptual Vector Table for Armv7 processors. +pub struct Armv7mVectorTable<'b> +{ + bytes: &'b [u8], +} + +impl<'b> Armv7mVectorTable<'b> +{ + fn word(&self, index: usize) -> Result + { + let array: [u8; 4] = self.bytes[(index)..(index + 4)] + .try_into()?; + + Ok(u32::from_le_bytes(array)) + } + + + /// Construct a conceptual Armv7m Vector Table from a bytes slice. + pub fn from_bytes(bytes: &'b [u8]) -> Self + { + if bytes.len() < (4 * 2) { + panic!("Data passed is not long enough for an Armv7m Vector Table!"); + } + + Self { + bytes, + } + } + + #[allow(dead_code)] + pub fn stack_pointer(&self) -> Result + { + self.word(0) + } + + pub fn reset_vector(&self) -> Result + { + self.word(1) + } + + #[allow(dead_code)] + pub fn exception(&self, exception_number: u32) -> Result + { + self.word((exception_number + 1) as usize) + } +} + + +/// Firmware types for the Black Magic Probe. +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub enum BmpFirmwareType +{ + /// The bootloader. For native probes this is linked at 0x0800_0000 + Bootloader, + /// The main application. For native probes this is linked at 0x0800_2000. + Application, +} + +impl BmpFirmwareType +{ + // FIXME: Make these more configurable based on the device? + pub const BOOTLOADER_START: u32 = 0x0800_0000; + pub const APPLICATION_START: u32 = 0x0800_2000; + + /// Detect the kind of firmware from the given binary by examining its reset vector address. + /// + /// On success, this function returns the cursor back to where it was before reading. + pub fn detect_from_firmware(mut firmware: R) -> Result + { + let mut buffer = [0u8; (4 * 2)]; + firmware.read_exact(&mut buffer) + .map_err(|e| ErrorKind::FirmwareFileIo(None).error_from(e))?; + + // Seek back to effectively undo the read we just did. + let read = buffer.len() as i64; + firmware.seek(SeekFrom::Current(-read)) + .map_err(|e| ErrorKind::FirmwareFileIo(None).error_from(e))?; + + + let vector_table = Armv7mVectorTable::from_bytes(&buffer); + let reset_vector = vector_table.reset_vector() + .map_err(|e| ErrorKind::InvalidFirmware(Some(S!("vector table too short"))).error_from(e))?; + + if reset_vector > Self::APPLICATION_START { + return Ok(Self::Application); + } else { + return Ok(Self::Bootloader); + } + } + + /// Get the load address for firmware of this type. + pub fn load_address(&self) -> u32 + { + match self { + Self::Bootloader => Self::BOOTLOADER_START, + Self::Application => Self::APPLICATION_START, + } + } +} + +impl Display for BmpFirmwareType +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + { + match self { + Self::Bootloader => write!(f, "bootloader")?, + Self::Application => write!(f, "application")?, + }; + + Ok(()) + } +} + #[derive(Debug, Clone, Default)] pub struct BlackmagicProbeMatcher diff --git a/src/error.rs b/src/error.rs index 0dde22b..c57b192 100644 --- a/src/error.rs +++ b/src/error.rs @@ -21,6 +21,9 @@ pub enum ErrorKind /// Failed to read firmware file. FirmwareFileIo(Option), + /// Specified firmware seems invalid. + InvalidFirmware(/** why **/ Option), + /// Current operation only supports one Black Magic Probe but more tha none device was found. TooManyDevices, @@ -102,6 +105,8 @@ impl Display for ErrorKind thing, )?; }, + InvalidFirmware(None) => write!(f, "specified firmware does not seem valid")?, + InvalidFirmware(Some(why)) => write!(f, "specified firmware does not seem valid: {}", why)?, External(source) => { use ErrorSource::*; match source { diff --git a/src/main.rs b/src/main.rs index a01e146..b10dba6 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,21 +3,21 @@ use std::backtrace::BacktraceStatus; use std::rc::Rc; +use std::io::Write; use std::str::FromStr; use std::time::Duration; use clap::{Command, Arg, ArgMatches}; - +use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor}; use indicatif::{ProgressBar, ProgressStyle}; - -use log::error; +use log::{debug, warn, error}; mod usb; mod error; mod bmp; use crate::usb::DfuOperatingMode; -use crate::bmp::{BlackmagicProbeDevice, BlackmagicProbeMatcher, find_matching_probes}; +use crate::bmp::{BlackmagicProbeDevice, BlackmagicProbeMatcher, BmpFirmwareType, find_matching_probes}; use crate::error::{Error, ErrorKind, ErrorSource}; #[macro_export] @@ -57,6 +57,45 @@ fn flash(matches: &ArgMatches) -> Result<(), Error> .map_err(|source| ErrorKind::FirmwareFileIo(Some(filename.to_string())).error_from(source)) .map_err(|e| e.with_ctx("reading firmware file to flash"))?; + // Detect what kind of firmware this is. + let firmware_type = BmpFirmwareType::detect_from_firmware(&firmware_file) + .map_err(|e| e.with_ctx("detecting firmware type"))?; + debug!("Firmware file was detected as {}", firmware_type); + + // But allow the user to override that type, if they *really* know what they are doing. + let firmware_type = if let Some(location) = matches.value_of("override-firmware-type") { + if let Some("really") = matches.value_of("allow-dangerous-options") { + warn!("Overriding firmware-type detection and flashing to user-specified location ({}) instead!", location); + } else { + // We're ignoring errors for setting the color because the most important thing is + // getting the message itself out. + // If the messages themselves don't write, though, then we might as well just panic. + 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" { + BmpFirmwareType::Bootloader + } else if location == "application" { + BmpFirmwareType::Application + } else { + unreachable!("Clap ensures invalid option cannot be passed to --override-firmware-type"); + } + } else { + firmware_type + }; + let file_size = firmware_file.metadata() .map_err(|source| ErrorKind::FirmwareFileIo(Some(filename.to_string())).error_from(source))? .len(); @@ -92,7 +131,7 @@ fn flash(matches: &ArgMatches) -> Result<(), Error> let progress_bar = Rc::new(progress_bar); let enclosed = Rc::clone(&progress_bar); - match dev.download(firmware_file, file_size, move |delta| { + match dev.download(firmware_file, file_size, firmware_type, move |delta| { enclosed.inc(delta as u64); }) { Ok(v) => Ok(v), @@ -108,13 +147,23 @@ fn flash(matches: &ArgMatches) -> Result<(), Error> progress_bar.finish(); - // Now that we've flashed, try and re-enumerate the device one more time. let mut dev = bmp::wait_for_probe_reboot(&serial, Duration::from_secs(5), "flash") .map_err(|e| { error!("Black Magic Probe did not re-enumerate after flashing! Invalid firmware?"); e })?; + std::thread::sleep(Duration::from_millis(1000)); + + dev.detach_and_enumerate() + .map_err(|e| { + error!("Black Magic Probe did not re-enumerate after flashing! Invalid firmware?"); + e + })?; + + dev.detach_and_enumerate()?; + + let languages = dev .handle() .read_languages(Duration::from_secs(2)) @@ -205,6 +254,14 @@ fn main() .global(true) .help("Use the device on the given USB port") ) + .arg(Arg::new("allow-dangerous-options") + .long("allow-dangerous-options") + .global(true) + .takes_value(true) + .possible_value("really") + .hide(true) + .help("Allow usage of advanced, dangerous options that can result in unbootable devices (use with heavy caution!)") + ) .subcommand(Command::new("info") .display_order(0) .about("Print information about connected Blackmagic Probe devices") @@ -216,6 +273,22 @@ fn main() .takes_value(true) .required(true) ) + .arg(Arg::new("override-firmware-type") + .long("override-firmware-type") + .required(false) + .takes_value(true) + .possible_values(&["bootloader", "application"]) + .hide_short_help(true) + .help("flash the specified firmware space regardless of autodetected firmware type") + ) + .arg(Arg::new("force-override-flash") + .long("force-override-flash") + .required(false) + .takes_value(true) + .possible_value("really") + .hide(true) + .help("forcibly override firmware-type autodetection and flash anyway (may result in an unbootable device!)") + ) ) .subcommand(Command::new("debug") .display_order(10)