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 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};
use nusb::descriptors::Descriptor;
use nusb::transfer::{Control, ControlType, Direction, Recipient, TransferError};
use nusb::{Device, DeviceInfo, Interface};
pub use crate::bmp_matcher::BmpMatcher;
use crate::error::ErrorKind;
use crate::firmware_file::FirmwareFile;
pub use crate::firmware_type::FirmwareType;
use crate::probe_identity::ProbeIdentity;
use crate::serial::aux::AuxInterface;
use crate::serial::bmd_rsp::BmdRspInterface;
use crate::serial::gdb_rsp::GdbRspInterface;
use crate::serial::interface::ProbeInterface;
use crate::usb::{
DfuFunctionalDescriptor, DfuOperatingMode, DfuRequest, GenericDescriptorRef, InterfaceClass, InterfaceSubClass,
Pid, PortId, Vid,
};
pub struct BmpDevice
{
device_info: Option<DeviceInfo>,
device: Option<Device>,
product_string_idx: u8,
serial_string_idx: u8,
language: u16,
interface: Option<Interface>,
mode: DfuOperatingMode,
platform: BmpPlatform,
serial: RefCell<Option<String>>,
port: PortId,
}
fn request_type(direction: Direction, control_type: ControlType, recipient: Recipient) -> u8
{
((direction as u8) << 7) | ((control_type as u8) << 5) | (recipient as u8)
}
fn handle_detach_errors<T>(result: std::result::Result<T, DfuNusbError>) -> Result<()>
{
if let Err(err) = result {
match err {
DfuNusbError::Transfer(error) => match error {
#[cfg(any(target_os = "linux", target_os = "android", target_os = "windows"))]
TransferError::Disconnected => Ok(()),
TransferError::Stall => Ok(()),
#[cfg(target_os = "macos")]
TransferError::Unknown => Ok(()),
#[cfg(any(target_os = "linux", target_os = "android"))]
TransferError::Fault => Ok(()),
_ => {
warn!("Possibly spurious error from OS while rebooting probe: {}", err);
Err(err.into())
},
},
_ => {
warn!("Possibly spurious error from OS while rebooting probe: {}", err);
Err(err.into())
},
}
} else {
Ok(())
}
}
impl BmpDevice
{
pub fn from_usb_device(device_info: DeviceInfo) -> Result<Self>
{
let vid = Vid(device_info.vendor_id());
let pid = Pid(device_info.product_id());
let (platform, mode) = BmpPlatform::from_vid_pid(vid, pid).ok_or_else(|| {
warn!("Device passed to BmpDevice::from_usb_device() does not seem to be a BMP device!");
warn!("The logic for finding this device is probably incorrect!");
ErrorKind::DeviceNotFound.error()
})?;
let device = device_info.open()?;
let device_desc = device.get_descriptor(
1, 0,
0,
Duration::from_secs(2),
)?;
let device_desc = match Descriptor::new(device_desc.as_slice()) {
None => {
return Err(ErrorKind::DeviceSeemsInvalid("no usable device descriptor".into())
.error()
.into());
},
Some(descriptor) => descriptor,
};
let mut languages = device.get_string_descriptor_supported_languages(Duration::from_secs(2))?;
let language = match languages.nth(0) {
Some(language) => language,
None => {
return Err(ErrorKind::DeviceSeemsInvalid("no string descriptor languages".into())
.error()
.into());
},
};
let interface = device.active_configuration()?
.interfaces()
.find(|interface|
interface.alt_settings()
.filter(|alt_mode|
InterfaceClass(alt_mode.class()) == InterfaceClass::APPLICATION_SPECIFIC &&
InterfaceSubClass(alt_mode.subclass()) == InterfaceSubClass::DFU
)
.count() > 0
)
.map(|interface| device.claim_interface(interface.interface_number()))
.ok_or_else(|| ErrorKind::DeviceSeemsInvalid("could not find DFU interface".into()).error())??;
let port = PortId::new(&device_info);
Ok(Self {
device_info: Some(device_info),
device: Some(device),
product_string_idx: device_desc[15],
serial_string_idx: device_desc[16],
interface: Some(interface),
language,
mode,
platform,
serial: RefCell::new(None),
port,
})
}
pub fn device_info(&self) -> &DeviceInfo
{
self.device_info
.as_ref()
.expect("Unreachable: self.device_info is None")
}
pub fn device(&self) -> &Device
{
self.device.as_ref().expect("Unreachable: self.device is None")
}
pub fn operating_mode(&self) -> DfuOperatingMode
{
self.mode
}
pub fn platform(&self) -> BmpPlatform
{
self.platform
}
pub fn serial_number(&self) -> Result<Ref<'_, str>>
{
let serial = self.serial.borrow();
if serial.is_some() {
return Ok(Ref::map(serial, |s| s.as_deref().unwrap()));
}
drop(serial);
let serial =
self.device()
.get_string_descriptor(self.serial_string_idx, self.language, Duration::from_secs(2))?;
*self.serial.borrow_mut() = Some(serial);
Ok(Ref::map(self.serial.borrow(), |s| s.as_deref().unwrap()))
}
pub fn firmware_identity(&self) -> Result<ProbeIdentity>
{
self.device()
.get_string_descriptor(self.product_string_idx, self.language, Duration::from_secs(2))
.map_err(|e| {
ErrorKind::DeviceSeemsInvalid("no product string descriptor".into())
.error_from(e)
.into()
})
.and_then(|identity| identity.try_into())
}
pub fn port(&self) -> PortId
{
self.port.clone()
}
pub fn display(&self) -> Result<String>
{
let identity = self.firmware_identity()?;
let serial = self.serial_number()?;
Ok(format!("{}\n Serial: {}\n Port: {}", identity, serial, self.port()))
}
pub fn dfu_descriptors(&self) -> Result<(u8, DfuFunctionalDescriptor)>
{
let interface = self.device().active_configuration()?
.interfaces()
.find(|interface|
interface.alt_settings()
.filter(|alt_mode|
InterfaceClass(alt_mode.class()) == InterfaceClass::APPLICATION_SPECIFIC &&
InterfaceSubClass(alt_mode.subclass()) == InterfaceSubClass::DFU
)
.count() > 0
)
.ok_or_else(|| ErrorKind::DeviceSeemsInvalid("could not find DFU interface".into()).error())?;
let dfu_interface_descriptor = interface
.alt_settings()
.nth(0)
.ok_or_else(|| ErrorKind::DeviceSeemsInvalid("no DFU interfaces".into()).error())?;
let extra_descriptors: Vec<_> =
GenericDescriptorRef::multiple_from_bytes(dfu_interface_descriptor.descriptors().as_bytes());
let dfu_func_desc_bytes: &[u8; DfuFunctionalDescriptor::LENGTH as usize] = extra_descriptors
.into_iter()
.find(|descriptor| descriptor.descriptor_type() == DfuFunctionalDescriptor::TYPE)
.expect("DFU interface does not have a DFU functional descriptor! This shouldn't be possible!")
.raw[0..DfuFunctionalDescriptor::LENGTH as usize]
.try_into() .unwrap();
let dfu_func_desc = DfuFunctionalDescriptor::copy_from_bytes(dfu_func_desc_bytes)
.map_err(|source| ErrorKind::DeviceSeemsInvalid("DFU functional descriptor".into()).error_from(source))?;
Ok((dfu_interface_descriptor.interface_number(), dfu_func_desc))
}
fn leave_dfu_mode(&mut self) -> Result<()>
{
debug!("Attempting to leave DFU mode...");
let dfu = DfuNusb::open(
self.device().clone(),
self.interface
.clone()
.ok_or_eyre("BmpDevice does not have valid interface")?,
0,
)?;
let descriptor = dfu.functional_descriptor();
if descriptor.manifestation_tolerant {
dfu.write_control(
request_type(Direction::Out, ControlType::Class, Recipient::Interface),
DfuRequest::Dnload as u8,
0,
&[],
)?;
let mut buf: [u8; 6] = [0; 6];
let status = dfu.read_control(
request_type(Direction::In, ControlType::Class, Recipient::Interface),
DfuRequest::GetStatus as u8,
0,
&mut buf,
)?;
trace!("Device status after zero-length DNLOAD is 0x{:02x}", status);
debug!("DFU_GETSTATUS request completed. Device should now re-enumerate into runtime mode.");
} else {
let timeout_ms = dfu.functional_descriptor().detach_timeout;
handle_detach_errors(dfu.write_control(
request_type(Direction::Out, ControlType::Class, Recipient::Interface),
DfuRequest::Detach as u8,
timeout_ms,
&[],
))?;
debug!("DFU_DETACH request completed. Device should now re-enumerate into runtime mode.");
}
if !dfu.functional_descriptor().will_detach {
dfu.usb_reset()?;
}
self.interface = None;
Ok(())
}
fn enter_dfu_mode(&mut self) -> Result<()>
{
let (iface_number, func_desc) = self.dfu_descriptors()?;
let timeout_ms = func_desc.wDetachTimeOut;
let request = Control {
control_type: ControlType::Class,
recipient: Recipient::Interface,
request: DfuRequest::Detach as u8,
value: timeout_ms,
index: iface_number as u16,
};
handle_detach_errors(
self.interface
.as_ref()
.unwrap()
.control_out_blocking(
request,
&[], Duration::from_secs(1), )
.map_err(DfuNusbError::Transfer),
)?;
debug!("DFU_DETACH request completed. Device should now re-enumerate into DFU mode.");
self.interface = None;
Ok(())
}
pub fn request_detach(&mut self) -> Result<()>
{
use DfuOperatingMode::*;
match self.mode {
Runtime => self.enter_dfu_mode(),
FirmwareUpgrade => self.leave_dfu_mode(),
}
}
pub fn detach_and_enumerate(&mut self) -> Result<()>
{
let port = self.port();
self.request_detach()?;
drop(self.device_info.take());
drop(self.device.take());
thread::sleep(Duration::from_millis(500));
let dev = wait_for_probe_reboot(port, Duration::from_secs(5), "flash")?;
*self = dev;
Ok(())
}
pub fn detach_and_destroy(mut self) -> Result<()>
{
self.request_detach()
}
pub fn reboot(&self, dfu_iface: DfuSync) -> Result<()>
{
if !dfu_iface.manifestation_tolerant() {
handle_detach_errors(dfu_iface.detach())?;
}
if !dfu_iface.will_detach() {
Ok(dfu_iface.usb_reset()?)
} else {
Ok(())
}
}
fn try_download<'r, R>(&mut self, firmware: &'r R, length: u32, dfu_iface: &mut dfu_nusb::DfuSync) -> Result<()>
where
&'r R: Read,
R: ?Sized,
{
dfu_iface.download(firmware, length).map_err(|source| match source {
DfuNusbError::Transfer(nusb::transfer::TransferError::Disconnected) => {
error!("Black Magic Probe device disconnected during the flash process!");
warn!(
"If the device now fails to enumerate, try holding down the button while plugging the device in \
order to enter the bootloader."
);
ErrorKind::DeviceDisconnectDuringOperation.error_from(source).into()
},
_ => source.into(),
})
}
pub fn download<P>(
&mut self,
firmware_file: &FirmwareFile,
firmware_type: FirmwareType,
progress: P,
) -> Result<DfuSync>
where
P: Fn(usize) + 'static,
{
if self.mode == DfuOperatingMode::Runtime {
self.detach_and_enumerate().wrap_err("detaching device for download")?;
}
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"),
self.interface.as_ref().unwrap().clone(),
0,
)?;
if let DfuProtocol::Dfuse {
..
} = dfu_dev.protocol()
{
println!("Erasing flash...");
}
let mut dfu_iface = dfu_dev.into_sync_dfu();
dfu_iface.with_progress(progress).override_address(load_address);
debug!("Load address: 0x{:08x}", load_address);
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::<DfuNusbError>()
{
warn!(
"Device reported an error when trying to flash; going to clear status and try one more time..."
);
thread::sleep(Duration::from_millis(250));
let request = Control {
control_type: ControlType::Class,
recipient: Recipient::Interface,
request: DfuRequest::ClrStatus as u8,
value: 0,
index: 0, };
self.interface
.as_ref()
.unwrap()
.control_out_blocking(request, &[], Duration::from_secs(2))?;
self.try_download(firmware_file.data(), firmware_file.len(), &mut dfu_iface)
} else {
Err(error)
}
},
result => result,
}?;
Ok(dfu_iface)
}
pub fn into_inner_parts(self) -> (DeviceInfo, Device, DfuOperatingMode)
{
(
self.device_info.expect("Unreachable: self.device_info is None"),
self.device.expect("Unreachable: self.device is None"),
self.mode,
)
}
pub fn gdb_serial_interface(&self) -> Result<GdbRspInterface>
{
let serial_interface = ProbeInterface::from_device(self)?;
serial_interface.gdb_interface()
}
pub fn bmd_serial_interface(&self) -> Result<BmdRspInterface>
{
let serial_interface = ProbeInterface::from_device(self)?;
serial_interface.bmd_interface()
}
pub fn aux_serial_interface(&self) -> Result<AuxInterface>
{
let serial_interface = ProbeInterface::from_device(self)?;
serial_interface.aux_interface()
}
}
impl Debug for BmpDevice
{
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result
{
writeln!(f, "BmpDevice {{")?;
writeln!(f, " {:?}", self.device_info)?;
writeln!(f, " {:?}", self.mode)?;
writeln!(f, " {:?}", self.platform)?;
writeln!(f, " {:?}", self.serial)?;
writeln!(f, " {:?}", self.port)?;
writeln!(f, "}}")?;
Ok(())
}
}
impl Display for BmpDevice
{
fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error>
{
let display_str = match self.display() {
Ok(s) => s,
Err(e) => {
error!("Error formatting BlackMagicProbeDevice: {}", e);
"Unknown Black Magic Probe (error occurred fetching device details)".into()
},
};
write!(f, "{}", display_str)?;
Ok(())
}
}
pub fn wait_for_probe_reboot(port: PortId, timeout: Duration, operation: &str) -> Result<BmpDevice>
{
let silence_timeout = timeout / 2;
let matcher = BmpMatcher::new_with_port(port);
let start = Instant::now();
let mut dev = matcher.find_matching_probes().pop_single_silent();
while let Err(ErrorKind::DeviceNotFound) = dev {
trace!(
"Waiting for probe reboot: {} ms",
Instant::now().duration_since(start).as_millis()
);
if Instant::now().duration_since(start) > timeout {
error!("Timed-out waiting for Black Magic Probe to re-enumerate!");
return dev.map_err(|kind| {
Error::from(kind.error())
.wrap_err("Black Magic Probe device did not come back online (invalid firmware?)")
});
}
thread::sleep(Duration::from_millis(200));
if Instant::now().duration_since(start) > silence_timeout {
dev = matcher.find_matching_probes().pop_single(operation);
} else {
dev = matcher.find_matching_probes().pop_single_silent();
}
}
let dev = dev.map_err(|kind| kind.error())?;
Ok(dev)
}
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
pub enum BmpPlatform
{
BlackMagicDebug,
DragonBoot,
STM32DeviceDFU,
}
impl BmpPlatform
{
pub const BMD_DFU_VID_PID: (Vid, Pid) = (Vid(0x1d50), Pid(0x6017));
pub const BMD_RUNTIME_VID_PID: (Vid, Pid) = (Vid(0x1d50), Pid(0x6018));
pub const DRAGON_BOOT_VID_PID: (Vid, Pid) = (Vid(0x1209), Pid(0xbadb));
pub const STM32_DFU_VID_PID: (Vid, Pid) = (Vid(0x0483), Pid(0xdf11));
pub const fn from_vid_pid(vid: Vid, pid: Pid) -> Option<(Self, DfuOperatingMode)>
{
use BmpPlatform::*;
use DfuOperatingMode::*;
match (vid, pid) {
Self::BMD_RUNTIME_VID_PID => Some((BlackMagicDebug, Runtime)),
Self::BMD_DFU_VID_PID => Some((BlackMagicDebug, FirmwareUpgrade)),
Self::DRAGON_BOOT_VID_PID => Some((DragonBoot, FirmwareUpgrade)),
Self::STM32_DFU_VID_PID => Some((STM32DeviceDFU, FirmwareUpgrade)),
_ => None,
}
}
#[allow(dead_code)]
pub const fn runtime_ids(self) -> (Vid, Pid)
{
Self::BMD_RUNTIME_VID_PID
}
#[allow(dead_code)]
pub const fn dfu_ids(self) -> (Vid, Pid)
{
use BmpPlatform::*;
match self {
BlackMagicDebug => Self::BMD_DFU_VID_PID,
DragonBoot => Self::DRAGON_BOOT_VID_PID,
STM32DeviceDFU => Self::STM32_DFU_VID_PID,
}
}
#[allow(dead_code)]
pub const fn ids_for_mode(self, mode: DfuOperatingMode) -> (Vid, Pid)
{
use DfuOperatingMode::*;
match mode {
Runtime => self.runtime_ids(),
FirmwareUpgrade => self.dfu_ids(),
}
}
pub const fn load_address(self, firm_type: FirmwareType) -> u32
{
use BmpPlatform::*;
use FirmwareType::*;
match self {
BlackMagicDebug => match firm_type {
Bootloader => 0x0800_0000,
Application => 0x0800_2000,
},
DragonBoot => 0x0800_2000,
STM32DeviceDFU => 0x0800_0000,
}
}
}
impl Default for BmpPlatform
{
fn default() -> Self
{
BmpPlatform::BlackMagicDebug
}
}