From e410e65b830f5486a9d15b87039817aa4668e06f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 17 Apr 2025 18:05:03 +0200 Subject: [PATCH 1/2] Add CMSIS-DAP driver --- embassy-usb/src/class/cmsis_dap_v2.rs | 101 ++++++++++++++++++++++++++ embassy-usb/src/class/mod.rs | 1 + 2 files changed, 102 insertions(+) create mode 100644 embassy-usb/src/class/cmsis_dap_v2.rs diff --git a/embassy-usb/src/class/cmsis_dap_v2.rs b/embassy-usb/src/class/cmsis_dap_v2.rs new file mode 100644 index 000000000..41f6be5dd --- /dev/null +++ b/embassy-usb/src/class/cmsis_dap_v2.rs @@ -0,0 +1,101 @@ +//! CMSIS-DAP V2 class implementation. + +use core::mem::MaybeUninit; + +use crate::driver::{Driver, Endpoint, EndpointError, EndpointIn, EndpointOut}; +use crate::types::StringIndex; +use crate::{msos, Builder, Handler}; + +/// State for the CMSIS-DAP v2 USB class. +pub struct State { + control: MaybeUninit, +} + +struct Control { + iface_string: StringIndex, +} + +impl Handler for Control { + fn get_string(&mut self, index: StringIndex, _lang_id: u16) -> Option<&str> { + if index == self.iface_string { + Some("CMSIS-DAP v2 Interface") + } else { + warn!("unknown string index requested"); + None + } + } +} + +impl State { + /// Create a new `State`. + pub const fn new() -> Self { + Self { + control: MaybeUninit::uninit(), + } + } +} + +/// USB device class for CMSIS-DAP v2 probes. +pub struct CmsisDapV2Class<'d, D: Driver<'d>> { + read_ep: D::EndpointOut, + write_ep: D::EndpointIn, + max_packet_size: u16, +} + +impl<'d, D: Driver<'d>> CmsisDapV2Class<'d, D> { + /// Creates a new CmsisDapV2Class with the provided UsbBus and `max_packet_size` in bytes. For + /// full-speed devices, `max_packet_size` has to be 64. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State, max_packet_size: u16) -> Self { + // DAP - Custom Class 0 + let iface_string = builder.string(); + let mut function = builder.function(0xFF, 0, 0); + function.msos_feature(msos::CompatibleIdFeatureDescriptor::new("WINUSB", "")); + function.msos_feature(msos::RegistryPropertyFeatureDescriptor::new( + "DeviceInterfaceGUIDs", + // CMSIS-DAP standard GUID, from https://arm-software.github.io/CMSIS_5/DAP/html/group__DAP__ConfigUSB__gr.html + msos::PropertyData::RegMultiSz(&["{CDB3B5AD-293B-4663-AA36-1AAE46463776}"]), + )); + let mut interface = function.interface(); + let mut alt = interface.alt_setting(0xFF, 0, 0, Some(iface_string)); + let read_ep = alt.endpoint_bulk_out(max_packet_size); + let write_ep = alt.endpoint_bulk_in(max_packet_size); + drop(function); + + builder.handler(state.control.write(Control { iface_string })); + + CmsisDapV2Class { + read_ep, + write_ep, + max_packet_size, + } + } + + /// Waits for the USB host to enable this interface + pub async fn wait_connection(&mut self) { + self.read_ep.wait_enabled().await; + } + + /// Write data to the host. + pub async fn write_packet(&mut self, data: &[u8]) -> Result<(), EndpointError> { + for chunk in data.chunks(self.max_packet_size as usize) { + self.write_ep.write(chunk).await?; + } + if data.len() % self.max_packet_size as usize == 0 { + self.write_ep.write(&[]).await?; + } + Ok(()) + } + + /// Read data from the host. + pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { + let mut n = 0; + + loop { + let i = self.read_ep.read(&mut data[n..]).await?; + n += i; + if i < self.max_packet_size as usize { + return Ok(n); + } + } + } +} diff --git a/embassy-usb/src/class/mod.rs b/embassy-usb/src/class/mod.rs index 4bd89eb66..c01707971 100644 --- a/embassy-usb/src/class/mod.rs +++ b/embassy-usb/src/class/mod.rs @@ -1,6 +1,7 @@ //! Implementations of well-known USB classes. pub mod cdc_acm; pub mod cdc_ncm; +pub mod cmsis_dap_v2; pub mod hid; pub mod midi; pub mod uac1; From b0eacf0eecbef48ac526da6bce250189e4999ce5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1niel=20Buga?= Date: Thu, 17 Apr 2025 22:29:15 +0200 Subject: [PATCH 2/2] Add optional trace endpoint --- embassy-usb/src/class/cmsis_dap_v2.rs | 29 ++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-) diff --git a/embassy-usb/src/class/cmsis_dap_v2.rs b/embassy-usb/src/class/cmsis_dap_v2.rs index 41f6be5dd..a94e3ddb7 100644 --- a/embassy-usb/src/class/cmsis_dap_v2.rs +++ b/embassy-usb/src/class/cmsis_dap_v2.rs @@ -39,13 +39,17 @@ impl State { pub struct CmsisDapV2Class<'d, D: Driver<'d>> { read_ep: D::EndpointOut, write_ep: D::EndpointIn, + trace_ep: Option, max_packet_size: u16, } impl<'d, D: Driver<'d>> CmsisDapV2Class<'d, D> { /// Creates a new CmsisDapV2Class with the provided UsbBus and `max_packet_size` in bytes. For /// full-speed devices, `max_packet_size` has to be 64. - pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State, max_packet_size: u16) -> Self { + /// + /// The `trace` parameter enables the trace output endpoint. This is optional and can be + /// disabled if the probe does not support trace output. + pub fn new(builder: &mut Builder<'d, D>, state: &'d mut State, max_packet_size: u16, trace: bool) -> Self { // DAP - Custom Class 0 let iface_string = builder.string(); let mut function = builder.function(0xFF, 0, 0); @@ -59,6 +63,11 @@ impl<'d, D: Driver<'d>> CmsisDapV2Class<'d, D> { let mut alt = interface.alt_setting(0xFF, 0, 0, Some(iface_string)); let read_ep = alt.endpoint_bulk_out(max_packet_size); let write_ep = alt.endpoint_bulk_in(max_packet_size); + let trace_ep = if trace { + Some(alt.endpoint_bulk_in(max_packet_size)) + } else { + None + }; drop(function); builder.handler(state.control.write(Control { iface_string })); @@ -66,6 +75,7 @@ impl<'d, D: Driver<'d>> CmsisDapV2Class<'d, D> { CmsisDapV2Class { read_ep, write_ep, + trace_ep, max_packet_size, } } @@ -86,6 +96,23 @@ impl<'d, D: Driver<'d>> CmsisDapV2Class<'d, D> { Ok(()) } + /// Write data to the host via the trace output endpoint. + /// + /// Returns `EndpointError::Disabled` if the trace output endpoint is not enabled. + pub async fn write_trace(&mut self, data: &[u8]) -> Result<(), EndpointError> { + let Some(ep) = self.trace_ep.as_mut() else { + return Err(EndpointError::Disabled); + }; + + for chunk in data.chunks(self.max_packet_size as usize) { + ep.write(chunk).await?; + } + if data.len() % self.max_packet_size as usize == 0 { + ep.write(&[]).await?; + } + Ok(()) + } + /// Read data from the host. pub async fn read_packet(&mut self, data: &mut [u8]) -> Result { let mut n = 0;