diff options
| author | Kaitlyn Kenwell <[email protected]> | 2023-12-13 14:40:49 -0500 |
|---|---|---|
| committer | Kaitlyn Kenwell <[email protected]> | 2023-12-13 14:40:49 -0500 |
| commit | 976a7ae22aa222213861c12d515115aac87bd2e0 (patch) | |
| tree | 88f802c7d57df011c02142ef309ff5cffe8bd3b0 /embassy-usb-dfu/src/application.rs | |
| parent | 14f41a71b6ea9dedb4ee5b9c741fe10575772c7d (diff) | |
Add embassy-usb-dfu
Diffstat (limited to 'embassy-usb-dfu/src/application.rs')
| -rw-r--r-- | embassy-usb-dfu/src/application.rs | 114 |
1 files changed, 114 insertions, 0 deletions
diff --git a/embassy-usb-dfu/src/application.rs b/embassy-usb-dfu/src/application.rs new file mode 100644 index 000000000..5a52a9fed --- /dev/null +++ b/embassy-usb-dfu/src/application.rs | |||
| @@ -0,0 +1,114 @@ | |||
| 1 | |||
| 2 | use embassy_boot::BlockingFirmwareUpdater; | ||
| 3 | use embassy_time::{Instant, Duration}; | ||
| 4 | use embassy_usb::{Handler, control::{RequestType, Recipient, OutResponse, InResponse}, Builder, driver::Driver}; | ||
| 5 | use embedded_storage::nor_flash::NorFlash; | ||
| 6 | |||
| 7 | use crate::consts::{DfuAttributes, Request, Status, State, USB_CLASS_APPN_SPEC, APPN_SPEC_SUBCLASS_DFU, DESC_DFU_FUNCTIONAL, DFU_PROTOCOL_RT}; | ||
| 8 | |||
| 9 | /// Internal state for the DFU class | ||
| 10 | pub struct Control<'d, DFU: NorFlash, STATE: NorFlash> { | ||
| 11 | updater: BlockingFirmwareUpdater<'d, DFU, STATE>, | ||
| 12 | attrs: DfuAttributes, | ||
| 13 | state: State, | ||
| 14 | timeout: Option<Duration>, | ||
| 15 | detach_start: Option<Instant>, | ||
| 16 | } | ||
| 17 | |||
| 18 | impl<'d, DFU: NorFlash, STATE: NorFlash> Control<'d, DFU, STATE> { | ||
| 19 | pub fn new(updater: BlockingFirmwareUpdater<'d, DFU, STATE>, attrs: DfuAttributes) -> Self { | ||
| 20 | Control { updater, attrs, state: State::AppIdle, detach_start: None, timeout: None } | ||
| 21 | } | ||
| 22 | } | ||
| 23 | |||
| 24 | impl<'d, DFU: NorFlash, STATE: NorFlash> Handler for Control<'d, DFU, STATE> { | ||
| 25 | fn reset(&mut self) { | ||
| 26 | if let Some(start) = self.detach_start { | ||
| 27 | let delta = Instant::now() - start; | ||
| 28 | let timeout = self.timeout.unwrap(); | ||
| 29 | #[cfg(feature = "defmt")] | ||
| 30 | defmt::info!("Received RESET with delta = {}, timeout = {}", delta.as_millis(), timeout.as_millis()); | ||
| 31 | if delta < timeout { | ||
| 32 | self.updater.mark_dfu().expect("Failed to mark DFU mode in bootloader"); | ||
| 33 | cortex_m::asm::dsb(); | ||
| 34 | cortex_m::peripheral::SCB::sys_reset(); | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | fn control_out(&mut self, req: embassy_usb::control::Request, _: &[u8]) -> Option<embassy_usb::control::OutResponse> { | ||
| 40 | if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { | ||
| 41 | return None; | ||
| 42 | } | ||
| 43 | |||
| 44 | #[cfg(feature = "defmt")] | ||
| 45 | defmt::info!("Received request {}", req); | ||
| 46 | |||
| 47 | match Request::try_from(req.request) { | ||
| 48 | Ok(Request::Detach) => { | ||
| 49 | #[cfg(feature = "defmt")] | ||
| 50 | defmt::info!("Received DETACH, awaiting USB reset"); | ||
| 51 | self.detach_start = Some(Instant::now()); | ||
| 52 | self.timeout = Some(Duration::from_millis(req.value as u64)); | ||
| 53 | self.state = State::AppDetach; | ||
| 54 | Some(OutResponse::Accepted) | ||
| 55 | } | ||
| 56 | _ => { | ||
| 57 | None | ||
| 58 | } | ||
| 59 | } | ||
| 60 | } | ||
| 61 | |||
| 62 | fn control_in<'a>(&'a mut self, req: embassy_usb::control::Request, buf: &'a mut [u8]) -> Option<embassy_usb::control::InResponse<'a>> { | ||
| 63 | if (req.request_type, req.recipient) != (RequestType::Class, Recipient::Interface) { | ||
| 64 | return None; | ||
| 65 | } | ||
| 66 | |||
| 67 | #[cfg(feature = "defmt")] | ||
| 68 | defmt::info!("Received request {}", req); | ||
| 69 | |||
| 70 | match Request::try_from(req.request) { | ||
| 71 | Ok(Request::GetStatus) => { | ||
| 72 | buf[0..6].copy_from_slice(&[Status::Ok as u8, 0x32, 0x00, 0x00, self.state as u8, 0x00]); | ||
| 73 | Some(InResponse::Accepted(buf)) | ||
| 74 | } | ||
| 75 | _ => None | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | /// An implementation of the USB DFU 1.1 runtime protocol | ||
| 81 | /// | ||
| 82 | /// This function will add a DFU interface descriptor to the provided Builder, and register the provided Control as a handler for the USB device. The USB builder can be used as normal once this is complete. | ||
| 83 | /// The handler is responsive to DFU GetStatus and Detach commands. | ||
| 84 | /// | ||
| 85 | /// Once a detach command, followed by a USB reset is received by the host, a magic number will be written into the bootloader state partition to indicate that | ||
| 86 | /// it should expose a DFU device, and a software reset will be issued. | ||
| 87 | /// | ||
| 88 | /// To apply USB DFU updates, the bootloader must be capable of recognizing the DFU magic and exposing a device to handle the full DFU transaction with the host. | ||
| 89 | pub fn usb_dfu<'d, D: Driver<'d>, DFU: NorFlash, STATE: NorFlash>(builder: &mut Builder<'d, D>, handler: &'d mut Control<'d, DFU, STATE>, timeout: Duration) { | ||
| 90 | #[cfg(feature = "defmt")] | ||
| 91 | defmt::info!("Application USB DFU initializing"); | ||
| 92 | let mut func = builder.function(0x00, 0x00, 0x00); | ||
| 93 | let mut iface = func.interface(); | ||
| 94 | let mut alt = iface.alt_setting( | ||
| 95 | USB_CLASS_APPN_SPEC, | ||
| 96 | APPN_SPEC_SUBCLASS_DFU, | ||
| 97 | DFU_PROTOCOL_RT, | ||
| 98 | None, | ||
| 99 | ); | ||
| 100 | let timeout = timeout.as_millis() as u16; | ||
| 101 | alt.descriptor( | ||
| 102 | DESC_DFU_FUNCTIONAL, | ||
| 103 | &[ | ||
| 104 | handler.attrs.bits(), | ||
| 105 | (timeout & 0xff) as u8, | ||
| 106 | ((timeout >> 8) & 0xff) as u8, | ||
| 107 | 0x40, 0x00, // 64B control buffer size for application side | ||
| 108 | 0x10, 0x01, // DFU 1.1 | ||
| 109 | ], | ||
| 110 | ); | ||
| 111 | |||
| 112 | drop(func); | ||
| 113 | builder.handler(handler); | ||
| 114 | } \ No newline at end of file | ||
