Skip to content

probe-rs (fork): replace hidapi with nusb for CMSIS-DAP v1 HID transport — the actual fix for touchless LPC-Link2 deploy #936

Description

@zackees

Goal (satisfaction criteria)

Touchless SWD flash of the physically-attached NXP LPC845-BRK (VID:PID 1FC9:0132, LPC-Link2 v1.0.7 firmware) works end-to-end via a locally-built probe-rs from the FastLED fork, using the STOCK factory debugger firmware — no button press, no reflash. Acceptance:

~/dev/probe-rs/target/x86_64-pc-windows-msvc/release/probe-rs.exe download \
    --chip LPC845M301JBD48 --probe 1fc9:0132 \
    ~/dev/fbuild/tests/platform/lpc845brk/.fbuild/build/release/firmware.elf

exits 0, reads back correct flash contents, and the board runs the flashed sketch after probe-rs reset --chip LPC845M301JBD48 --probe 1fc9:0132.

Related: #935 is the hardware-validation umbrella and closes automatically when this issue closes with the LPC845-BRK deploying touchless.


Background — everything an agent needs to know before starting

Root cause (finally correctly diagnosed). The v1.0.7 firmware on the LPC-Link2 debug chip is a compiled build of open-source ARMmbed/DAPLink + ARM's CMSIS-5 reference DAP firmware. Reading source/daplink/cmsis-dap/DAP.c in DAPLink confirms:

  • DAP_Info(PacketSize=0xFF) handler EXISTS unconditionally, returns hard-coded 64 bytes (DAP_PACKET_SIZE in source/hic_hal/nxp/lpc11u35/DAP_config.h).
  • DAP_Info(Capabilities=0xF0) handler EXISTS unconditionally, returns 0x01 (SWD-only).
  • DAP_Connect handler EXISTS unconditionally.

The firmware is NOT silently dropping DAP commands. That was our wrong hypothesis for months. The commands never reach the chip because probe-rs is talking to the wrong USB endpoint (or failing to open one at all) on Windows via hidapi.

What MCUXpresso does differently — it uses direct WinUSB, not hidapi. No proprietary protocol, no secret commands, no reverse-engineering needed — just a different USB stack on the host side that avoids Windows hidapi's composite-HID interface-picker bug.

Prior work that already landed (do NOT re-do these):

  1. FastLED/probe-rs PR Esp32-s3 PSRAM use #1 (merged): DAP_Info sub-command fallbacks (PacketSize / PacketCount / Capabilities) with spec defaults on timeout. Preserved — improves resilience against slow probes.
  2. FastLED/probe-rs PR cdc on boot warning #2 (merged): DAP_Connect explicit-SWD + 4× retry. Preserved.
  3. Cross-compile CI on the tools branch of FastLED/framework-arduino-lpc8xx (six targets, cached by source-tree SHA256). Runs on tag push fastled-v* and publishes GitHub Releases.
  4. Local interface-picker patch (already committed at ~/dev/probe-rs/probe-rs/src/probe/cmsisdap/tools.rs): rejects interface_number > 0 for VID 0x1fc9 PIDs 0x0090/0x0132, mirrors OpenOCD cmsis_dap_usb_hid.c:107-109. Compiles + passes the filter step, but hid_api.open_device() itself still returns NotFound on Windows for this composite HID.

Why the current patches don't finish the job. hidapi on Windows cannot reliably open the CMSIS-DAP HID interface of NXP's composite device — the interface-picker patch got us past the "which candidate to select" step, but the subsequent HidDevice::open_path() call fails at the Windows HID.dll level. Symptom: Failed to open probe → NotFound.


The fix — replace hidapi with nusb for CMSIS-DAP v1 HID transport

probe-rs already uses nusb for the CMSIS-DAP v2 (bulk) transport (probe/cmsisdap/tools.rs::open_v2_device). Extend the same pattern to v1: talk to interrupt endpoints on interface 0 directly through nusb, skip hidapi entirely on all platforms.

CMSIS-DAP v1 over HID is just interrupt IN + OUT endpoints. Nothing hidapi does is load-bearing — it's a thin wrapper over what nusb already exposes as Interface::interrupt_in_queue / interrupt_out_queue.

Concrete file map

File Change
probe-rs/src/probe/cmsisdap/tools.rs Add open_v1_nusb_device() mirroring open_v2_device. Drop hidapi fallback for supported paths. Keep hidapi behind a feature flag / graceful-degrade for niche cases.
probe-rs/src/probe/cmsisdap/commands/mod.rs Add a CmsisDapDevice::V1Nusb { handle, ep_in, ep_out, packet_size } variant (or teach the existing V1 variant to hold a nusb Interface + endpoint numbers instead of a HidDevice). Route send_command / receive_response through nusb interrupt_in_queue.submit / next_complete and interrupt_out_queue.submit / next_complete.
probe-rs/Cargo.toml Move hidapi to an optional dep (retain for Linux hidraw-only edge cases if any). Confirm nusb version already vendored covers all six cross-compile targets.

