Skip to content

Support HID-only probes with no USB string descriptors (pico2-debug)#1988

Open
ajsb85 wants to merge 2 commits into
pyocd:mainfrom
ajsb85:fix/pico2-debug-no-langid-hid-support
Open

Support HID-only probes with no USB string descriptors (pico2-debug)#1988
ajsb85 wants to merge 2 commits into
pyocd:mainfrom
ajsb85:fix/pico2-debug-no-langid-hid-support

Conversation

@ajsb85

@ajsb85 ajsb85 commented Jun 23, 2026

Copy link
Copy Markdown

Summary

Fixes four issues that together prevented pico2-debug (VID:PID 1209:2488, Dapper Miser CMSIS-DAP) from working with pyocd on Linux. pico2-debug is a popular software CMSIS-DAP probe for RP2040/RP2350 that deliberately exposes no USB string descriptors to save RAM.

Changes

1. interface/common.py — Add pico2-debug to known VID:PID list

(0x1209, 0x2488) was not in KNOWN_CMSIS_DAP_IDS. Without this, the probe was silently skipped during enumeration whenever the product string was empty (which it always is for pico2-debug).

2. interface/pyusb_backend.py — Wrap all USB string descriptor reads in try/except

During get_all_connected_interfaces() and FindDap.__call__(), reads of dev.product, dev.manufacturer, and dev.serial_number are sent as USB GET_DESCRIPTOR (String) control transfers. Devices with no LangID table respond with a stall, raising ValueError: The device has no langid. These three reads are now wrapped in try/except Exception so the probe is still discovered and gets a synthesised unique ID from its bus location.

3. interface/__init__.py — Default Linux backend to hidapiusb

Linux was defaulting to the pyusb backend. pyusb sends SET_CONFIGURATION and other control transfers during enumeration that crash HID-only firmware like pico2-debug, leaving the device in an unresponsive 0000:0002 Unknown USB Device state. Switch Linux default to hidapiusb when available (matching the macOS behaviour); fall back to pyusb only when hidapi is absent.

4. dap_access_cmsis_dap.py — Close interface on setup failure to fix "already open"

Session.__init__() calls probe.create_associated_board()associated_board_info_TemporaryOpen. If _interface.open() (HID open_path) succeeds but the subsequent CMSIS-DAP identify() call fails (timeout or protocol error), _is_open is never set to True. _TemporaryOpen.__exit__ skips close() because _did_open_link was never set. The HID device is left open at the OS level. The real session.open() then calls open_path() again and gets RuntimeError: already open. Wrapping the post-open CMSIS-DAP setup in try/except and calling _interface.close() before re-raising fixes the double-open.

Test plan

  • Tested with pico2-debug v11.00 on Seeed Studio XIAO RP2350 (RP2350 A2 silicon) via WSL2 on Windows using usbipd-win
  • pyocd list correctly finds and shows 1209:2488 probe with synthesised unique ID
  • pyocd flash --target rp2350 firmware.elf successfully programs flash through the probe
  • pyocd cmd --target rp2350 -c "halt; reg; read32 0x40000000; go" works correctly
  • No regressions on standard DAPLink probes (string descriptors present, pyusb path still available via PYOCD_USB_BACKEND=pyusb)

Related

🤖 Generated with Claude Code

…bug)

Four issues prevented pico2-debug (VID:PID 1209:2488, Dapper Miser CMSIS-DAP)
from being usable on Linux:

1. 1209:2488 not in KNOWN_CMSIS_DAP_IDS — probe was silently skipped even when
   the HID interface was clearly CMSIS-DAP capable.

2. pyusb backend reads USB string descriptors (product, manufacturer, serial)
   during enumeration via control transfers. Devices that have no string
   descriptor support (no LangID table) raise ValueError or USBError on
   these reads, crashing the scan. Wrap all three reads in try/except so the
   probe is still discovered and gets a synthesised unique ID from its bus
   location.

3. Linux backend defaulted to pyusb. pyusb sends SET_CONFIGURATION and other
   control transfers that crash HID-only firmware. Switch Linux default to
   hidapiusb when available; fall back to pyusb only when hidapi is absent.

4. DAPAccessCMSISDAP.open() left the HID interface open on failure. If setup
   fails after _interface.open() (e.g. CMSIS-DAP identify() times out because
   the probe is not yet ready), _is_open stays False. A subsequent open()
   attempt then calls open_path() on an already-open hid.device, raising
   RuntimeError: already open. Wrap the post-open setup block in try/except
   and call _interface.close() before re-raising so the caller can retry.

Tested with pico2-debug v11.00 on a Seeed Studio XIAO RP2350 (RP2350 A2)
connected via WSL2 on Windows using usbipd-win. After these fixes, pyocd
list, pyocd flash, and pyocd cmd all work correctly with pico2-debug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 23, 2026 13:39

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

@ajsb85 ajsb85 changed the title fix: support HID-only probes with no USB string descriptors (pico2-debug) Support HID-only probes with no USB string descriptors (pico2-debug) Jun 23, 2026
get_all_connected_interfaces() was calling hid.device(vid, pid, path)
which auto-opens the device during enumeration.  When _TemporaryOpen
later called open() → open_path(), it created a second file descriptor.
After _TemporaryOpen.__exit__ closed one FD, the subsequent session.open()
call tried open_path() on the closed hid.device object — but after close()
the underlying hid_device* pointer is freed, so the call fails with
OSError: open failed.

Fix: create an unopened hid.device() during enumeration, and always
assign a fresh hid.device() at the start of open() so each open/close
cycle starts from a clean object state.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants