Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
120 changes: 63 additions & 57 deletions pyocd/probe/pydapaccess/dap_access_cmsis_dap.py
Original file line number Diff line number Diff line change
Expand Up @@ -751,67 +751,73 @@ def open(self):

self._interface.open()

# If this probe has already been opened and examined previously, we don't need to examine it again.
if self._has_opened_once:
self._init_deferred_buffers()
if self._has_swo_uart:
self._swo_disable()
self._swo_status = SWOStatus.DISABLED
self._is_open = True
return
# Ensure the interface is closed if any subsequent setup step fails, so that
# re-open attempts don't hit "already open" from the HID layer.
try:
# If this probe has already been opened and examined previously, we don't need to examine it again.
if self._has_opened_once:
self._init_deferred_buffers()
if self._has_swo_uart:
self._swo_disable()
self._swo_status = SWOStatus.DISABLED
self._is_open = True
return

if session.Session.get_current().options['cmsis_dap.limit_packets'] or DAPSettings.limit_packets:
self._packet_count = 1
LOG.debug("Limiting packet count to %d", self._packet_count)
else:
self._packet_count = self.identify(self.ID.MAX_PACKET_COUNT)
assert isinstance(self._packet_count, int)

# Get the protocol version.
self._read_protocol_version()

# Read the firmware version if the protocol supports it.
# THe PRODUCT_FW_VERSION ID was added in versions 1.3.0 (HID) and 2.1.0 (bulk).
if (self._cmsis_dap_version >= CMSISDAPVersion.V2_1_0) or (self._cmsis_dap_version >= CMSISDAPVersion.V1_3_0
and self._cmsis_dap_version < CMSISDAPVersion.V2_0_0):
fw_version_value = self.identify(self.ID.PRODUCT_FW_VERSION)
assert isinstance(fw_version_value, (str, NoneType))
self._fw_version = fw_version_value

# Major protocol version based on use of bulk endpoints.
proto_major = (2 if self._interface.is_bulk else 1)

# Log probe's firmware version.
if self._fw_version:
LOG.debug("CMSIS-DAP v%d probe %s: firmware version %s, protocol version %i.%i.%i",
proto_major, self._unique_id, self._fw_version, *self._cmsis_dap_version)
else:
LOG.debug("CMSIS-DAP v%d probe %s: protocol version %i.%i.%i",
proto_major, self._unique_id, *self._cmsis_dap_version)

self._interface.set_packet_count(self._packet_count)
self._packet_size = self.identify(self.ID.MAX_PACKET_SIZE)
assert isinstance(self._packet_size, int)
self._interface.set_packet_size(self._packet_size)
self._capabilities = self.identify(self.ID.CAPABILITIES)
assert isinstance(self._capabilities, int)
self._has_swo_uart = (self._capabilities & Capabilities.SWO_UART) != 0
if self._has_swo_uart:
swo_buffer_size_value = self.identify(self.ID.SWO_BUFFER_SIZE)
if isinstance(swo_buffer_size_value, int) and swo_buffer_size_value > 0:
self._swo_buffer_size = swo_buffer_size_value
if session.Session.get_current().options['cmsis_dap.limit_packets'] or DAPSettings.limit_packets:
self._packet_count = 1
LOG.debug("Limiting packet count to %d", self._packet_count)
else:
LOG.debug("CMSIS-DAP probe %s reported invalid SWO_BUFFER_SIZE (%d)",
self._unique_id, swo_buffer_size_value)
self._has_swo_uart = False
else:
self._swo_buffer_size = 0
self._swo_status = SWOStatus.DISABLED
self._packet_count = self.identify(self.ID.MAX_PACKET_COUNT)
assert isinstance(self._packet_count, int)

# Get the protocol version.
self._read_protocol_version()

# Read the firmware version if the protocol supports it.
# THe PRODUCT_FW_VERSION ID was added in versions 1.3.0 (HID) and 2.1.0 (bulk).
if (self._cmsis_dap_version >= CMSISDAPVersion.V2_1_0) or (self._cmsis_dap_version >= CMSISDAPVersion.V1_3_0
and self._cmsis_dap_version < CMSISDAPVersion.V2_0_0):
fw_version_value = self.identify(self.ID.PRODUCT_FW_VERSION)
assert isinstance(fw_version_value, (str, NoneType))
self._fw_version = fw_version_value

# Major protocol version based on use of bulk endpoints.
proto_major = (2 if self._interface.is_bulk else 1)

# Log probe's firmware version.
if self._fw_version:
LOG.debug("CMSIS-DAP v%d probe %s: firmware version %s, protocol version %i.%i.%i",
proto_major, self._unique_id, self._fw_version, *self._cmsis_dap_version)
else:
LOG.debug("CMSIS-DAP v%d probe %s: protocol version %i.%i.%i",
proto_major, self._unique_id, *self._cmsis_dap_version)

