Skip to content

Commit

Permalink
Merge pull request #997 from flit/bugfix/jlink_open
Browse files Browse the repository at this point in the history
Fix issue when another process has a J-Link open
  • Loading branch information
flit committed Nov 8, 2020
1 parent 799885f commit 6070a62
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 24 deletions.
11 changes: 10 additions & 1 deletion pyocd/commands/commander.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ class PyOCDCommander(object):
Responsible for connecting the execution context, REPL, and commands, and handles connection.
Exit codes:
- 0 = no errors
- 1 = command error
- 2 = transfer error
- 3 = failed to create session (probe might not exist)
- 4 = failed to open probe
@todo Replace use of args from argparse with something cleaner.
"""

Expand Down Expand Up @@ -169,7 +176,9 @@ def connect(self):
self.exit_code = 3
return False

self._post_connect()
if not self._post_connect():
self.exit_code = 4
return False

result = self.context.attach_session(self.session)
if not result:
Expand Down
9 changes: 9 additions & 0 deletions pyocd/probe/aggregator.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,15 @@ def get_all_connected_probes(unique_id=None):
klasses, unique_id, is_explicit = DebugProbeAggregator._get_probe_classes(unique_id)

probes = []

# First look for a match against the full ID, as this can be more efficient for certain probes.
if unique_id is not None:
for cls in klasses:
probe = cls.get_probe_with_id(unique_id, is_explicit)
if probe is not None:
return [probe]

# No full match, so ask probe classes for probes.
for cls in klasses:
probes += cls.get_all_connected_probes(unique_id, is_explicit)

Expand Down
17 changes: 10 additions & 7 deletions pyocd/probe/debug_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,10 +63,14 @@ class Capability(Enum):
def get_all_connected_probes(cls, unique_id=None, is_explicit=False):
"""! @brief Returns a list of DebugProbe instances.
To filter the list of returned probes, the `unique_id` parameter may be set to a string with a full or
partial unique ID (canonically the serial number). Alternatively, the probe class may simply return all
available probes and let the caller handle filtering.
@param cls The class instance.
@param unique_id Optional unique ID value on which probes are being filtered. May be
used by the probe to optimize retreiving the probe list.
@param is_explicit Whether the probe type was explicitly specified in the unique ID. This
@param unique_id String. Optional partial unique ID value used to filter available probes. May be used by the
probe to optimize retreiving the probe list; there is no requirement to filter the results.
@param is_explicit Boolean. Whether the probe type was explicitly specified in the unique ID. This
can be used, for instance, to specially interpret the unique ID as an IP address or
domain name when the probe class was specifically requested but not for general lists
of available probes.
Expand All @@ -78,12 +82,11 @@ def get_all_connected_probes(cls, unique_id=None, is_explicit=False):
def get_probe_with_id(cls, unique_id, is_explicit=False):
"""! @brief Returns a DebugProbe instance for a probe with the given unique ID.
If no probe is connected with a matching unique ID, then None will be returned.
If no probe is connected with a fully matching unique ID, then None will be returned.
@param cls The class instance.
@param unique_id Optional unique ID value on which probes are being filtered. May be
used by the probe to optimize retreiving the probe list.
@param is_explicit Whether the probe type was explicitly specified in the unique ID.
@param unique_id Unique ID string to match against probes' full unique ID. No partial matches are allowed.
@param is_explicit Boolean. Whether the probe type was explicitly specified in the unique ID.
@return DebugProbe instance, or None
"""
raise NotImplementedError()
Expand Down
55 changes: 39 additions & 16 deletions pyocd/probe/jlink_probe.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,14 +54,18 @@ def _get_jlink(cls):
)
except TypeError:
return None

@classmethod
def _format_serial_number(cls, serial_number):
return "{:d}".format(serial_number)

@classmethod
def get_all_connected_probes(cls, unique_id=None, is_explicit=False):
try:
jlink = cls._get_jlink()
if jlink is None:
return []
return [cls(str(info.SerialNumber)) for info in jlink.connected_emulators()]
return [cls(cls._format_serial_number(info.SerialNumber)) for info in jlink.connected_emulators()]
except JLinkException as exc:
six.raise_from(cls._convert_exception(exc), exc)

Expand All @@ -72,43 +76,60 @@ def get_probe_with_id(cls, unique_id, is_explicit=False):
if jlink is None:
return None
for info in jlink.connected_emulators():
if str(info.SerialNumber) == unique_id:
return cls(str(info.SerialNumber))
sn = cls._format_serial_number(info.SerialNumber)
if sn == unique_id:
return cls(sn)
else:
return None
except JLinkException as exc:
six.raise_from(cls._convert_exception(exc), exc)

@classmethod
def _get_probe_info(cls, serial_number, jlink):
"""! @brief Look up and return a JLinkConnectInfo for the probe with matching serial number.
@param cls The class object.
@param serial_number String serial number. Must be the full serial number.
@return JLinkConnectInfo object or None if there was no match.
"""
try:
for info in jlink.connected_emulators():
if cls._format_serial_number(info.SerialNumber) == serial_number:
return info
else:
return None
except JLinkException as exc:
six.raise_from(cls._convert_exception(exc), exc)

def __init__(self, serial_number):
"""! @brief Constructor.
@param self The object.
@param serial_number String. The J-Link's serial number.
"""
super(JLinkProbe, self).__init__()
self._link = self._get_jlink()
if self._link is None:
raise exceptions.ProbeError("unable to open JLink DLL")

info = self._get_probe_info(serial_number, self._link)
if info is None:
raise exceptions.ProbeError("could not find JLink probe with serial number '{}'".format(serial_number))

self._serial_number = serial_number
self._serial_number_int = int(serial_number, base=10)
self._supported_protocols = None
self._protocol = None
self._default_protocol = None
self._is_open = False
self._dp_select = -1
self._product_name = None
self._oem = None

# Get some strings by temporarily opening.
try:
self.open()
self._product_name = self._link.product_name
self._oem = self._link.oem
finally:
self.close()
self._product_name = six.ensure_str(info.acProduct)

@property
def description(self):
return self.vendor_name + " " + self.product_name

@property
def vendor_name(self):
return self._oem or "Segger"
return "Segger"

@property
def product_name(self):
Expand Down Expand Up @@ -137,7 +158,7 @@ def capabilities(self):

def open(self):
try:
self._link.open(self._serial_number)
self._link.open(self._serial_number_int)
self._is_open = True

# Get available wire protocols.
Expand All @@ -147,7 +168,9 @@ def open(self):
self._supported_protocols.append(DebugProbe.Protocol.JTAG)
if ifaces & (1 << pylink.enums.JLinkInterfaces.SWD):
self._supported_protocols.append(DebugProbe.Protocol.SWD)
assert len(self._supported_protocols) > 1
if not len(self._supported_protocols) >= 2: # default + 1
raise exceptions.ProbeError("J-Link probe {} does not support any known wire protocols".format(
self.unique_id))

# Select default protocol, preferring SWD over JTAG.
if DebugProbe.Protocol.SWD in self._supported_protocols:
Expand Down

0 comments on commit 6070a62

Please sign in to comment.