Reference implementation to mirror

Read open_v2_device in probe-rs/src/probe/cmsisdap/tools.rs — it opens a nusb interface, claims it, and hands endpoint numbers to a V2 variant of CmsisDapDevice. Do the same for v1 but with interrupt endpoints instead of bulk.

For CMSIS-DAP v1 HID framing: on the wire it's just packets, no HID report ID prefix (see DAPLink's usbd_LPC11Uxx.c). But hidapi historically requires a leading 0x00 "no report ID" byte on Windows writes — that byte is a hidapi convention, NOT something the firmware expects. When you cut over to nusb, drop the leading 0x00 padding; write DAP command bytes directly starting with the command ID.

Pitfalls learned the hard way

  • HID Report ID. DAPLink's LPC11U35 HID descriptor has no Report ID (single unnamed report). hidapi's leading 0x00 on Windows is spurious for nusb — do NOT prepend it.
  • Interface number. Interface 0 is the CMSIS-DAP HID. Interface 1 is the CDC VCOM (UART bridge). Interface 2 is CDC data. Do not claim interfaces 1/2 — they're already claimed by usbccgp / VCOM driver.
  • Windows WinUSB driver binding. nusb on Windows uses WinUSB via the composite driver stack. The LPC-Link2's interface 0 (HID class) currently binds to HidUsb, which may prevent nusb from claiming it. You may need to detach HidUsb first (WinUSB substitution via libwdi or a device-installer INF). Investigate first — if this is a blocker, document exactly what INF the user has to install once, and stage that install into ~/.fbuild/tools/ (parallels the dfu-util scaffolding in crates/fbuild-deploy/src/lpc_debugger_reflash.rs).
  • Endpoint packet size. DAPLink's LPC11U35 config hard-codes DAP_PACKET_SIZE = 64. Read this via DAP_Info(0xFF) after connect (already handled by PR Esp32-s3 PSRAM use #1's fallback); don't assume.
  • The v1.0.7 firmware doesn't populate per-interface iStrings — only the top-level product_string is "LPC11U3x CMSIS-DAP v1.0.7". Don't filter interfaces by iString presence.

Hardware acceptance test

Physically-attached LPC845-BRK on the maintainer's Windows machine (COM10, VID:PID 1FC9:0132, serial 0B03400A). Test project already exists at tests/platform/lpc845brk/ with a compiled firmware.elf.

Verification steps:

  1. probe-rs list shows the LPC-Link2 (already works today).
  2. probe-rs info --chip LPC845M301JBD48 --probe 1fc9:0132 returns IDCODE 0x0BC11477 (Cortex-M0+ ROM table) and does NOT error at "USB device could not be opened."
  3. probe-rs download … firmware.elf writes flash and exits 0.
  4. probe-rs reset --chip … --probe … puts the target into the flashed sketch.
  5. All FOUR of the above complete without touching SW3 (ISP) or SW4 (RESET) on the board.

Definition of done

  • probe-rs download flashes firmware.elf to the LPC845-BRK on the maintainer's Windows box, touchless, from the FastLED-fork build.
  • Same test also passes on Linux x86_64 (via WSL2 usbipd or a native Linux box), to confirm the nusb path is cross-platform.
  • A cross-compiled Windows binary published via the tools-branch fastled-release-cross.yml GitHub Action reproduces the hardware test locally on the maintainer's box (no local soldr cargo build required).
  • fbuild's LpcDeployer (crates/fbuild-deploy/src/lpc.rs) gains an optional dispatch to probe-rs SWD when the LPC-Link2 is present, falling back to lpc21isp UART ISP when it isn't. Guarded behind a --via probe-rs flag until stable.
  • probe-rs LPC-Link2 v1.0.7: push through all blockers end-to-end, single-shot #935 closes with a link to this issue's fixing PR.
  • Upstream probe-rs/probe-rs PR opened (mirrors the fork PR body — draft under the same title).

Prior-session reference material — links to preserve


Note on Option D (reflash) as a fallback

If this Option B path hits a blocker (WinUSB driver-binding, endpoint discovery, etc.), the reflash-to-DAPLink escape hatch is scaffolded already at crates/fbuild-deploy/src/lpc_debugger_reflash.rs. Don't confuse the two paths — this issue is Option B (touchless with STOCK firmware). Option D lives in its own follow-up.

Also: the DFU jumper on the LPC845-BRK is P5, not JP1 — earlier notes had the wrong pin. Only relevant if this path has to fall back.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions