use embassy_usb_driver::Driver;
use super::consts::{
APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_DFU, DfuAttributes, Request, State, Status,
USB_CLASS_APPN_SPEC,
};
use crate::control::{InResponse, OutResponse, Recipient, Request as ControlRequest, RequestType};
use crate::{Builder, FunctionBuilder};
pub trait Handler {
fn start(&mut self) -> Result<(), Status>;
fn write(&mut self, data: &[u8]) -> Result<(), Status>;
fn finish(&mut self) -> Result<(), Status>;
fn system_reset(&mut self);
}
pub struct DfuState<H: Handler> {
handler: H,
attrs: DfuAttributes,
state: State,
status: Status,
next_block_num: usize,
}
impl<'d, H: Handler> DfuState<H> {
pub fn new(handler: H, attrs: DfuAttributes) -> Self {
Self {
handler,
attrs,
state: State::DfuIdle,
status: Status::Ok,
next_block_num: 0,
}
}
pub fn set_to_firmware_error(&mut self) {
self.reset_state();
self.state = State::Error;
self.status = Status::ErrFirmware;
}
fn reset_state(&mut self) {
self.next_block_num = 0;
self.state = State::DfuIdle;
self.status = Status::Ok;
}
}
impl<H: Handler> crate::Handler for DfuState<H> {
fn reset(&mut self) {
if matches!(self.state, State::ManifestSync | State::Manifest) {
self.handler.system_reset();
}
}
fn control_out(&mut self, req: ControlRequest, data: &[u8]) -> Option<OutResponse> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
match Request::try_from(req.request) {
Ok(Request::Abort) => {
info!("Abort requested");
self.reset_state();
Some(OutResponse::Accepted)
}
Ok(Request::Dnload) if self.attrs.contains(DfuAttributes::CAN_DOWNLOAD) => {
if req.value as usize != self.next_block_num {
error!("expected next block num {}, got {}", self.next_block_num, req.value);
self.state = State::Error;
self.status = Status::ErrUnknown;
return Some(OutResponse::Rejected);
}
if req.value == 0 {
match self.handler.start() {
Ok(_) => {
self.state = State::Download;
}
Err(e) => {
self.state = State::Error;
self.status = e;
return Some(OutResponse::Rejected);
}
}
}
if req.length == 0 {
match self.handler.finish() {
Ok(_) => {
self.status = Status::Ok;
self.state = State::ManifestSync;
}
Err(e) => {
self.state = State::Error;
self.status = e;
}
}
} else {
if self.state != State::Download {
error!("Unexpected DNLOAD while chip is waiting for a GETSTATUS");
self.status = Status::ErrUnknown;
self.state = State::Error;
return Some(OutResponse::Rejected);
}
match self.handler.write(data) {
Ok(_) => {
self.status = Status::Ok;
self.state = State::DlSync;
self.next_block_num += 1;
}
Err(e) => {
self.state = State::Error;
self.status = e;
}
}
}
Some(OutResponse::Accepted)
}
Ok(Request::Detach) => Some(OutResponse::Accepted), Ok(Request::ClrStatus) => {
info!("Clear status requested");
self.reset_state();
Some(OutResponse::Accepted)
}
_ => {
debug!("Unknown OUT request {:?}", req);
None
}
}
}
fn control_in<'a>(&'a mut self, req: ControlRequest, buf: &'a mut [u8]) -> Option<InResponse<'a>> {
if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) {
return None;
}
match Request::try_from(req.request) {
Ok(Request::GetStatus) => {
match self.state {
State::DlSync => self.state = State::Download,
State::ManifestSync if self.attrs.contains(DfuAttributes::MANIFESTATION_TOLERANT) => {
self.state = State::DfuIdle
}
State::ManifestSync => {
self.state = State::Manifest;
if self.attrs.contains(DfuAttributes::WILL_DETACH) {
self.reset();
}
}
_ => {}
}
buf[0..6].copy_from_slice(&[self.status as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]);
Some(InResponse::Accepted(&buf[0..6]))
}
Ok(Request::GetState) => {
buf[0] = self.state as u8;
Some(InResponse::Accepted(&buf[0..1]))
}
Ok(Request::Upload) if self.attrs.contains(DfuAttributes::CAN_UPLOAD) => {
Some(InResponse::Rejected)
}
_ => {
debug!("Unknown IN request {:?}", req);
None
}
}
}
}
pub fn usb_dfu<'d, D: Driver<'d>, H: Handler>(
builder: &mut Builder<'d, D>,
state: &'d mut DfuState<H>,
max_write_size: usize,
func_modifier: impl Fn(&mut FunctionBuilder<'_, 'd, D>),
) {
let mut func = builder.function(0x00, 0x00, 0x00);
func_modifier(&mut func);
let mut iface = func.interface();
let mut alt = iface.alt_setting(USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DFU_PROTOCOL_DFU, None);
alt.descriptor(
DESC_DFU_FUNCTIONAL,
&[
state.attrs.bits(),
0xc4,
0x09, (max_write_size & 0xff) as u8,
((max_write_size & 0xff00) >> 8) as u8,
0x10,
0x01, ],
);
drop(func);
builder.handler(state);
}