self._interface.set_packet_count(self._packet_count)
self._packet_size = self.identify(self.ID.MAX_PACKET_SIZE)
assert isinstance(self._packet_size, int)
self._interface.set_packet_size(self._packet_size)
self._capabilities = self.identify(self.ID.CAPABILITIES)
assert isinstance(self._capabilities, int)
self._has_swo_uart = (self._capabilities & Capabilities.SWO_UART) != 0
if self._has_swo_uart:
swo_buffer_size_value = self.identify(self.ID.SWO_BUFFER_SIZE)
if isinstance(swo_buffer_size_value, int) and swo_buffer_size_value > 0:
self._swo_buffer_size = swo_buffer_size_value
else:
LOG.debug("CMSIS-DAP probe %s reported invalid SWO_BUFFER_SIZE (%d)",
self._unique_id, swo_buffer_size_value)
self._has_swo_uart = False
else:
self._swo_buffer_size = 0
self._swo_status = SWOStatus.DISABLED

self._init_deferred_buffers()
self._init_deferred_buffers()

self._has_opened_once = True
self._is_open = True
self._has_opened_once = True
self._is_open = True
except Exception:
self._interface.close()
raise

@locked
def close(self):
Expand Down
5 changes: 3 additions & 2 deletions pyocd/probe/pydapaccess/interface/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,10 @@
# Default to hidapi for OS X.
elif system == "Darwin":
USB_BACKEND = "hidapiusb"
# Default to pyUSB for Linux.
# Prefer hidapi on Linux — pyusb sends control transfers that crash HID-only probes
# (e.g. pico2-debug) which have no string descriptors / langid support.
elif system == "Linux":
USB_BACKEND = "pyusb"
USB_BACKEND = "hidapiusb" if HidApiUSB.isAvailable else "pyusb"
elif "BSD" in system:
USB_BACKEND = "pyusb"
else:
Expand Down
1 change: 1 addition & 0 deletions pyocd/probe/pydapaccess/interface/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@
NXP_MCULINK_ID,
(0x1a86, 0x8011), # WCH-Link
(0x2a86, 0x8011), # WCH-Link clone
(0x1209, 0x2488), # pico2-debug / Dapper Miser CMSIS-DAP (RP2350 software debug probe)
]

## List of substrings to look for in product and interface name strings.
Expand Down
15 changes: 10 additions & 5 deletions pyocd/probe/pydapaccess/interface/hidapi_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,11 @@ def _update_report_sizes(self) -> bool:


def open(self):
# Always use a fresh hid.device() object. The device opened during
# enumeration (hid.device(vid, pid, path) auto-opens) or a previously
# closed object cannot be safely re-opened via open_path() — the
# underlying hid_device* pointer is freed after close().
self.device = hid.device()
try:
self.device.open_path(self.device_info['path'])
except IOError as exc:
Expand Down Expand Up @@ -294,11 +299,11 @@ def get_all_connected_interfaces():
if filter_device_by_usage_page(vid, pid, deviceInfo['usage_page']):
continue

try:
dev = hid.device(vendor_id=vid, product_id=pid, path=deviceInfo['path'])
except IOError as exc:
LOG.debug("Failed to open USB device: %s", exc)
continue
# Create an unopened device object. open() will call open_path()
# when the probe is actually used — don't open here because the
# auto-open constructor leaks a file descriptor that open() would
# then double-open, breaking the close→reopen lifecycle.
dev = hid.device()

# Create the USB interface object for this device.
new_board = HidApiUSB(dev, deviceInfo)
Expand Down
30 changes: 24 additions & 6 deletions pyocd/probe/pydapaccess/interface/pyusb_backend.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,19 @@ def __init__(self, dev):
super().__init__()
self.vid = dev.idVendor
self.pid = dev.idProduct
self.product_name = dev.product or f"{dev.idProduct:#06x}"
self.vendor_name = dev.manufacturer or f"{dev.idVendor:#06x}"
self.serial_number = dev.serial_number \
try:
self.product_name = dev.product or f"{dev.idProduct:#06x}"
except Exception:
self.product_name = f"{dev.idProduct:#06x}"
try:
self.vendor_name = dev.manufacturer or f"{dev.idVendor:#06x}"
except Exception:
self.vendor_name = f"{dev.idVendor:#06x}"
try:
_serial = dev.serial_number
except Exception:
_serial = None
self.serial_number = _serial \
or generate_device_unique_id(dev.idProduct, dev.idVendor, dev.bus, dev.address)
self.ep_out = None
self.ep_in = None
Expand Down Expand Up @@ -358,7 +368,11 @@ def __call__(self, dev):
config = dev.get_active_configuration()

# Now read the product name string.
device_string = dev.product
# Some devices (e.g. pico2-debug) expose no string descriptors; treat as empty.
try:
device_string = dev.product
except Exception:
device_string = None
if ((device_string is None) or (not is_known_device_string(device_string))) and (not known_cmsis_dap):
return False

Expand Down Expand Up @@ -391,11 +405,15 @@ def __call__(self, dev):
if cmsis_dap_interface is None:
return False
if self._serial is not None:
if dev.serial_number is None:
try:
dev_serial = dev.serial_number
except Exception:
dev_serial = None
if dev_serial is None:
if self._serial == "":
return True
if self._serial == generate_device_unique_id(dev.idProduct, dev.idVendor, dev.bus, dev.address):
return True
if self._serial != dev.serial_number:
if self._serial != dev_serial:
return False
return True