use crate::dfuse_command::DfuseCommand;
use crate::error::Error;
use crate::memory_layout::MemoryLayout;
use crate::status::{State, Status};
use std::convert::TryFrom;
use std::fs::File;
use std::io::{Read, Write};
use std::slice::Iter;
use std::str::FromStr;
use std::time::Duration;
use usbapi::*;
#[allow(dead_code)]
const DFU_DETACH: u8 = 0;
const DFU_DNLOAD: u8 = 1;
#[allow(dead_code)]
const DFU_UPLOAD: u8 = 2;
pub(crate) const DFU_GET_STATUS: u8 = 3;
const DFU_CLRSTATUS: u8 = 4;
#[allow(dead_code)]
const DFU_GETSTATE: u8 = 5;
#[allow(dead_code)]
const DFU_ABORT: u8 = 6;
#[derive(Debug)]
struct Transaction {
transaction: u16,
address: u32,
pending: u32,
xfer: u16,
xfer_max: u16,
}
impl Transaction {
fn new(address: u32, pending: u32, xfer_max: u16) -> Self {
let mut t = Transaction {
transaction: 2,
address,
pending,
xfer: xfer_max,
xfer_max,
};
t.set_xfer();
t
}
fn set_xfer(&mut self) {
if self.pending >= self.xfer_max as u32 {
self.xfer = self.xfer_max;
self.pending -= self.xfer_max as u32;
} else {
self.xfer = (self.pending % self.xfer_max as u32) as u16;
self.pending = 0;
}
}
}
impl Iterator for Transaction {
type Item = ();
fn next(&mut self) -> Option<()> {
if self.pending == 0 {
self.xfer = 0;
return None;
}
self.set_xfer();
self.address += self.xfer as u32;
self.transaction += 1;
Some(())
}
}
struct DfuDescriptor {
length: u8,
descriptor_type: u8,
attributes: u8,
detach_timeout: u16,
transfer_size: u16,
dfu_version: u8,
}
impl DfuDescriptor {
fn new(mut iter: Vec<u8>) -> Option<Self> {
let mut iter = iter.iter_mut();
Some(DfuDescriptor {
length: if *iter.next()? == 9 { 9 } else { return None },
descriptor_type: if *iter.next()? == 33 { 33 } else { return None },
attributes: *iter.next()?,
detach_timeout: *iter.next()? as u16 | (*iter.next()? as u16) << 8,
transfer_size: *iter.next()? as u16 | (*iter.next()? as u16) << 8,
dfu_version: *iter.next()?,
})
}
}
pub struct Dfu {
usb: UsbCore,
timeout: Duration,
interface: u16,
detached: bool,
dfu_descriptor: DfuDescriptor,
mem_layout: MemoryLayout,
}
impl Drop for Dfu {
fn drop(&mut self) {
if self.detached {
return;
}
if self.status_wait_for(0, Some(State::DfuIdle)).is_err() {
log::debug!("Dfu was not idle abort to idle");
self.abort_to_idle().unwrap_or_else(|e| {
log::warn!("Abort to idle failed {}", e);
});
}
self.usb
.release_interface(self.interface as u32)
.unwrap_or_else(|e| {
log::warn!("Release interface failed with {}", e);
});
}
}
impl Dfu {
fn setup(mut usb: UsbCore, iface_index: u32, alt: u32) -> Result<Self, Error> {
usb.claim_interface(iface_index).unwrap_or_else(|e| {
log::error!("Claim interface failed with {}", e);
});
usb.set_interface(iface_index, alt).unwrap_or_else(|e| {
log::error!("Set interface failed with {}", e);
});
let iface = usb
.descriptors()
.as_ref()
.and_then(|dev| dev.device.configurations.get(iface_index as usize))
.and_then(|conf| conf.interfaces.get(iface_index as usize))
.map(|iface| iface.iinterface)
.ok_or_else(|| Error::DeviceNotFound("Missing configuration descriptor".to_string()))?;
log::debug!("iface id {:?}", iface);
let mem_layout = MemoryLayout::from_str(&usb.get_descriptor_string_iface(0, iface)?)?;
let dfu_descriptor = usb
.descriptors()
.as_ref()
.and_then(|dev| dev.device.configurations.get(iface_index as usize))
.and_then(|conf| conf.unknown_descriptors.get(0))
.and_then(|desc| DfuDescriptor::new(desc.clone()))
.ok_or_else(|| {
Error::DeviceNotFound("Missing configuration dfu transfer descriptor".to_string())
})?;
log::debug!("Transfer size: {} bytes", dfu_descriptor.transfer_size);
let timeout = Duration::from_millis(3000);
Ok(Self {
usb,
timeout,
interface: 0,
dfu_descriptor,
detached: false,
mem_layout,
})
}
pub fn from_bus_device(bus: u8, dev: u8, iface_index: u32, alt: u32) -> Result<Self, Error> {
let mut usb =
UsbCore::from_bus_device(bus, dev).map_err(|e| Error::USB("open".into(), e))?;
let mut dfu = Dfu::setup(usb, iface_index, alt)?;
dfu.abort_to_idle_clear_once()?;
Ok(dfu)
}
pub fn get_status(&mut self, mut retries: u8) -> Result<Status, Error> {
let mut status = Err(Error::Argument("Get status retries failed".into()));
retries += 1;
while retries > 0 {
retries -= 1;
status = Status::get(&mut self.usb, self.interface);
if let Err(e) = &status {
if let Error::USB(_, e) = e {
if e.kind() == std::io::ErrorKind::BrokenPipe {
log::warn!("Epipe try again");
std::thread::sleep(std::time::Duration::from_millis(3000));
continue;
}
} else if let Error::InvalidControlResponse(e) = e {
log::warn!("retries {} Get status error cause '{}'", retries, e);
std::thread::sleep(std::time::Duration::from_millis(100));
continue;
}
} else {
retries = 0;
}
}
status
}
pub fn clear_status(&mut self) -> Result<(), Error> {
let ctl = self.usb.new_control_nodata(
ENDPOINT_OUT | REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE,
DFU_CLRSTATUS,
0,
self.interface,
)?;
let _ = self
.usb
.control_async_wait(ctl, TimeoutMillis::from(self.timeout))
.map_err(|e| Error::USB("Control transfer".into(), e))?;
Ok(())
}
pub fn detach(&mut self) -> Result<(), Error> {
let ctl = self.usb.new_control_nodata(
ENDPOINT_OUT | REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE,
DFU_DETACH,
0,
self.interface,
)?;
let _ = self
.usb
.control_async_wait(ctl, TimeoutMillis::from(self.timeout))
.map_err(|e| Error::USB("Detach".into(), e))?;
Ok(())
}
pub fn status_wait_for(
&mut self,
mut retries: u8,
wait_for_state: Option<State>,
) -> Result<Status, Error> {
retries += 1;
let wait_for_state = if let Some(wait_for_state) = wait_for_state {
wait_for_state
} else {
State::DfuDownloadBusy
};
let mut s = self.get_status(10)?;
while retries > 0 {
if s.state == u8::from(&wait_for_state) {
break;
}
std::thread::sleep(std::time::Duration::from_millis(100));
retries -= 1;
s = self.get_status(10)?;
}
if s.state != u8::from(&wait_for_state) {
return Err(Error::InvalidState(s, wait_for_state));
}
if s.status != 0 {
return Err(Error::InvalidStatus(s, 0));
}
Ok(s)
}
pub fn set_address(&mut self, address: u32) -> Result<(), Error> {
self.dfuse_download(Vec::from(DfuseCommand::SetAddress(address)), 0)?;
self.status_wait_for(0, Some(State::DfuDownloadIdle))?;
Ok(())
}
pub fn reset_stm32(&mut self, address: u32) -> Result<(), Error> {
self.set_address(address)?;
log::debug!("set done");
self.dfuse_download(Vec::new(), 2)?;
log::debug!("dfuse None, 2 done");
self.get_status(0).unwrap_or_else(|e| {
log::warn!("get_status failed cause {}", e);
Status::default()
});
self.detached = true;
Ok(())
}
pub fn dfuse_get_commands(&mut self) -> Result<Vec<DfuseCommand>, Error> {
self.abort_to_idle()?;
let mut v = Vec::new();
let cmds = &self.dfuse_upload(0, 1024)?;
if let Some(cmd) = cmds.iter().next() {
if *cmd != 0 {
return Err(Error::InvalidControlResponse(format!(
"Get command {:X} {:X?}",
cmd, cmds
)));
}
}
for cmd in &cmds[1..] {
v.push(DfuseCommand::try_from(*cmd)?)
}
Ok(v)
}
pub fn verify(
&mut self,
file: &mut File,
address: u32,
length: Option<u32>,
) -> Result<(), Error> {
let length = Self::get_length_from_file(file, length)?;
self.dfuse_download(Vec::from(DfuseCommand::SetAddress(address)), 0)?;
self.status_wait_for(0, None)?;
self.abort_to_idle()?;
self.status_wait_for(0, Some(State::DfuIdle))?;
let mut t = Transaction::new(address, length, self.dfu_descriptor.transfer_size);
while t.xfer > 0 {
let address = t.address;
self.flash_read_chunk(&mut t, |v| {
let mut r = vec![0; v.len()];
file.read_exact(&mut r)?;
let mut i2 = v.iter();
for (i, byte) in r.iter().enumerate() {
if let Some(byte2) = i2.next() {
if byte == byte2 {
continue;
}
}
return Err(Error::Verify(address + i as u32));
}
if v.len() != r.len() {
return Err(Error::Verify(address + v.len() as u32));
}
Ok(())
})?;
}
self.abort_to_idle()?;
Ok(())
}
pub fn erase_pages(&mut self, mut address: u32, length: u32) -> Result<(), Error> {
self.status_wait_for(0, Some(State::DfuIdle))?;
let mut pages = self.mem_layout.num_pages(address, length)?;
let page = self.mem_layout.address(address)?;
address = page.address;
while pages > 0 {
self.dfuse_download(Vec::from(DfuseCommand::ErasePage(address)), 0)?;
self.status_wait_for(0, Some(State::DfuDownloadBusy))?;
self.status_wait_for(100, Some(State::DfuDownloadIdle))?;
pages -= 1;
address += page.size;
}
Ok(())
}
pub fn mass_erase(&mut self) -> Result<(), Error> {
self.status_wait_for(0, Some(State::DfuIdle))?;
self.dfuse_download(Vec::from(DfuseCommand::MassErase), 0)?;
self.status_wait_for(0, Some(State::DfuDownloadBusy))?;
self.status_wait_for(10, Some(State::DfuDownloadIdle))?;
Ok(())
}
fn flash_read_chunk<F>(&mut self, t: &mut Transaction, mut f: F) -> Result<(), Error>
where
F: FnMut(Vec<u8>) -> Result<(), Error>,
{
log::debug!("{:X?}", t);
let v = self.dfuse_upload(t.transaction, t.xfer)?;
f(v)?;
let _ = t.next().is_some();
Ok(())
}
pub fn write_flash_from_slice(&mut self, address: u32, buf: &[u8]) -> Result<usize, Error> {
let mut length = buf.len() as u32;
self.erase_pages(address, length)?;
self.abort_to_idle()?;
self.status_wait_for(0, Some(State::DfuIdle))?;
let mut transaction = 2;
let mut xfer;
if length >= self.dfu_descriptor.transfer_size as u32 {
panic!(
"FIXME write_flash_from_slice only allow xfer size max {}",
self.dfu_descriptor.transfer_size
);
}
while length != 0 {
if length >= self.dfu_descriptor.transfer_size as u32 {
xfer = self.dfu_descriptor.transfer_size;
length -= self.dfu_descriptor.transfer_size as u32;
} else {
xfer = length as u16;
length = 0;
}
log::debug!(
"{}: 0x{:4X} xfer: {} length: {}",
transaction,
address,
xfer,
length
);
self.dfuse_download(Vec::from(DfuseCommand::SetAddress(address)), 0)?;
self.status_wait_for(100, Some(State::DfuDownloadIdle))?;
self.dfuse_download(buf.into(), transaction)?;
self.status_wait_for(100, Some(State::DfuDownloadBusy))?;
self.status_wait_for(100, Some(State::DfuDownloadIdle))?;
transaction += 1;
}
self.abort_to_idle()?;
Ok(length as usize)
}
pub fn read_flash_to_slice(&mut self, address: u32, buf: &mut [u8]) -> Result<usize, Error> {
self.dfuse_download(Vec::from(DfuseCommand::SetAddress(address)), 0)?;
self.status_wait_for(0, None)?;
self.abort_to_idle()?;
self.status_wait_for(0, Some(State::DfuIdle))?;
let mut len = 0;
let size = buf.len();
let mut t = Transaction::new(address, size as u32, self.dfu_descriptor.transfer_size);
while t.xfer > 0 {
self.flash_read_chunk(&mut t, |v| {
for b in v {
buf[len] = b;
len += 1;
}
Ok(())
})?;
}
self.abort_to_idle()?;
Ok(len)
}
pub fn upload(&mut self, file: &mut File, address: u32, length: u32) -> Result<(), Error> {
self.dfuse_download(Vec::from(DfuseCommand::SetAddress(address)), 0)?;
self.status_wait_for(0, None)?;
self.abort_to_idle()?;
self.status_wait_for(0, Some(State::DfuIdle))?;
let mut t = Transaction::new(address, length, self.dfu_descriptor.transfer_size);
while t.xfer > 0 {
self.flash_read_chunk(&mut t, |v| Ok(file.write_all(&v)?))?;
}
self.abort_to_idle()?;
Ok(())
}
pub fn abort_to_idle_clear_once(&mut self) -> Result<(), Error> {
let s = self.get_status(0)?;
if s.state == u8::from(&State::DfuIdle) {
log::debug!("Status is {}", s.state);
return Ok(());
}
let ctl = self.usb.new_control_nodata(
ENDPOINT_OUT | REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE,
DFU_ABORT,
0,
self.interface,
)?;
self.usb
.control_async_wait(ctl, TimeoutMillis::from(self.timeout))
.map_err(|e| Error::USB("Abort to idle".into(), e))?;
let s = self.get_status(0)?;
log::debug!("Status is after one abort {}", s.state);
if s.state != u8::from(&State::DfuIdle) {
self.clear_status()?;
log::debug!("Status cleared");
self.get_status(0)?;
}
Ok(())
}
pub fn abort_to_idle(&mut self) -> Result<(), Error> {
let ctl = self.usb.new_control_nodata(
ENDPOINT_OUT | REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE,
DFU_ABORT,
0,
self.interface,
)?;
self.usb
.control_async_wait(ctl, TimeoutMillis::from(self.timeout))
.map_err(|e| Error::USB("Abort to idle".into(), e))?;
let s = self.get_status(0)?;
if s.state != u8::from(&State::DfuIdle) {
return Err(Error::InvalidState(s, State::DfuIdle));
}
Ok(())
}
fn get_length_from_file(file: &File, length: Option<u32>) -> Result<u32, Error> {
let file_length = file.metadata()?.len() as u32;
Ok(match length {
Some(length) => {
if file_length < length {
return Err(Error::Argument(format!(
"error on '{:?}' is {} bytes, but length is set to {} bytes",
file, file_length, length
)));
}
length
}
None => {
if file_length == 0 {
return Err(Error::Argument(format!("File '{:?}' is empty", file)));
}
file_length
}
})
}
pub fn download_raw(
&mut self,
file: &mut File,
address: u32,
length: Option<u32>,
) -> Result<(), Error> {
let mut length = Self::get_length_from_file(file, length)?;
self.erase_pages(address, length)?;
self.abort_to_idle()?;
self.status_wait_for(0, Some(State::DfuIdle))?;
let mut transaction = 2;
let mut xfer;
while length != 0 {
if length >= self.dfu_descriptor.transfer_size as u32 {
xfer = self.dfu_descriptor.transfer_size;
length -= self.dfu_descriptor.transfer_size as u32;
} else {
xfer = length as u16;
length = 0;
}
log::debug!(
"{}: 0x{:4X} xfer: {} length: {}",
transaction,
address,
xfer,
length
);
let mut buf = vec![0; xfer as usize];
file.read_exact(&mut buf)?;
self.dfuse_download(Vec::from(DfuseCommand::SetAddress(address)), 0)?;
self.status_wait_for(100, Some(State::DfuDownloadIdle))?;
self.dfuse_download(buf, transaction)?;
self.status_wait_for(100, Some(State::DfuDownloadBusy))?;
self.status_wait_for(100, Some(State::DfuDownloadIdle))?;
transaction += 1;
}
self.abort_to_idle()?;
Ok(())
}
fn dfuse_download(&mut self, buf: Vec<u8>, transaction: u16) -> Result<(), Error> {
let ctl = self.usb.new_control_out(
ENDPOINT_OUT | REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE,
DFU_DNLOAD,
transaction,
self.interface,
&buf,
)?;
match self
.usb
.control_async_wait(ctl, TimeoutMillis::from(self.timeout))
{
Err(e) if e.kind() == std::io::ErrorKind::BrokenPipe => {
log::warn!("stalled on transaction {}", transaction);
std::thread::sleep(std::time::Duration::from_millis(10));
Ok(())
}
Err(e) => Err(Error::USB("Dfuse download".into(), e)),
Ok(_) => Ok(()),
}
}
pub fn descriptors(&mut self) -> &Option<UsbDevice> {
self.usb.descriptors()
}
pub fn memory_layout(&self) -> &MemoryLayout {
&self.mem_layout
}
fn dfuse_upload(&mut self, transaction: u16, xfer: u16) -> Result<Vec<u8>, Error> {
let ctl = self.usb.new_control_in(
ENDPOINT_IN | REQUEST_TYPE_CLASS | RECIPIENT_INTERFACE,
DFU_UPLOAD,
transaction,
self.interface,
xfer,
)?;
match self
.usb
.control_async_wait(ctl, TimeoutMillis::from(self.timeout))
{
Err(e) => Err(Error::USB("Dfuse upload".into(), e)),
Ok(buf) => Ok(buf.buffer_from_raw().to_vec()),
}
}
pub fn usb(&mut self) -> &mut UsbCore {
&mut self.usb
}
}