Repository: pyocd/pyocd
Issue: PSoC6: gdb server broken on v0.28.0 onwards  

I've just noticed that gdb server is not usable with pyOCD master and PSoC6A-2M target. Any attempt to reset the target e.g. using 'monitor reset' command always leads to 'TransferError: No ACK received' exception and makes any further communication with the target impossible.

Short investigation showed that this if statement:
https://github.com/pyocd/pyOCD/blob/aefed9151a37bf15a8ddf8222397bee089af2b37/pyocd/probe/shared_probe_proxy.py#L76-L78

Makes this a NO-OP:
https://github.com/pyocd/pyOCD/blob/aefed9151a37bf15a8ddf8222397bee089af2b37/pyocd/target/family/target_psoc6.py#L64-L68

PSoC6 behavior is somewhat strange, even soft-reset via SYSRESETREQ causes partial reset of the DAP so it switches back to JTAG mode and contents of some DAP registers (e.g. TAR) gets lost. Full re-initialization of the DAP after Reset is a must with PSoC6 targets (JTAG-to-SWD, read IDCODE, power-up debug domain, etc). DAP re-initialization code can be found in numerous places in the family/target_psoc6.py file.

Removing this if statement 
https://github.com/pyocd/pyOCD/blob/aefed9151a37bf15a8ddf8222397bee089af2b37/pyocd/probe/shared_probe_proxy.py#L77
resolves the issue but I clearly understand that this is not a valid fix.

I will try to find a proper way to re-initialize the DAP on my own but I am not closely familiar with pyOCD architecture, any support will be appreciated. 

Thanks in advance,
Bohdan

Changed files:
pyocd/coresight/dap.py, pyocd/probe/swj.py, pyocd/target/builtin/target_CC3220SF.py, pyocd/target/family/target_psoc6.py, pyocd/coresight/coresight_target.py, pyocd/target/builtin/target_s5js100.py, pyocd/probe/debug_probe.py, pyocd/probe/shared_probe_proxy.py, pyocd/commands/commands.py, pyocd/commands/commander.py

diff --git a/pyocd/commands/commander.py b/pyocd/commands/commander.py
index 0c4ced6a..884401a2 100755
--- a/pyocd/commands/commander.py
+++ b/pyocd/commands/commander.py
@@ -21,6 +21,7 @@ import traceback
 
 from ..core.helpers import ConnectHelper
 from ..core import (exceptions, session)
+from ..probe.shared_probe_proxy import SharedDebugProbeProxy
 from ..utility.cmdline import convert_session_options
 from ..commands.repl import (PyocdRepl, ToolExitException)
 from ..commands.execution_context import CommandExecutionContext
@@ -160,14 +161,24 @@ class PyOCDCommander(object):
             connect_mode = 'attach'
         
         # Connect to board.
-        self.session = ConnectHelper.session_with_chosen_probe(
+        probe = ConnectHelper.choose_probe(
                         blocking=(not self.args.no_wait),
+                        unique_id=self.args.unique_id,
+                        )
+        if probe is None:
+            self.exit_code = 3
+            return False
+        
+        # Create a proxy so the probe can be shared between the session and a possible probe server.
+        probe_proxy = SharedDebugProbeProxy(probe)
+        
+        # Create the session.
+        self.session = session.Session(probe_proxy,
                         project_dir=self.args.project_dir,
                         config_file=self.args.config,
                         user_script=self.args.script,
                         no_config=self.args.no_config,
                         pack=self.args.pack,
-                        unique_id=self.args.unique_id,
                         target_override=self.args.target_override,
                         connect_mode=connect_mode,
                         frequency=self.args.frequency,
@@ -175,10 +186,8 @@ class PyOCDCommander(object):
                         option_defaults=dict(
                             auto_unlock=False,
                             resume_on_disconnect=False,
-                            ))
-        if self.session is None:
-            self.exit_code = 3
-            return False
+                            )
+                        )
         
         if not self._post_connect():
             self.exit_code = 4
