diff --git a/src/bmp.rs b/src/bmp.rs index 0b6e742..6672ef7 100644 --- a/src/bmp.rs +++ b/src/bmp.rs @@ -3,16 +3,13 @@ // SPDX-FileContributor: Written by Mikaela Szekely // SPDX-FileContributor: Modified by Rachel Mant -use std::array::TryFromSliceError; use std::cell::{Ref, RefCell}; use std::fmt::{self, Debug, Display, Formatter}; use std::io::Read; use std::thread; use std::time::{Duration, Instant}; -use clap::ValueEnum; -use clap::builder::PossibleValue; -use color_eyre::eyre::{Context, Error, OptionExt, Result, eyre}; +use color_eyre::eyre::{Context, Error, OptionExt, Result}; use dfu_core::{DfuIo, DfuProtocol, Error as DfuCoreError, State as DfuState}; use dfu_nusb::{DfuNusb, DfuSync, Error as DfuNusbError}; use log::{debug, error, trace, warn}; @@ -22,6 +19,9 @@ use nusb::{Device, DeviceInfo, Interface}; pub use crate::bmp_matcher::BmpMatcher; use crate::error::ErrorKind; +use crate::firmware_file::FirmwareFile; +// XXX: Ideally this shouldn't be pub here, but that's a breaking change. +pub use crate::firmware_type::FirmwareType; use crate::probe_identity::ProbeIdentity; use crate::serial::bmd_rsp::BmdRspInterface; use crate::serial::gdb_rsp::GdbRspInterface; @@ -497,23 +497,24 @@ impl BmpDevice /// /// `progress` is a callback of the form `fn(just_written: usize)`, for callers to keep track of /// the flashing process. - pub fn download<'r, R, P>( + pub fn download

