#![no_std]
#![warn(missing_docs)]
#![allow(clippy::type_complexity)]
#![cfg_attr(docsrs, feature(doc_cfg))]
#[cfg(any(feature = "std", test))]
#[macro_use]
extern crate std;
pub mod detach;
pub mod download;
pub mod functional_descriptor;
pub mod get_status;
pub mod memory_layout;
#[cfg(any(feature = "std", test))]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub mod sync;
use core::convert::TryFrom;
use displaydoc::Display;
#[cfg(any(feature = "std", test))]
use thiserror::Error;
#[derive(Debug, Display)]
#[cfg_attr(any(feature = "std", test), derive(Error))]
#[allow(missing_docs)]
pub enum Error {
OutOfCapabilities,
InvalidState { got: State, expected: State },
BufferTooBig { got: usize, expected: usize },
MaximumTransferSizeExceeded,
EraseLimitReached,
MaximumChunksExceeded,
NoSpaceLeft,
UnrecognizedStatusCode(u8),
UnrecognizedStateCode(u8),
ResponseTooShort { got: usize, expected: usize },
StatusError(Status),
StateError(State),
UnknownProtocol,
InvalidInterfaceString,
#[cfg(any(feature = "std", test))]
MemoryLayout(memory_layout::Error),
InvalidAddress,
}
pub trait DfuIo {
type Read;
type Write;
type Reset;
type Error: From<Error>;
type MemoryLayout: AsRef<memory_layout::mem>;
fn read_control(
&self,
request_type: u8,
request: u8,
value: u16,
buffer: &mut [u8],
) -> Result<Self::Read, Self::Error>;
fn write_control(
&self,
request_type: u8,
request: u8,
value: u16,
buffer: &[u8],
) -> Result<Self::Write, Self::Error>;
fn usb_reset(&self) -> Result<Self::Reset, Self::Error>;
fn protocol(&self) -> &DfuProtocol<Self::MemoryLayout>;
fn functional_descriptor(&self) -> &functional_descriptor::FunctionalDescriptor;
}
pub enum DfuProtocol<M> {
Dfu,
Dfuse {
address: u32,
memory_layout: M,
},
}
#[cfg(any(feature = "std", test))]
impl DfuProtocol<memory_layout::MemoryLayout> {
pub fn new(interface_string: &str, version: (u8, u8)) -> Result<Self, Error> {
match version {
(0x1, 0x10) => Ok(DfuProtocol::Dfu),
(0x1, 0x1a) => {
let (rest, memory_layout) = interface_string
.rsplit_once('/')
.ok_or(Error::InvalidInterfaceString)?;
let memory_layout = memory_layout::MemoryLayout::try_from(memory_layout)
.map_err(Error::MemoryLayout)?;
let (_rest, address) =
rest.rsplit_once('/').ok_or(Error::InvalidInterfaceString)?;
let address = address
.strip_prefix("0x")
.and_then(|s| u32::from_str_radix(s, 16).ok())
.ok_or(Error::InvalidAddress)?;
Ok(DfuProtocol::Dfuse {
address,
memory_layout,
})
}
_ => Err(Error::UnknownProtocol),
}
}
}
pub struct DfuSansIo<IO> {
io: IO,
}
impl<IO: DfuIo> DfuSansIo<IO> {
pub fn new(io: IO) -> Self {
Self { io }
}
fn protocol(&self) -> &DfuProtocol<IO::MemoryLayout> {
self.io.protocol()
}
pub fn download(
&self,
length: u32,
) -> Result<
get_status::GetStatus<
'_,
IO,
get_status::ClearStatus<'_, IO, get_status::GetStatus<'_, IO, download::Start<'_, IO>>>,
>,
Error,
> {
let (protocol, end_pos) = match self.protocol() {
DfuProtocol::Dfu => (download::ProtocolData::Dfu, length),
DfuProtocol::Dfuse {
address,
memory_layout,
..
} => (
download::ProtocolData::Dfuse(download::DfuseProtocolData {
address: *address,
erased_pos: *address,
address_set: false,
memory_layout: memory_layout.as_ref(),
}),
address.checked_add(length).ok_or(Error::NoSpaceLeft)?,
),
};
Ok(get_status::GetStatus {
dfu: self,
chained_command: get_status::ClearStatus {
dfu: self,
chained_command: get_status::GetStatus {
dfu: self,
chained_command: download::Start {
dfu: self,
protocol,
end_pos,
},
},
},
})
}
pub fn detach(&self) -> Result<(), IO::Error> {
const REQUEST_TYPE: u8 = 0b00100001;
const DFU_DETACH: u8 = 0;
self.io.write_control(REQUEST_TYPE, DFU_DETACH, 1000, &[])?;
Ok(())
}
pub fn usb_reset(&self) -> Result<IO::Reset, IO::Error> {
self.io.usb_reset()
}
pub fn into_inner(self) -> IO {
self.io
}
pub fn will_detach(&self) -> bool {
self.io.functional_descriptor().will_detach
}
pub fn manifestation_tolerant(&self) -> bool {
self.io.functional_descriptor().manifestation_tolerant
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Display)]
pub enum Status {
Ok,
ErrTarget,
ErrFile,
ErrWrite,
ErrErase,
ErrCheckErased,
ErrProg,
ErrVerify,
ErrAddress,
ErrNotdone,
ErrFirmware,
ErrVendor,
ErrUsbr,
ErrPor,
ErrUnknown,
ErrStalledpkt,
Other(u8),
}
impl From<u8> for Status {
fn from(state: u8) -> Self {
match state {
0x00 => Status::Ok,
0x01 => Status::ErrTarget,
0x02 => Status::ErrFile,
0x03 => Status::ErrWrite,
0x04 => Status::ErrErase,
0x05 => Status::ErrCheckErased,
0x06 => Status::ErrProg,
0x07 => Status::ErrVerify,
0x08 => Status::ErrAddress,
0x09 => Status::ErrNotdone,
0x0a => Status::ErrFirmware,
0x0b => Status::ErrVendor,
0x0c => Status::ErrUsbr,
0x0d => Status::ErrPor,
0x0e => Status::ErrUnknown,
0x0f => Status::ErrStalledpkt,
other => Status::Other(other),
}
}
}
impl From<Status> for u8 {
fn from(state: Status) -> Self {
match state {
Status::Ok => 0x00,
Status::ErrTarget => 0x01,
Status::ErrFile => 0x02,
Status::ErrWrite => 0x03,
Status::ErrErase => 0x04,
Status::ErrCheckErased => 0x05,
Status::ErrProg => 0x06,
Status::ErrVerify => 0x07,
Status::ErrAddress => 0x08,
Status::ErrNotdone => 0x09,
Status::ErrFirmware => 0x0a,
Status::ErrVendor => 0x0b,
Status::ErrUsbr => 0x0c,
Status::ErrPor => 0x0d,
Status::ErrUnknown => 0x0e,
Status::ErrStalledpkt => 0x0f,
Status::Other(other) => other,
}
}
}
#[derive(Debug, Clone, Copy, Eq, PartialEq, Display)]
pub enum State {
AppIdle,
AppDetach,
DfuIdle,
DfuDnloadSync,
DfuDnbusy,
DfuDnloadIdle,
DfuManifestSync,
DfuManifest,
DfuManifestWaitReset,
DfuUploadIdle,
DfuError,
Other(u8),
}
impl From<u8> for State {
fn from(state: u8) -> Self {
match state {
0 => State::AppIdle,
1 => State::AppDetach,
2 => State::DfuIdle,
3 => State::DfuDnloadSync,
4 => State::DfuDnbusy,
5 => State::DfuDnloadIdle,
6 => State::DfuManifestSync,
7 => State::DfuManifest,
8 => State::DfuManifestWaitReset,
9 => State::DfuUploadIdle,
10 => State::DfuError,
other => State::Other(other),
}
}
}
impl From<State> for u8 {
fn from(state: State) -> Self {
match state {
State::AppIdle => 0,
State::AppDetach => 1,
State::DfuIdle => 2,
State::DfuDnloadSync => 3,
State::DfuDnbusy => 4,
State::DfuDnloadIdle => 5,
State::DfuManifestSync => 6,
State::DfuManifest => 7,
State::DfuManifestWaitReset => 8,
State::DfuUploadIdle => 9,
State::DfuError => 10,
State::Other(other) => other,
}
}
}
impl State {
fn for_status(self) -> Self {
match self {
State::DfuManifestSync => State::DfuManifest,
State::DfuDnloadSync => State::DfuDnbusy,
_ => self,
}
}
}
pub trait ChainedCommand {
type Arg;
type Into;
fn chain(self, arg: Self::Arg) -> Self::Into;
}
#[cfg(test)]
mod tests {
use super::*;
use std::prelude::v1::*;
const _: [&dyn DfuIo<Read = (), Write = (), Reset = (), MemoryLayout = (), Error = Error>; 0] =
[];
}