From 6070a62db94097117f662e3ec27849256a0ae8c3 Mon Sep 17 00:00:00 2001 From: Chris Reed Date: Sun, 8 Nov 2020 17:32:58 -0600 Subject: [PATCH] Merge pull request #997 from flit/bugfix/jlink_open Fix issue when another process has a J-Link open --- pyocd/commands/commander.py | 11 +++++++- pyocd/probe/aggregator.py | 9 ++++++ pyocd/probe/debug_probe.py | 17 +++++++----- pyocd/probe/jlink_probe.py | 55 ++++++++++++++++++++++++++----------- 4 files changed, 68 insertions(+), 24 deletions(-) diff --git a/pyocd/commands/commander.py b/pyocd/commands/commander.py index dda3def7d..16e1ede0e 100755 --- a/pyocd/commands/commander.py +++ b/pyocd/commands/commander.py @@ -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. """ @@ -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: diff --git a/pyocd/probe/aggregator.py b/pyocd/probe/aggregator.py index ff370ef3a..e00a786a6 100644 --- a/pyocd/probe/aggregator.py +++ b/pyocd/probe/aggregator.py @@ -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) diff --git a/pyocd/probe/debug_probe.py b/pyocd/probe/debug_probe.py index cc7e8af6a..25ae5c14f 100644 --- a/pyocd/probe/debug_probe.py +++ b/pyocd/probe/debug_probe.py @@ -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. @@ -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() diff --git a/pyocd/probe/jlink_probe.py b/pyocd/probe/jlink_probe.py index dcd6da73d..62e07886f 100644 --- a/pyocd/probe/jlink_probe.py +++ b/pyocd/probe/jlink_probe.py @@ -54,6 +54,10 @@ 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): @@ -61,7 +65,7 @@ def get_all_connected_probes(cls, unique_id=None, is_explicit=False): 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) @@ -72,35 +76,52 @@ 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): @@ -108,7 +129,7 @@ def description(self): @property def vendor_name(self): - return self._oem or "Segger" + return "Segger" @property def product_name(self): @@ -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. @@ -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: