use super::CmsisDapDevice;
use crate::probe::{
cmsisdap::CmsisDapFactory, DebugProbeInfo, DebugProbeSelector, ProbeCreationError,
};
use hidapi::HidApi;
use nusb::{
transfer::{Direction, EndpointType},
DeviceInfo,
};
const USB_CLASS_HID: u8 = 0x03;
#[tracing::instrument(skip_all)]
pub fn list_cmsisdap_devices() -> Vec<DebugProbeInfo> {
tracing::debug!("Searching for CMSIS-DAP probes using nusb");
let mut probes = match nusb::list_devices() {
Ok(devices) => devices
.filter_map(|device| get_cmsisdap_info(&device))
.collect(),
Err(e) => {
tracing::warn!("error listing devices with nusb: {:?}", e);
vec![]
}
};
tracing::debug!(
"Found {} CMSIS-DAP probes using nusb, searching HID",
probes.len()
);
if let Ok(api) = hidapi::HidApi::new() {
for device in api.device_list() {
if let Some(info) = get_cmsisdap_hid_info(device) {
if !probes.iter().any(|p| {
p.vendor_id == info.vendor_id
&& p.product_id == info.product_id
&& p.serial_number == info.serial_number
}) {
tracing::trace!("Adding new HID-only probe {:?}", info);
probes.push(info)
} else {
tracing::trace!("Ignoring duplicate {:?}", info);
}
}
}
}
tracing::debug!("Found {} CMSIS-DAP probes total", probes.len());
probes
}
fn get_cmsisdap_info(device: &DeviceInfo) -> Option<DebugProbeInfo> {
let prod_str = device.product_string().unwrap_or("");
let sn_str = device.serial_number();
let cmsis_dap_product = is_cmsis_dap(prod_str) || is_known_cmsis_dap_dev(device);
let mut cmsis_dap_interface = false;
let mut hid_interface = None;
for interface in device.interfaces() {
let Some(interface_desc) = interface.interface_string() else {
tracing::trace!(
"interface {} has no string, skipping",
interface.interface_number()
);
continue;
};
if is_cmsis_dap(interface_desc) {
tracing::trace!(
" Interface {}: {}",
interface.interface_number(),
interface_desc
);
cmsis_dap_interface = true;
if interface.class() == USB_CLASS_HID {
tracing::trace!(" HID interface found");
hid_interface = Some(interface.interface_number());
}
}
}
if cmsis_dap_interface || cmsis_dap_product {
tracing::trace!(
"{}: CMSIS-DAP device with {} interfaces",
prod_str,
device.interfaces().count()
);
if let Some(interface) = hid_interface {
tracing::trace!("Will use interface number {} for CMSIS-DAPv1", interface);
} else {
tracing::trace!("No HID interface for CMSIS-DAP found.")
}
Some(DebugProbeInfo::new(
prod_str.to_string(),
device.vendor_id(),
device.product_id(),
sn_str.map(Into::into),
&CmsisDapFactory,
hid_interface,
))
} else {
None
}
}
fn get_cmsisdap_hid_info(device: &hidapi::DeviceInfo) -> Option<DebugProbeInfo> {
let prod_str = device.product_string().unwrap_or("");
let path = device.path().to_str().unwrap_or("");
if is_cmsis_dap(prod_str) || is_cmsis_dap(path) {
tracing::trace!("CMSIS-DAP device with USB path: {:?}", device.path());
tracing::trace!(" product_string: {:?}", prod_str);
tracing::trace!(
" interface: {}",
device.interface_number()
);
Some(DebugProbeInfo::new(
prod_str.to_owned(),
device.vendor_id(),
device.product_id(),
device.serial_number().map(|s| s.to_owned()),
&CmsisDapFactory,
Some(device.interface_number() as u8),
))
} else {
None
}
}
pub fn open_v2_device(device_info: &DeviceInfo) -> Option<CmsisDapDevice> {
let vid = device_info.vendor_id();
let pid = device_info.product_id();
let device = device_info.open().ok()?;
let c_desc = device.configurations().next()?;
for interface in c_desc.interfaces() {
for i_desc in interface.alt_settings() {
let Some(interface_str) = device_info
.interfaces()
.find(|i| i.interface_number() == interface.interface_number())
.and_then(|i| i.interface_string())
else {
continue;
};
if !is_cmsis_dap(interface_str) {
continue;
}
let n_ep = i_desc.num_endpoints();
if !(2..=3).contains(&n_ep) {
continue;
}
let eps: Vec<_> = i_desc.endpoints().collect();
if eps[0].transfer_type() != EndpointType::Bulk || eps[0].direction() != Direction::Out
{
continue;
}
if eps[1].transfer_type() != EndpointType::Bulk || eps[1].direction() != Direction::In {
continue;
}
let mut swo_ep = None;
if eps.len() > 2
&& eps[2].transfer_type() == EndpointType::Bulk
&& eps[2].direction() == Direction::In
{
swo_ep = Some((eps[2].address(), eps[2].max_packet_size()));
}
match device.claim_interface(interface.interface_number()) {
Ok(handle) => {
tracing::debug!("Opening {:04x}:{:04x} in CMSIS-DAPv2 mode", vid, pid);
return Some(CmsisDapDevice::V2 {
handle,
out_ep: eps[0].address(),
in_ep: eps[1].address(),
swo_ep,
max_packet_size: eps[1].max_packet_size(),
});
}
Err(_) => continue,
}
}
}
tracing::debug!(
"Could not open {:04x}:{:04x} in CMSIS-DAP v2 mode",
vid,
pid
);
None
}
pub fn open_device_from_selector(
selector: &DebugProbeSelector,
) -> Result<CmsisDapDevice, ProbeCreationError> {
tracing::trace!("Attempting to open device matching {}", selector);
let mut hid_device_info = None;
if let Ok(devices) = nusb::list_devices() {
for device in devices {
tracing::trace!("Trying device {:?}", device);
if selector.matches(&device) {
hid_device_info = get_cmsisdap_info(&device);
if hid_device_info.is_some() {
if let Some(device) = open_v2_device(&device) {
return Ok(device);
}
}
}
}
} else {
tracing::debug!("No devices matched using nusb");
}
let vid = selector.vendor_id;
let pid = selector.product_id;
let sn = selector.serial_number.as_deref();
tracing::debug!(
"Attempting to open {:04x}:{:04x} in CMSIS-DAP v1 mode",
vid,
pid
);
let hid_api = HidApi::new()?;
let mut device_list = hid_api.device_list();
let device_info = device_list
.find(|info| {
let mut device_match = info.vendor_id() == vid && info.product_id() == pid;
if let Some(sn) = sn {
device_match &= Some(sn) == info.serial_number();
}
if let Some(hid_interface) =
hid_device_info.as_ref().and_then(|info| info.hid_interface)
{
device_match &= info.interface_number() == hid_interface as i32;
}
device_match
})
.ok_or(ProbeCreationError::NotFound)?;
let device = device_info.open_device(&hid_api)?;
match device.get_product_string() {
Ok(Some(s)) if is_cmsis_dap(&s) => {
Ok(CmsisDapDevice::V1 {
handle: device,
report_size: 64,
})
}
_ => {
Err(ProbeCreationError::NotFound)
}
}
}
fn is_cmsis_dap(id: &str) -> bool {
id.contains("CMSIS-DAP") || id.contains("CMSIS_DAP")
}
fn is_known_cmsis_dap_dev(device: &DeviceInfo) -> bool {
const KNOWN_DAPS: &[(u16, u16)] = &[(0x1a86, 0x8012)];
KNOWN_DAPS
.iter()
.any(|&(vid, pid)| device.vendor_id() == vid && device.product_id() == pid)
}