Repository: probe-rs/probe-rs
Pull request #3473 (2025-07-17 10:54:28)

Feature gate cmsisdap
Putting cmsisdap behind a feature gate allows us to make hidapi an optional dependency, which makes it much easier (or even possible?) to build for Android. With this change I was able to compile my custom toolchain for Android, run it as root, and flash some devices.

Related discussion: #3274

bugadani: Please just feature-gate the relevant parts of cmsis-dap, v2 doesn't need hidapi.
darkwater: Didn't realize that, on it.

Patch:
diff --git a/Cargo.lock b/Cargo.lock
index 6f08d02f..f947e4f5 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -173,9 +173,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
 
 [[package]]
 name = "async-io"
-version = "2.4.1"
+version = "2.5.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "1237c0ae75a0f3765f58910ff9cdd0a12eeb39ab2f4c7de23262f337f0aacbb3"
+checksum = "19634d6336019ef220f09fd31168ce5c184b295cbf80345437cc36094ef223ca"
 dependencies = [
  "async-lock",
  "cfg-if",
@@ -186,8 +186,7 @@ dependencies = [
  "polling",
  "rustix 1.0.7",
  "slab",
- "tracing",
- "windows-sys 0.59.0",
+ "windows-sys 0.60.2",
 ]
 
 [[package]]
@@ -2976,9 +2975,9 @@ dependencies = [
 
 [[package]]
 name = "postcard-rpc"
-version = "0.11.10"
+version = "0.11.11"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c741980fc5a15ac8301bf3e69385eb0da54fccd69f03301286c3176f0d6e40d7"
+checksum = "2e95207d958aa62466faa3357890047caeb69c4bf50d6f17478cc4ea92374cfc"
 dependencies = [
  "heapless 0.8.0",
  "maitake-sync",
@@ -3835,9 +3834,9 @@ dependencies = [
 
 [[package]]
 name = "serde_json"
-version = "1.0.140"
+version = "1.0.141"
 source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "20068b6e96dc6c9bd23e01df8827e6c7e1f2fddd43c21810382803c136b99373"
+checksum = "30b9eff21ebe718216c6ec64e1d9ac57087aad11efc64e32002bce4a0d4c03d3"
 dependencies = [
  "itoa",
  "memchr",
diff --git a/changelog/changed-cmsisdap-feature.md b/changelog/changed-cmsisdap-feature.md
new file mode 100644
index 00000000..b0037d40
--- /dev/null
+++ b/changelog/changed-cmsisdap-feature.md
@@ -0,0 +1,1 @@
+cmsisdap: V1 is now behind a feature gate `cmsisdap_v1`, making the `hidapi` dependency optional.
diff --git a/changelog/fixed-mimxrt1010-memory-map b/changelog/fixed-mimxrt1010-memory-map
new file mode 100644
index 00000000..c0e78df2
--- /dev/null
+++ b/changelog/fixed-mimxrt1010-memory-map
@@ -0,0 +1,1 @@
+Fixed MIMXRT1010 memory map: Added DTCM region, correct OCRAM region size.
diff --git a/probe-rs/Cargo.toml b/probe-rs/Cargo.toml
index 3a4dcb19..6625faca 100644
--- a/probe-rs/Cargo.toml
+++ b/probe-rs/Cargo.toml
@@ -20,10 +20,11 @@ license.workspace = true
 exclude = ["tests/"]
 
 [features]
-default = ["builtin-targets"]
+default = ["builtin-targets", "cmsisdap_v1"]
 
 # Enable all built in targets.
 builtin-targets = ["dep:bincode", "dep:serde_yaml", "dep:probe-rs-target"]
+cmsisdap_v1 = ["dep:hidapi"]
 
 # Enable helpers for testing
 test = []
@@ -41,7 +42,7 @@ bitfield = "0.19.0"
 bitvec = "1"
 hidapi = { version = "2", default-features = false, features = [
     "linux-native",
-] }
+], optional = true }
 ihex = "3.0"
 itertools = "0.14"
 jep106 = "0.3"
@@ -54,7 +55,7 @@ object = { version = "0.37", default-features = false, features = [
 nusb = "0.1.12"
 futures-lite = { version = "2", default-features = false }
 scroll = "0.13"
-serialport = { version = "4.6.0", default-features = false, features = [
+serialport = { version = "4.7.0", default-features = false, features = [
     "usbportinfo-interface",
 ] }
 tracing = "0.1"
diff --git a/probe-rs/src/probe.rs b/probe-rs/src/probe.rs
index 640e93ed..0a5258ef 100644
--- a/probe-rs/src/probe.rs
+++ b/probe-rs/src/probe.rs
@@ -246,6 +246,7 @@ pub enum ProbeCreationError {
     CouldNotOpen,
 
     /// An HID API occurred.
+    #[cfg(feature = "cmsisdap_v1")]
     HidApi(#[from] hidapi::HidError),
 
     /// A USB error occurred.
diff --git a/probe-rs/src/probe/cmsisdap/commands/mod.rs b/probe-rs/src/probe/cmsisdap/commands/mod.rs
index 248fe8c6..b949587f 100644
--- a/probe-rs/src/probe/cmsisdap/commands/mod.rs
+++ b/probe-rs/src/probe/cmsisdap/commands/mod.rs
@@ -62,6 +62,7 @@ impl ProbeError for CmsisDapError {}
 #[derive(Debug, thiserror::Error, docsplay::Display)]
 pub enum SendError {
     /// Error in the USB HID access.
+    #[cfg(feature = "cmsisdap_v1")]
     HidApi(#[from] hidapi::HidError),
 
     /// Error in the USB access.
@@ -172,6 +173,7 @@ pub enum RequestError {
 pub enum CmsisDapDevice {
     /// CMSIS-DAP v1 over HID.
     /// Stores a HID device handle and maximum HID report size.
+    #[cfg(feature = "cmsisdap_v1")]
     V1 {
         handle: hidapi::HidDevice,
         report_size: usize,
@@ -193,6 +195,7 @@ impl CmsisDapDevice {
     /// Read from the probe into `buf`, returning the number of bytes read on success.
     fn read(&self, buf: &mut [u8]) -> Result<usize, SendError> {
         match self {
+            #[cfg(feature = "cmsisdap_v1")]
             CmsisDapDevice::V1 { handle, .. } => {
                 match handle.read_timeout(buf, USB_TIMEOUT.as_millis() as i32)? {
                     // Timeout is not indicated by error, but by returning 0 read bytes
@@ -209,6 +212,7 @@ impl CmsisDapDevice {
     /// Write `buf` to the probe, returning the number of bytes written on success.
     fn write(&self, buf: &[u8]) -> Result<usize, SendError> {
         match self {
+            #[cfg(feature = "cmsisdap_v1")]
             CmsisDapDevice::V1 { handle, .. } => Ok(handle.write(buf)?),
             CmsisDapDevice::V2 { handle, out_ep, .. } => {
                 // Skip first byte as it's set to 0 for HID transfers
@@ -224,6 +228,7 @@ impl CmsisDapDevice {
         tracing::debug!("Draining probe of any pending data.");
 
         match self {
+            #[cfg(feature = "cmsisdap_v1")]
             CmsisDapDevice::V1 {
                 handle,
                 report_size,
@@ -261,6 +266,7 @@ impl CmsisDapDevice {
     pub(super) fn set_packet_size(&mut self, packet_size: usize) {
         tracing::debug!("Configuring probe to use packet size {}", packet_size);
         match self {
+            #[cfg(feature = "cmsisdap_v1")]
             CmsisDapDevice::V1 { report_size, .. } => {
                 *report_size = packet_size;
             }
@@ -308,6 +314,7 @@ impl CmsisDapDevice {
     /// Check if SWO streaming is supported by this device.
     pub(super) fn swo_streaming_supported(&self) -> bool {
         match self {
+            #[cfg(feature = "cmsisdap_v1")]
             CmsisDapDevice::V1 { .. } => false,
             CmsisDapDevice::V2 { swo_ep, .. } => swo_ep.is_some(),
         }
@@ -320,6 +327,7 @@ impl CmsisDapDevice {
     /// On timeout, returns a zero-length buffer.
     pub(super) fn read_swo_stream(&self, timeout: Duration) -> Result<Vec<u8>, CmsisDapError> {
         match self {
+            #[cfg(feature = "cmsisdap_v1")]
             CmsisDapDevice::V1 { .. } => Err(CmsisDapError::SwoModeNotAvailable),
             CmsisDapDevice::V2 { handle, swo_ep, .. } => match swo_ep {
                 Some((ep, len)) => {
@@ -432,6 +440,7 @@ fn send_command_inner<Req: Request>(
     // on v2 we can truncate to just the required data.
     // Add one byte for HID report ID.
     let buffer_len: usize = match device {
+        #[cfg(feature = "cmsisdap_v1")]
         CmsisDapDevice::V1 { report_size, .. } => *report_size + 1,
         CmsisDapDevice::V2 {
             max_packet_size, ..
@@ -441,12 +450,14 @@ fn send_command_inner<Req: Request>(
 
     // Leave byte 0 as the HID report, and write the command and request to the buffer.
     buffer[1] = Req::COMMAND_ID as u8;
+    #[cfg_attr(not(feature = "cmsisdap_v1"), allow(unused_mut))]
     let mut size = request.to_bytes(&mut buffer[2..])? + 2;
 
     // For HID devices we must write a full report every time,
     // so set the transfer size to the report size, plus one
     // byte for the HID report ID. On v2 devices, we just
     // write the exact required size every time.
+    #[cfg(feature = "cmsisdap_v1")]
     if let CmsisDapDevice::V1 { report_size, .. } = device {
         size = *report_size + 1;
     }
diff --git a/probe-rs/src/probe/cmsisdap/tools.rs b/probe-rs/src/probe/cmsisdap/tools.rs
index 55dc74c2..83357c24 100644
--- a/probe-rs/src/probe/cmsisdap/tools.rs
+++ b/probe-rs/src/probe/cmsisdap/tools.rs
@@ -3,6 +3,7 @@ use crate::probe::{
     BoxedProbeError, DebugProbeInfo, DebugProbeSelector, ProbeCreationError,
     cmsisdap::{CmsisDapFactory, commands::CmsisDapError},
 };
+#[cfg(feature = "cmsisdap_v1")]
 use hidapi::HidApi;
 use nusb::{
     DeviceInfo,
@@ -20,6 +21,7 @@ const USB_CLASS_HID: u8 = 0x03;
 pub fn list_cmsisdap_devices() -> Vec<DebugProbeInfo> {
     tracing::debug!("Searching for CMSIS-DAP probes using nusb");
 
+    #[cfg_attr(not(feature = "cmsisdap_v1"), expect(unused_mut))]
     let mut probes = match nusb::list_devices() {
         Ok(devices) => devices
             .filter_map(|device| get_cmsisdap_info(&device))
@@ -30,11 +32,13 @@ pub fn list_cmsisdap_devices() -> Vec<DebugProbeInfo> {
         }
     };
 
+    #[cfg(feature = "cmsisdap_v1")]
     tracing::debug!(
         "Found {} CMSIS-DAP probes using nusb, searching HID",
         probes.len()
     );
 
+    #[cfg(feature = "cmsisdap_v1")]
     if let Ok(api) = hidapi::HidApi::new() {
         for device in api.device_list() {
             if let Some(info) = get_cmsisdap_hid_info(device) {
@@ -120,6 +124,7 @@ fn get_cmsisdap_info(device: &DeviceInfo) -> Option<DebugProbeInfo> {
 }
 
 /// Checks if a given HID device is a CMSIS-DAP v1 probe, returning Some(DebugProbeInfo) if so.
+#[cfg(feature = "cmsisdap_v1")]
 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("");
@@ -279,6 +284,7 @@ pub fn open_device_from_selector(
     //
     // If nusb cannot be used, we will just use the first HID interface and
     // try to open that.
+    #[cfg_attr(not(feature = "cmsisdap_v1"), expect(unused_assignments))]
     let mut hid_device_info = None;
 
     // Try using nusb to open a v2 device. This might fail if
@@ -305,70 +311,76 @@ pub fn open_device_from_selector(
         tracing::debug!("No devices matched using nusb");
     }
 
-    // If nusb failed or the device didn't support v2, try using hidapi to open in v1 mode.
-    let vid = selector.vendor_id;
-    let pid = selector.product_id;
-    let sn = selector.serial_number.as_deref();
+    #[cfg(not(feature = "cmsisdap_v1"))]
+    return Err(ProbeCreationError::NotFound);
 
-    tracing::debug!(
-        "Attempting to open {:04x}:{:04x} in CMSIS-DAP v1 mode",
-        vid,
-        pid
-    );
-
-    // Attempt to open provided VID/PID/SN with hidapi
+    #[cfg(feature = "cmsisdap_v1")]
+    {
+        // If nusb failed or the device didn't support v2, try using hidapi to open in v1 mode.
+        let vid = selector.vendor_id;
+        let pid = selector.product_id;
+        let sn = selector.serial_number.as_deref();
 
-    let Ok(hid_api) = HidApi::new() else {
-        return Err(ProbeCreationError::NotFound);
-    };
+        tracing::debug!(
+            "Attempting to open {:04x}:{:04x} in CMSIS-DAP v1 mode",
+            vid,
+            pid
+        );
 
-    let mut device_list = hid_api.device_list();
+        // Attempt to open provided VID/PID/SN with hidapi
 
-    // We have to filter manually so that we can check the correct HID interface number.
-    // Using HidApi::open() will return the first device which matches PID and VID,
-    // which is not always what we want.
-    let device_info = device_list
-        .find(|info| {
-            let mut device_match = info.vendor_id() == vid && info.product_id() == pid;
+        let Ok(hid_api) = HidApi::new() else {
+            return Err(ProbeCreationError::NotFound);
+        };
 
-            if let Some(sn) = sn {
-                device_match &= Some(sn) == info.serial_number();
-            }
+        let mut device_list = hid_api.device_list();
 
-            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;
-            }
+        // We have to filter manually so that we can check the correct HID interface number.
+        // Using HidApi::open() will return the first device which matches PID and VID,
+        // which is not always what we want.
+        let device_info = device_list
+            .find(|info| {
+                let mut device_match = info.vendor_id() == vid && info.product_id() == pid;
 
-            device_match
-        })
-        .ok_or(ProbeCreationError::NotFound)?;
+                if let Some(sn) = sn {
+                    device_match &= Some(sn) == info.serial_number();
+                }
 
-    let Ok(device) = device_info.open_device(&hid_api) else {
-        return Err(ProbeCreationError::NotFound);
-    };
+                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;
+                }
 
-    match device.get_product_string() {
-        Ok(Some(s)) if is_cmsis_dap(&s) => {
-            reject_probe_by_version(
-                device_info.vendor_id(),
-                device_info.product_id(),
-                device_info.release_number(),
-            )?;
-            Ok(CmsisDapDevice::V1 {
-                handle: device,
-                // Start with a default 64-byte report size, which is the most
-                // common size for CMSIS-DAPv1 HID devices. We'll request the
-                // actual size to use from the probe later.
-                report_size: 64,
+                device_match
             })
-        }
-        _ => {
-            // Return NotFound if this VID:PID was not a valid CMSIS-DAP probe,
-            // or if it couldn't be opened, so that other probe modules can
-            // attempt to open it instead.
-            Err(ProbeCreationError::NotFound)
+            .ok_or(ProbeCreationError::NotFound)?;
+
+        let Ok(device) = device_info.open_device(&hid_api) else {
+            return Err(ProbeCreationError::NotFound);
+        };
+
+        match device.get_product_string() {
+            Ok(Some(s)) if is_cmsis_dap(&s) => {
+                reject_probe_by_version(
+                    device_info.vendor_id(),
+                    device_info.product_id(),
+                    device_info.release_number(),
+                )?;
+                Ok(CmsisDapDevice::V1 {
+                    handle: device,
+                    // Start with a default 64-byte report size, which is the most
+                    // common size for CMSIS-DAPv1 HID devices. We'll request the
+                    // actual size to use from the probe later.
+                    report_size: 64,
+                })
+            }
+            _ => {
+                // Return NotFound if this VID:PID was not a valid CMSIS-DAP probe,
+                // or if it couldn't be opened, so that other probe modules can
+                // attempt to open it instead.
+                Err(ProbeCreationError::NotFound)
+            }
         }
     }
 }
diff --git a/probe-rs/targets/MIMXRT1010.yaml b/probe-rs/targets/MIMXRT1010.yaml
index fa2104a5..5d7950b3 100644
--- a/probe-rs/targets/MIMXRT1010.yaml
+++ b/probe-rs/targets/MIMXRT1010.yaml
@@ -10,11 +10,18 @@ variants:
     core_access_options: !Arm
       ap: !v1 0
   memory_map:
+  - !Ram
+    name: DTCM
+    range:
+      start: 0x20000000
+      end: 0x20020000
+    cores:
+    - main
   - !Ram
     name: OCRAM
     range:
       start: 0x20200000
-      end: 0x20210000
+      end: 0x20220000
     cores:
     - main
   - !Nvm


Test patch:
diff --git a/.github/workflows/smoketest.yml b/.github/workflows/smoketest.yml
index a9570303..3d276300 100644
--- a/.github/workflows/smoketest.yml
+++ b/.github/workflows/smoketest.yml
@@ -30,7 +30,7 @@ jobs:
         run: rustup target add aarch64-unknown-linux-gnu
 
       - name: Install cross
-        uses: taiki-e/install-action@v2.56.13
+        uses: taiki-e/install-action@v2.56.19
         with:
           tool: cross