diff --git a/pyocd/commands/commands.py b/pyocd/commands/commands.py
index 84315141..a30ef04d 100755
--- a/pyocd/commands/commands.py
+++ b/pyocd/commands/commands.py
@@ -1253,8 +1253,7 @@ class InitDpCommand(CommandBase):
             }
 
     def execute(self):
-        self.context.target.dp.init()
-        self.context.target.dp.power_up_debug()
+        self.context.target.dp.connect()
 
 class MakeApCommand(CommandBase):
     INFO = {
diff --git a/pyocd/coresight/coresight_target.py b/pyocd/coresight/coresight_target.py
index 1fe2246c..04547ec7 100644
--- a/pyocd/coresight/coresight_target.py
+++ b/pyocd/coresight/coresight_target.py
@@ -75,7 +75,7 @@ class CoreSightTarget(SoCTarget):
         seq = CallSequence(
             ('load_svd',            self.load_svd),
             ('pre_connect',         self.pre_connect),
-            ('dp_init',             self.dp.init_sequence),
+            ('dp_init',             self.dp.create_connect_sequence),
             ('create_discoverer',   self.create_discoverer),
             ('discovery',           lambda : self._discoverer.discover()),
             ('check_for_cores',     self.check_for_cores),
diff --git a/pyocd/coresight/dap.py b/pyocd/coresight/dap.py
index f7727359..c5f92d0f 100644
--- a/pyocd/coresight/dap.py
+++ b/pyocd/coresight/dap.py
@@ -126,6 +126,15 @@ class DPConnector(object):
         """! @brief DPIDR instance containing values read from the DP IDR register."""
         return self._idr
     
+    def _get_protocol(self, protocol):
+        # Convert protocol from setting if not passed as parameter.
+        if protocol is None:
+            protocol_name = self._session.options.get('dap_protocol').strip().lower()
+            protocol = DebugProbe.PROTOCOL_NAME_MAP[protocol_name]
+            if protocol not in self._probe.supported_wire_protocols:
+                raise exceptions.DebugError("requested wire protocol %s not supported by the debug probe" % protocol.name)
+        return protocol
+    
     def connect(self, protocol=None):
         """! @brief Establish a connection to the DP.
         
@@ -138,27 +147,52 @@ class DPConnector(object):
         @exception DebugError
         @exception TransferError
         """
-        protocol_name = self._session.options.get('dap_protocol').strip().lower()
-        send_swj = self._session.options.get('dap_swj_enable') \\
-                and (DebugProbe.Capability.SWJ_SEQUENCE in self._probe.capabilities)
-        use_dormant = self._session.options.get('dap_swj_use_dormant')
+        try:
+            self._probe.lock()
 
-        # Convert protocol from setting if not passed as parameter.
-        if protocol is None:
-            protocol = DebugProbe.PROTOCOL_NAME_MAP[protocol_name]
-            if protocol not in self._probe.supported_wire_protocols:
-                raise exceptions.DebugError("requested wire protocol %s not supported by the debug probe" % protocol.name)
-        if protocol != DebugProbe.Protocol.DEFAULT:
+            # Determine the requested wire protocol.
+            protocol = self._get_protocol(protocol)
+
+            # If this is not None then the probe is already connected.
+            current_wire_protocol = self._probe.wire_protocol
+            already_connected = current_wire_protocol is not None
+        
+            if already_connected:
+                self._check_protocol(current_wire_protocol, protocol)
+            else:
+                self._connect_probe(protocol)
+
+            protocol = self._probe.wire_protocol
+            self._connect_dp(protocol)
+        finally:
+            self._probe.unlock()
+    
+    def _check_protocol(self, current_wire_protocol, protocol):
+        # Warn about mismatched current and requested wire protocols.
+        if (protocol is not current_wire_protocol) and (protocol is not DebugProbe.Protocol.DEFAULT):
+            LOG.warning("Cannot use %s; already connected with %s", protocol.name, current_wire_protocol.name)
+        else:
+            LOG.debug("Already connected with %s", current_wire_protocol.name)
+
+    def _connect_probe(self, protocol):
+        # Debug log with the selected protocol.
+        if protocol is not DebugProbe.Protocol.DEFAULT:
             LOG.debug("Using %s wire protocol", protocol.name)
-            
+        
         # Connect using the selected protocol.
         self._probe.connect(protocol)
 
         # Log the actual protocol if selected was default.
-        if protocol == DebugProbe.Protocol.DEFAULT:
+        if protocol is DebugProbe.Protocol.DEFAULT:
             protocol = self._probe.wire_protocol
             LOG.debug("Default wire protocol selected; using %s", protocol.name)
         
+    def _connect_dp(self, protocol):
+        # Get SWJ settings.
+        use_dormant = self._session.options.get('dap_swj_use_dormant')
+        send_swj = self._session.options.get('dap_swj_enable') \\
+                and (DebugProbe.Capability.SWJ_SEQUENCE in self._probe.capabilities)
+
         # Create object to send SWJ sequences.
         swj = SWJSequenceSender(self._probe, use_dormant)
         
@@ -207,6 +241,13 @@ class DebugPort(object):
     """! @brief Represents the Arm Debug Interface (ADI) Debug Port (DP)."""
     
     def __init__(self, probe, target):
+        """! @brief Constructor.
+        @param self The DebugPort object.
+        @param probe The @ref pyocd.probe.debug_probe.DebugProbe "DebugProbe" object. The probe is assumed to not
+            have been opened yet.
+        @param target An instance of @ref pyocd.core.soc_target.SoCTarget "SoCTarget". Assumed to not have been
+            fully initialized.
+        """
         self._probe = probe
         self.target = target
         self._session = target.session
@@ -219,6 +260,8 @@ class DebugPort(object):
         self._probe_managed_ap_select = False
         self._probe_managed_dpbanksel = False
         self._probe_supports_dpbanksel = False
+        self._have_probe_capabilities = False
+        self._did_check_version = False
         
         # DPv3 attributes
         self._is_dpv3 = False
@@ -268,24 +311,24 @@ class DebugPort(object):
         """! @brief Unlock the DP."""
         self.probe.unlock()
 
-    def init(self, protocol=None):
+    def connect(self, protocol=None):
         """! @brief Connect to the target.
         
         This method causes the debug probe to connect using the selected wire protocol. The probe
         must have already been opened prior to this call.
         
-        Unlike init_sequence(), this method is intended to be used when manually constructing a
-        DebugPort instance. It simply calls init_sequence() and invokes the returned call sequence.
+        Unlike create_connect_sequence(), this method is intended to be used when manually constructing a
+        DebugPort instance. It simply calls create_connect_sequence() and invokes the returned call sequence.
         
         @param self
         @param protocol One of the @ref pyocd.probe.debug_probe.DebugProbe.Protocol
             "DebugProbe.Protocol" enums. If not provided, will default to the `protocol` setting.
         """
         self._protocol = protocol
-        self.init_sequence().invoke()
+        self.create_connect_sequence().invoke()
 
-    def init_sequence(self):
-        """! @brief Init task to connect to the target.
+    def create_connect_sequence(self):
+        """! @brief Returns call sequence to connect to the target.
         
         Returns a @ref pyocd.utility.sequence.CallSequence CallSequence that will connect to the
         DP, power up debug and the system, check the DP version to identify whether the target uses
@@ -294,14 +337,28 @@ class DebugPort(object):
         The probe must have already been opened prior to this method being called.
         
         @param self
+        @return @ref pyocd.utility.sequence.CallSequence CallSequence
         """
-        return CallSequence(
-            ('get_probe_capabilities', self._get_probe_capabilities),
+        seq = [
+            ('lock_probe',          self.probe.lock),
+            ]
+        if not self._have_probe_capabilities:
+            seq += [
+                ('get_probe_capabilities', self._get_probe_capabilities),
+                ]
+        seq += [
             ('connect',             self._connect),
             ('clear_sticky_err',    self.clear_sticky_err),
             ('power_up_debug',      self.power_up_debug),
-            ('check_version',       self._check_version),
-            )
+            ]
+        if not self._did_check_version:
+            seq += [
+                ('check_version',       self._check_version),
+                ]
+        seq += [
+            ('unlock_probe',        self.probe.unlock),
+            ]
+        return CallSequence(*seq)
 
     def _get_probe_capabilities(self):
         """! @brief Examine the probe's capabilities."""
@@ -309,6 +366,7 @@ class DebugPort(object):
         self._probe_managed_ap_select = (DebugProbe.Capability.MANAGED_AP_SELECTION in caps)
         self._probe_managed_dpbanksel = (DebugProbe.Capability.MANAGED_DPBANKSEL in caps)
         self._probe_supports_dpbanksel = (DebugProbe.Capability.BANKED_DP_REGISTERS in caps)
+        self._have_probe_capabilities = True
 
     def _connect(self):
         # Attempt to connect.
@@ -347,6 +405,8 @@ class DebugPort(object):
             else:
                 LOG.warning("DPv3 has no valid base address")
 
+        self._did_check_version = True
+
     def flush(self):
         try:
             self.probe.flush()
diff --git a/pyocd/probe/debug_probe.py b/pyocd/probe/debug_probe.py
index 25ae5c14..53c941d1 100644
--- a/pyocd/probe/debug_probe.py
+++ b/pyocd/probe/debug_probe.py
@@ -142,7 +142,8 @@ class DebugProbe(object):
         """! @brief Currently selected wire protocol.
         
         If the probe is not open and connected, i.e., open() and connect() have not been called,
-        then this property will be None.
+        then this property will be None. If a value other than None is returned, then the probe
+        has been connected successfully.
         """
         raise NotImplementedError()
     
@@ -188,6 +189,8 @@ class DebugProbe(object):
         
         This lock is recursive, so locking multiple times from a single thread is acceptable as long
         as the thread unlocks the same number of times.
+        
+        This method does not return until the calling thread has ownership of the lock.
         """
         self._lock.acquire()
     
diff --git a/pyocd/probe/shared_probe_proxy.py b/pyocd/probe/shared_probe_proxy.py
index cd3fbfcc..0ad30ed0 100644
--- a/pyocd/probe/shared_probe_proxy.py
+++ b/pyocd/probe/shared_probe_proxy.py
@@ -33,7 +33,6 @@ class SharedDebugProbeProxy(object):
         self._probe = probe
         self._open_count = 0
         self._connect_count = 0
-        self._active_protocol = None
 
     @property
     def session(self):
@@ -73,9 +72,7 @@ class SharedDebugProbeProxy(object):
         self._connect_count -= 1
 
     def swj_sequence(self, length, bits):
-        # Only the first connected client can perform SWJ sequences.
-        if self._connect_count == 1:
-            self._probe.swj_sequence(length, bits)
+        self._probe.swj_sequence(length, bits)
     
     def __getattr__(self, name):
         """! @brief Redirect to underlying probe object methods."""
diff --git a/pyocd/probe/swj.py b/pyocd/probe/swj.py
index 3c0387cc..bdb06812 100644
--- a/pyocd/probe/swj.py
+++ b/pyocd/probe/swj.py
@@ -43,6 +43,8 @@ class SWJSequenceSender(object):
             self._switch_to_swd()
         elif protocol == DebugProbe.Protocol.JTAG:
             self._switch_to_jtag()
+        else:
+            assert False, "unhandled protocol %s in SWJSequenceSender" % protocol
 
     def _switch_to_swd(self):
         """! @brief Send SWJ sequence to select SWD."""
diff --git a/pyocd/target/builtin/target_CC3220SF.py b/pyocd/target/builtin/target_CC3220SF.py
index 095097f7..48158660 100644
--- a/pyocd/target/builtin/target_CC3220SF.py
+++ b/pyocd/target/builtin/target_CC3220SF.py
@@ -99,8 +99,7 @@ class Flash_cc3220sf(Flash):
         time.sleep(1.3)
 
         # reconnect to the board
-        self.target.dp.init()
-        self.target.dp.power_up_debug()
+        self.target.dp.connect()
 
         self.target.halt()
         self.target.reset_and_halt()
diff --git a/pyocd/target/builtin/target_s5js100.py b/pyocd/target/builtin/target_s5js100.py
index aa69241d..80d5a1db 100644
--- a/pyocd/target/builtin/target_s5js100.py
+++ b/pyocd/target/builtin/target_s5js100.py
@@ -232,8 +232,7 @@ class CortexM_S5JS100(CortexM):
                     sleep(0.1)
                 except exceptions.TransferError:
                     self.flush()
-                    self._ap.dp.init()
-                    self._ap.dp.power_up_debug()
+                    self._ap.dp.connect()
                     sleep(0.01)
             else:
                 raise exceptions.TimeoutError("Timeout waiting for reset")
@@ -271,8 +270,7 @@ class CortexM_S5JS100(CortexM):
             # LOG.info("s5js100.get_state dhcsr 0x%x", dhcsr)
         except exceptions.TransferError:
             # LOG.info("s5js100.get_state read fail dhcsr..try more")
-            self._ap.dp.init()
-            self._ap.dp.power_up_debug()
+            self._ap.dp.connect()
             dhcsr = self.read_memory(CortexM.DHCSR)
             # LOG.info("fail s5js100.get_state dhcsr 0x%x", dhcsr)
 
diff --git a/pyocd/target/family/target_psoc6.py b/pyocd/target/family/target_psoc6.py
index eee9adfe..351daf42 100644
--- a/pyocd/target/family/target_psoc6.py
+++ b/pyocd/target/family/target_psoc6.py
@@ -39,8 +39,7 @@ class CortexM_PSoC6(CortexM):
         if reset_type is Target.ResetType.HW:
             self._ap.dp.reset()
             sleep(0.5)
-            self._ap.dp.init()
-            self._ap.dp.power_up_debug()
+            self._ap.dp.connect()
             self.fpb.enable()
         else:
             if reset_type is Target.ResetType.SW_VECTRESET:
@@ -63,8 +62,7 @@ class CortexM_PSoC6(CortexM):
                 except exceptions.TransferError:
                     self.flush()
                     try:
-                        self._ap.dp.init()
-                        self._ap.dp.power_up_debug()
+                        self._ap.dp.connect()
                     except exceptions.TransferError:
                         self.flush()
 
@@ -213,8 +211,7 @@ class CortexM_PSoC64(CortexM):
         with Timeout(5.0) as t_o:
             while t_o.check():
                 try:
-                    self._ap.dp.init()
-                    self._ap.dp.power_up_debug()
+                    self._ap.dp.connect()
                     dhcsr_reg = self.read32(CortexM.DHCSR)
                     if (dhcsr_reg & CortexM.S_RESET_ST) == 0:
                         break
@@ -241,8 +238,7 @@ class CortexM_PSoC64(CortexM):
         with Timeout(2.0) as t_o:
             while t_o.check():
                 try:
-                    self._ap.dp.init()
-                    self._ap.dp.power_up_debug()
+                    self._ap.dp.connect()
                     self.flush()
                     break
                 except exceptions.TransferError:
@@ -255,8 +251,7 @@ class CortexM_PSoC64(CortexM):
         with Timeout(self.acquire_timeout) as t_o:
             while t_o.check():
                 try:
-                    self._ap.dp.init()
-                    self._ap.dp.power_up_debug()
+                    self._ap.dp.connect()
                     # self.write32(self.IPC2_DATA_ADDR, 0)
                     self.write32(self.TEST_MODE_ADDR, self.TEST_MODE_VALUE)
                     self.flush()
@@ -315,8 +310,7 @@ class CortexM_PSoC64(CortexM):
         with Timeout(self.acquire_timeout) as t_o:
             while t_o.check():
                 try:
-                    self._ap.dp.init()
-                    self._ap.dp.power_up_debug()
+                    self._ap.dp.connect()
                     self.halt()
                     self.wait_halted()
                     self.write_core_register('xpsr', CortexM.XPSR_THUMB)
@@ -384,8 +378,7 @@ class SYS_AP_PSoC64(GenericMemAPTarget):
         with Timeout(self._acquire_timeout) as t_o:
             while t_o.check():
                 try:
-                    self._ap.dp.init()
-                    self._ap.dp.power_up_debug()
+                    self._ap.dp.connect()
                     break
                 except exceptions.TransferError:
                     pass