( &mut self, - firmware: &'r R, - length: u32, + firmware_file: &FirmwareFile, firmware_type: FirmwareType, progress: P, ) -> Result where - &'r R: Read, - R: ?Sized, P: Fn(usize) + 'static, { if self.mode == DfuOperatingMode::Runtime { self.detach_and_enumerate().wrap_err("detaching device for download")?; } - let load_address = self.platform.load_address(firmware_type); + // Extract the file's load address, and if that isn't a thing, fall back on + // more crude methods based on the platform we're downloading to + let load_address = firmware_file + .load_address() + .unwrap_or_else(|| self.platform.load_address(firmware_type)); let dfu_dev = DfuNusb::open( self.device.take().expect("Must have a valid device handle"), @@ -534,7 +535,7 @@ impl BmpDevice debug!("Load address: 0x{:08x}", load_address); - match self.try_download(firmware, length, &mut dfu_iface) { + match self.try_download(firmware_file.data(), firmware_file.len(), &mut dfu_iface) { Err(error) => { if let Some(DfuNusbError::Dfu(DfuCoreError::StateError(DfuState::DfuError))) = error.downcast_ref::() @@ -558,7 +559,7 @@ impl BmpDevice .unwrap() .control_out_blocking(request, &[], Duration::from_secs(2))?; - self.try_download(firmware, length, &mut dfu_iface) + self.try_download(firmware_file.data(), firmware_file.len(), &mut dfu_iface) } else { Err(error) } @@ -631,164 +632,6 @@ impl Display for BmpDevice } } -/// 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 start = index * 4; - let array: [u8; 4] = self.bytes[(start)..(start + 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, - } - } - - pub fn stack_pointer(&self) -> Result - { - self.word(0) - } - - pub fn reset_vector(&self) -> Result - { - self.word(1) - } - - 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 FirmwareType -{ - /// 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 FirmwareType -{ - /// Detect the kind of firmware from the given binary by examining its reset vector address. - /// - /// This function panics if `firmware.len() < 8`. - pub fn detect_from_firmware(platform: BmpPlatform, firmware: &[u8]) -> Result - { - let buffer = &firmware[0..(4 * 2)]; - - let vector_table = Armv7mVectorTable::from_bytes(buffer); - let reset_vector = vector_table - .reset_vector() - .wrap_err("Firmware file does not seem valid: vector table too short")?; - - debug!("Detected reset vector in firmware file: 0x{:08x}", reset_vector); - - // Sanity check. - if (reset_vector & 0x0800_0000) != 0x0800_0000 { - return Err(eyre!( - "Firmware file does not seem valid: reset vector address seems to be outside of reasonable bounds - \ - 0x{:08x}", - reset_vector - )); - } - - let app_start = platform.load_address(Self::Application); - - if reset_vector > app_start { - Ok(Self::Application) - } else { - Ok(Self::Bootloader) - } - } -} - -impl Display for FirmwareType -{ - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result - { - match self { - Self::Bootloader => write!(f, "bootloader")?, - Self::Application => write!(f, "application")?, - }; - - Ok(()) - } -} - -/// Defaults to [`FirmwareType::Application`]. -impl Default for FirmwareType -{ - /// Defaults to [`FirmwareType::Application`]. - fn default() -> Self - { - FirmwareType::Application - } -} - -impl ValueEnum for FirmwareType -{ - fn value_variants<'a>() -> &'a [Self] - { - &[Self::Application, Self::Bootloader] - } - - fn to_possible_value(&self) -> Option - { - match self { - Self::Bootloader => Some("bootloader".into()), - Self::Application => Some("application".into()), - } - } -} - -/// File formats that Black Magic Probe firmware can be in. -pub enum FirmwareFormat -{ - /// Raw binary format. Made with `objcopy -O binary`. Typical file extension: `.bin`. - Binary, - - /// The Unix ELF executable binary format. Typical file extension: `.elf`. - Elf, - - /// Intel HEX. Typical file extensions: `.hex`, `.ihex`. - IntelHex, -} - -impl FirmwareFormat -{ - /// Detect the kind of firmware from its data. - /// - /// Panics if `firmware.len() < 4`. - pub fn detect_from_firmware(firmware: &[u8]) -> Self - { - if &firmware[0..4] == b"\x7fELF" { - FirmwareFormat::Elf - } else if &firmware[0..1] == b":" { - FirmwareFormat::IntelHex - } else { - FirmwareFormat::Binary - } - } -} - /// Waits for a Black Magic Probe to reboot, erroring after a timeout. /// /// This function takes a port identifier to attempt to keep track of a single physical device diff --git a/src/elf.rs b/src/elf.rs deleted file mode 100644 index ef6d4fa..0000000 --- a/src/elf.rs +++ /dev/null @@ -1,98 +0,0 @@ -// SPDX-License-Identifier: MIT OR Apache-2.0 -// SPDX-FileCopyrightText: 2022-2025 1BitSquared -// SPDX-FileContributor: Written by Mikaela Szekely -// SPDX-FileContributor: Modified by Rachel Mant - -use color_eyre::eyre::Result; -use goblin::elf::{Elf, SectionHeader}; -use goblin::error::Error as GoblinError; - -/// Convenience extensions to [Elf]. -trait ElfExt -{ - /// Get a reference to a section header with the given name. Returns None if - /// a section by that name does not exist. - fn get_section_by_name(&self, name: &str) -> Option<&goblin::elf::SectionHeader>; -} - -impl<'a> ElfExt for Elf<'a> -{ - fn get_section_by_name(&self, name: &str) -> Option<&goblin::elf::SectionHeader> - { - for section in &self.section_headers { - let parsed_name = self.shdr_strtab.get_at(section.sh_name)?; - - if parsed_name == name { - return Some(section); - } - } - - None - } -} - -/// Convenience extensions to [SectionHeader]. -trait SectionHeaderExt -{ - /// Get the raw data of this section, given the full ELF data. - fn get_data<'s>(&'s self, parent_data: &'s [u8]) -> Result<&'s [u8]>; -} - -impl SectionHeaderExt for SectionHeader -{ - fn get_data<'s>(&'s self, parent_data: &'s [u8]) -> Result<&'s [u8]> - { - let start_idx = self.sh_offset as usize; - let size = self.sh_size; - let end_idx = start_idx + size as usize; - let data: &[u8] = parent_data.get(start_idx..end_idx).ok_or_else(|| { - GoblinError::Malformed(format!( - "ELF section header does not point to a valid section (offset [{}..{}])", - start_idx, end_idx, - )) - })?; - - Ok(data) - } -} - -/// Extracts binary data from raw ELF data. -/// -/// This should be equivalent to `$ arm-none-eabi-objcopy -Obinary`, but is not yet robust -/// enough to automatically detect what sections should be copied. -/// Currently, `.text`, `.ARM.exidx`, and `.data` are copied. -pub fn extract_binary(elf_data: &[u8]) -> Result> -{ - let elf = Elf::parse(elf_data)?; - - // FIXME: Dynamically detect what sections should be copied. - // arm-none-eabi-objcopy seems to only copy these three, but I'm not yet certain why only these three - // (as these aren't the only three that have PROGBITS set). - - let text = elf - .get_section_by_name(".text") - .ok_or_else(|| GoblinError::Malformed("ELF .text section not found".into()))? - .get_data(elf_data)?; - - // Allow .ARM.exidx to not exist. - let arm_exidx = elf - .get_section_by_name(".ARM.exidx") - .and_then(|v| v.get_data(elf_data).ok()); - let arm_exidx_len = arm_exidx.map(|sect| sect.len()).unwrap_or(0); - - let data = elf - .get_section_by_name(".data") - .ok_or_else(|| GoblinError::Malformed("ELF .data section not found".into()))? - .get_data(elf_data)?; - - let mut extracted = Vec::with_capacity(text.len() + arm_exidx_len + data.len()); - - extracted.extend_from_slice(text); - if let Some(arm_exidx) = arm_exidx { - extracted.extend_from_slice(arm_exidx); - } - - extracted.extend_from_slice(data); - - Ok(extracted) -} diff --git a/src/firmware_file/elf.rs b/src/firmware_file/elf.rs new file mode 100644 index 0000000..9dc16bf --- /dev/null +++ b/src/firmware_file/elf.rs @@ -0,0 +1,135 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::iter::zip; +use std::ops::Range; +use std::{collections::BTreeMap, fs::File, io::Read}; + +use color_eyre::eyre::{Report, Result, eyre}; +use goblin::container::Endian; +use goblin::elf::program_header::PT_LOAD; +use goblin::elf::{Elf, header::{EI_CLASS, ELFCLASS32, EM_ARM, ET_EXEC}}; +use log::debug; + +use super::FirmwareStorage; + +pub struct ELFFirmwareFile +{ + contents: Box<[u8]>, + segments: BTreeMap>, + firmware_image: Box<[u8]>, +} + +impl TryFrom for ELFFirmwareFile +{ + type Error = Report; + + fn try_from(mut file: File) -> Result + { + debug!("Loading file as ELF firmware binary"); + // Extract the contents of the ELF file into memory + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + let contents = contents.into_boxed_slice(); + + // Try to parse the file as an ELF + let elf = Elf::parse(&contents)?; + + // Validate the header is for a 32-bit ARM device + let header = elf.header; + if header.e_type != ET_EXEC || header.e_machine != EM_ARM || + header.endianness()? != Endian::Little || header.e_ident[EI_CLASS] != ELFCLASS32 { + return Err(eyre!("ELF does not represent firmware for a Black Magic Debug device")); + } + + // Extract loadable non-zero-length program headers + let segments = elf.program_headers + .iter() + .flat_map(|header| { + // Map into base address + file byte range for the data covered by the segment + (header.p_type == PT_LOAD && header.p_filesz != 0) + .then_some((header.p_paddr as u32, header.file_range())) + }) + .collect::>(); + debug!("Consuming {} segments from file", segments.len()); + + // Make one of ourself + let mut result = Self { + contents, + segments, + firmware_image: Box::default(), + }; + // Use the data to make the firmware image + result.build_firmware_image(); + + Ok(result) + } +} + +impl ELFFirmwareFile +{ + fn build_firmware_image(&mut self) + { + // Figure out where the first segment sits + let load_address = self.load_address().unwrap_or(0); + debug!("Firmware image loads at 0x{load_address:08x}"); + + // Extract slices for each of the segments ready to flatten out + let segments_data = self.segments + .values() + .map(|range| { + &self.contents[range.clone()] + }) + .collect::>(); + + // Extract a set of position and length ranges + let segment_ranges = self.segments + .iter() + .map(|(&address, range)| { + address..address + (range.len() as u32) + }) + .collect::>(); + + // Figure out the total length of the flattened firmware image to allocate + let total_length = segment_ranges + .clone() + .into_iter() + .reduce(|a, b| { + a.start..b.end + }) + .map(|range| range.len()) + .unwrap_or(0); + debug!("Firmware is {total_length} bytes long flattened"); + + // Allocate enough memory to hold the completely flattened firmware image + // and initialise it to the erased byte value + let mut firmware_image = vec![0xffu8; total_length].into_boxed_slice(); + + // Loop through all the segments of data and copy them to their final resting places + // in the flattened image + for (segment, position) in zip(segments_data, segment_ranges) { + // Calculate the location of the segment in the flattened image + let range = ((position.start - load_address) as usize)..((position.end - load_address) as usize); + // Grab the relevant slice and copy the segment data in + firmware_image[range].copy_from_slice(segment); + } + + // Store the resulting image back on ourself + self.firmware_image = firmware_image; + } +} + +impl FirmwareStorage for ELFFirmwareFile +{ + fn load_address(&self) -> Option + { + // Extract the first segment (the map ordered them for us) and return its address + self.segments.first_key_value().map(|(&address, _)| address) + } + + fn firmware_data(&self) -> &[u8] + { + &self.firmware_image + } +} diff --git a/src/firmware_file/ihex.rs b/src/firmware_file/ihex.rs new file mode 100644 index 0000000..74779cb --- /dev/null +++ b/src/firmware_file/ihex.rs @@ -0,0 +1,38 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; + +use owo_colors::OwoColorize; + +use super::FirmwareStorage; + +pub struct IntelHexFirmwareFile {} + +impl From for IntelHexFirmwareFile +{ + fn from(_file: File) -> Self + { + eprintln!( + "{} 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.", + "Error:".red() + ); + std::process::exit(1); + // Self {} + } +} + +impl FirmwareStorage for IntelHexFirmwareFile +{ + fn load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &[] + } +} diff --git a/src/firmware_file/mod.rs b/src/firmware_file/mod.rs new file mode 100644 index 0000000..9855881 --- /dev/null +++ b/src/firmware_file/mod.rs @@ -0,0 +1,75 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; +use std::io::{Read, Seek}; +use std::path::Path; + +use color_eyre::eyre::{Context, Result, eyre}; +use log::debug; + +mod elf; +mod ihex; +mod raw; + +use self::elf::ELFFirmwareFile; +use self::ihex::IntelHexFirmwareFile; +use self::raw::RawFirmwareFile; + +trait FirmwareStorage +{ + fn load_address(&self) -> Option; + fn firmware_data(&self) -> &[u8]; +} + +pub struct FirmwareFile +{ + inner: Box, +} + +impl FirmwareFile +{ + /// Construct a FirmwareFile from a path to a file + pub fn from_path(file_name: &Path) -> Result + { + let mut file = + File::open(file_name).wrap_err_with(|| eyre!("Failed to read file {} as firmware", file_name.display()))?; + + debug!("Reading firmware from {}", file_name.display()); + + let mut signature = [0u8; 4]; + let _ = file.read(&mut signature)?; + file.rewind()?; + + let storage: Box = if &signature == b"\x7fELF" { + Box::new(ELFFirmwareFile::try_from(file)?) + } else if &signature[0..1] == b":" { + Box::new(IntelHexFirmwareFile::from(file)) + } else { + Box::new(RawFirmwareFile::try_from(file)?) + }; + + Ok(Self { + inner: storage, + }) + } + + pub fn load_address(&self) -> Option + { + self.inner.load_address() + } + + /// Provides the firmware image data this file holds in a format suitable for + /// writing into Flash directly at the load address + pub fn data(&self) -> &[u8] + { + self.inner.firmware_data() + } + + /// Provides the length of the firmware image this file holds as a u32 + pub fn len(&self) -> u32 + { + self.inner.firmware_data().len() as u32 + } +} diff --git a/src/firmware_file/raw.rs b/src/firmware_file/raw.rs new file mode 100644 index 0000000..1de212a --- /dev/null +++ b/src/firmware_file/raw.rs @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2025 1BitSquared +// SPDX-FileContributor: Written by Rachel Mant + +use std::fs::File; +use std::io::Read; + +use color_eyre::eyre::{Report, Result, eyre}; +use log::debug; + +use super::FirmwareStorage; + +pub struct RawFirmwareFile +{ + contents: Box<[u8]>, +} + +impl TryFrom for RawFirmwareFile +{ + type Error = Report; + + fn try_from(mut file: File) -> Result + { + debug!("Loading file as raw firmware binary"); + // Pull out the entire file contents into memory and stuff it in a vec + let mut contents = Vec::new(); + file.read_to_end(&mut contents)?; + + // Check that the result isn't too insanely big + if contents.len() > u32::MAX as usize { + Err(eyre!( + "Firmware file size exceeds the max value for a 32-bit integer! Firmware binary invalid (too big)" + )) + } else { + // Otherwise put the vec inside our little container and be done + Ok(Self { + contents: contents.into_boxed_slice(), + }) + } + } +} + +impl FirmwareStorage for RawFirmwareFile +{ + fn load_address(&self) -> Option + { + None + } + + fn firmware_data(&self) -> &[u8] + { + &self.contents + } +} diff --git a/src/firmware_type.rs b/src/firmware_type.rs new file mode 100644 index 0000000..f5f7e7b --- /dev/null +++ b/src/firmware_type.rs @@ -0,0 +1,146 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +// SPDX-FileCopyrightText: 2022-2025 1BitSquared +// SPDX-FileContributor: Written by Mikaela Szekely +// SPDX-FileContributor: Modified by Rachel Mant + +use std::array::TryFromSliceError; +use std::fmt::Display; + +use clap::{ValueEnum, builder::PossibleValue}; +use color_eyre::eyre::{Context, Result, eyre}; +use log::debug; + +use crate::{bmp::BmpPlatform, firmware_file::FirmwareFile}; + +/// 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 start = index * 4; + let array: [u8; 4] = self.bytes[(start)..(start + 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, + } + } + + pub fn stack_pointer(&self) -> Result + { + self.word(0) + } + + pub fn reset_vector(&self) -> Result + { + self.word(1) + } + + 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 FirmwareType +{ + /// 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 FirmwareType +{ + /// Detect the kind of firmware from the given binary by examining its reset vector address. + /// + /// This function panics if `firmware.len() < 8`. + pub fn detect_from_firmware(platform: BmpPlatform, firmware_file: &FirmwareFile) -> Result + { + // If the firmware image has a load address + if let Some(load_address) = firmware_file.load_address() { + // Check if the address is the bootloader area for the platform + let boot_start = platform.load_address(Self::Bootloader); + return Ok( + if load_address == boot_start { + Self::Bootloader + } else { + Self::Application + } + ); + } + + // If the firmware doesn't have a known load address, fall back to figuring it out + // from the NVIC table at the front of the image + let buffer = &firmware_file.data()[0..(4 * 2)]; + + let vector_table = Armv7mVectorTable::from_bytes(buffer); + let reset_vector = vector_table + .reset_vector() + .wrap_err("Firmware file does not seem valid: vector table too short")?; + + debug!("Detected reset vector in firmware file: 0x{:08x}", reset_vector); + + // Sanity check. + if (reset_vector & 0x0800_0000) != 0x0800_0000 { + return Err(eyre!( + "Firmware file does not seem valid: reset vector address seems to be outside of reasonable bounds - \ + 0x{:08x}", + reset_vector + )); + } + + let app_start = platform.load_address(Self::Application); + + if reset_vector > app_start { + Ok(Self::Application) + } else { + Ok(Self::Bootloader) + } + } +} + +impl Display for FirmwareType +{ + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result + { + match self { + Self::Bootloader => write!(f, "bootloader")?, + Self::Application => write!(f, "application")?, + }; + + Ok(()) + } +} + +impl ValueEnum for FirmwareType +{ + fn value_variants<'a>() -> &'a [Self] + { + &[Self::Application, Self::Bootloader] + } + + fn to_possible_value(&self) -> Option + { + match self { + Self::Bootloader => Some("bootloader".into()), + Self::Application => Some("application".into()), + } + } +} diff --git a/src/flasher.rs b/src/flasher.rs index edf34dc..f3abbff 100644 --- a/src/flasher.rs +++ b/src/flasher.rs @@ -3,7 +3,7 @@ // SPDX-FileContributor: Written by Mikaela Szekely // SPDX-FileContributor: Modified by Rachel Mant -use std::io::{Read, Write}; +use std::io::Write; use std::path::PathBuf; use std::rc::Rc; use std::thread; @@ -14,38 +14,35 @@ use color_eyre::owo_colors::OwoColorize; use indicatif::{ProgressBar, ProgressStyle}; use log::{debug, error, info, warn}; -use crate::bmp::{self, BmpDevice, FirmwareFormat, FirmwareType}; +use crate::bmp::{self, BmpDevice}; +use crate::firmware_file::FirmwareFile; +use crate::firmware_type::FirmwareType; use crate::usb::PortId; -use crate::{AllowDangerous, BmpParams, FlashParams, elf}; +use crate::{AllowDangerous, BmpParams, FlashParams}; pub struct Firmware { firmware_type: FirmwareType, - data: Vec, - length: u32, + firmware_file: FirmwareFile, } impl Firmware { - pub fn new(params: &Params, device: &BmpDevice, firmware_data: Vec) -> Result + pub fn new(params: &Params, device: &BmpDevice, firmware_file: FirmwareFile) -> Result where Params: BmpParams + FlashParams, { - 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(params, device, &firmware_data)?, - data: firmware_data, - length: firmware_length, + firmware_type: Self::determine_firmware_type(params, device, &firmware_file)?, + firmware_file, }) } + // XXX: Move me to FirmwareFile? fn determine_firmware_type( params: &Params, device: &BmpDevice, - firmware_data: &[u8], + firmware_file: &FirmwareFile, ) -> Result where Params: BmpParams + FlashParams, @@ -54,7 +51,7 @@ impl Firmware // Using the platform to determine the link address. let platform = device.platform(); let firmware_type = - FirmwareType::detect_from_firmware(platform, firmware_data).wrap_err("detecting firmware type")?; + FirmwareType::detect_from_firmware(platform, firmware_file).wrap_err("detecting firmware type")?; debug!("Firmware file was detected as {}", firmware_type); @@ -90,21 +87,24 @@ impl Firmware pub fn program_firmware(&self, device: &mut BmpDevice) -> Result<()> { + // Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda + let firmware_type = self.firmware_type; + // Pull out the length of the firmware image and make it a u64 + let firmware_length = self.firmware_file.len() as u64; + // We need an Rc as [`dfu_core::sync::DfuSync`] requires `progress` to be 'static, // so it must be moved into the closure. However, since we need to call .finish() here, // it must be owned by both. Hence: Rc. // Default template: `{wide_bar} {pos}/{len}`. - let progress_bar = ProgressBar::new(self.length as u64).with_style( + 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); - // Extract the firmware type as a value so it can be captured and moved (copied) by the progress lambda - let firmware_type = self.firmware_type; - let result = device.download(&*self.data, self.length, firmware_type, move |flash_pos_delta| { + let result = device.download(&self.firmware_file, firmware_type, move |flash_pos_delta| { // Don't actually print flashing until the erasing has finished. if enclosed.position() == 0 { if firmware_type == FirmwareType::Application { @@ -119,7 +119,7 @@ impl Firmware let dfu_iface = result?; info!("Flash complete!"); - if progress_bar.position() == (self.length as u64) { + if progress_bar.position() == firmware_length { device.reboot(dfu_iface) } else { Err(eyre!("Failed to flash device, download incomplete")) @@ -127,43 +127,6 @@ impl Firmware } } -fn intel_hex_error() -> ! -{ - eprintln!( - "{} 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.", - "Error:".red() - ); - std::process::exit(1); -} - -fn read_firmware(file_name: PathBuf) -> Result> -{ - 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(); - - // FirmwareFormat::detect_from_firmware() needs at least 4 bytes, and - // FirmwareType::detect_from_firmware() needs at least 8 bytes, - // but also if we don't even have 8 bytes there's _no way_ this is valid firmware. - if firmware_data.len() < 8 { - return Err(eyre!("Firmware file appears invalid: less than 8 bytes long")); - } - - // Extract the actual firmware data from the file, based on the format we're using. - 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(), // FIXME: implement this. - }; - - Ok(firmware_data) -} - fn check_programming(port: PortId) -> Result<()> { let dev = bmp::wait_for_probe_reboot(port, Duration::from_secs(5), "flash").inspect_err(|_| { @@ -188,12 +151,12 @@ pub fn flash_probe(params: &Params, mut device: BmpDevice, file_name: Pa where Params: BmpParams + FlashParams, { - let firmware_data = read_firmware(file_name)?; + let firmware_file = FirmwareFile::from_path(&file_name)?; // Grab the the port the probe can be found on, which we need to re-find the probe after rebooting. let port = device.port(); - let firmware = Firmware::new(params, &device, firmware_data)?; + let firmware = Firmware::new(params, &device, firmware_file)?; // If we can't get the string descriptors, try to go ahead with flashing anyway. // It's unlikely that other control requests will succeed, but the OS might be messing with diff --git a/src/lib.rs b/src/lib.rs index d55b2b5..5f8a657 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -7,14 +7,15 @@ use clap::ValueEnum; use clap::builder::PossibleValue; -use crate::bmp::FirmwareType; +use crate::firmware_type::FirmwareType; pub mod bmp; mod bmp_matcher; pub mod docs_viewer; -pub mod elf; pub mod error; +mod firmware_file; pub mod firmware_selector; +pub mod firmware_type; pub mod flasher; pub mod metadata; pub mod probe_identity;