From f8df723ae00e09ffde955519675567d07e6dd74d Mon Sep 17 00:00:00 2001 From: David Lesnjak Date: Mon, 12 Oct 2026 10:15:40 +0200 Subject: [PATCH] run_cmd: update run server scheduling and RTT discovery (align with gdbserver) --- pyocd/subcommands/run_cmd.py | 270 ++++++++++++++++++++--------------- 1 file changed, 155 insertions(+), 115 deletions(-) diff --git a/pyocd/subcommands/run_cmd.py b/pyocd/subcommands/run_cmd.py index b7d26125c..50d0f70a7 100644 --- a/pyocd/subcommands/run_cmd.py +++ b/pyocd/subcommands/run_cmd.py @@ -19,24 +19,22 @@ import logging import sys import threading -from time import sleep, time - -from pyocd.core import exceptions +from time import sleep, monotonic from .base import SubcommandBase +from ..core import exceptions from ..core.helpers import ConnectHelper from ..core.session import Session -from ..utility.cmdline import convert_session_options -from ..coresight.generic_mem_ap import GenericMemAPTarget - from ..core.target import Target -from ..debug import semihost +from ..coresight.generic_mem_ap import GenericMemAPTarget +from ..utility.cmdline import convert_session_options from ..utility.timeout import Timeout +from ..utility.rtt_server import RTTServer from ..utility.rtt_manager import RTTConfig, RTTManager from ..utility.systemview import SystemViewConfig, SystemViewSVDat -from ..trace.swv import SWVReader - from ..utility.stdio import StdioHandler +from ..debug import semihost +from ..trace.swv import SWVReader LOG = logging.getLogger(__name__) @@ -142,20 +140,6 @@ def invoke(self) -> int: swv_reader = SWVReader(session) swv_reader.init(sys_clock, swo_clock, sys.stdout) - # Start up the run servers - for core_number, core in session.board.target.cores.items(): - # Don't create a server for CPU-less memory Access Port - if isinstance(session.board.target.cores[core_number], GenericMemAPTarget): - continue - - run_server = RunServer(session=session, - core=core_number, - rtt_config=rtt_config_list[core_number], - systemview_config=systemview_config, - enable_eot=self._args.eot, - shutdown_event=self.shared_shutdown) - self._run_servers.append(run_server) - # Reset the cores before starting the Run servers. for core in session.board.target.cores.values(): core.set_reset_catch() @@ -172,17 +156,28 @@ def invoke(self) -> int: core.resume() # Start the Run servers - for run_server in self._run_servers: + for core_number, core in session.board.target.cores.items(): + # Don't create a server for CPU-less memory Access Port + if isinstance(session.board.target.cores[core_number], GenericMemAPTarget): + continue + + run_server = RunServer(session=session, + core=core_number, + rtt_config=rtt_config_list[core_number], + systemview_config=systemview_config, + enable_eot=self._args.eot, + shutdown_event=self.shared_shutdown) + self._run_servers.append(run_server) run_server.start() # Wait for all servers to complete or timelimit to expire - start_time = time() + start_time = monotonic() timelimit = self._args.timelimit while any(server.is_alive() for server in self._run_servers): # Check if timelimit has been exceeded if timelimit is not None: - elapsed = time() - start_time + elapsed = monotonic() - start_time if elapsed >= timelimit: LOG.info("Time limit of %.1f seconds reached; shutting down all Run servers", timelimit) self.shutdown() @@ -216,15 +211,20 @@ def shutdown(self): if server.is_alive(): LOG.warning("Run server for core %d did not terminate cleanly", server.core) - class RunServer(threading.Thread): - def __init__(self, session: Session, core: Optional[int] = None, rtt_config: "RTTConfig" = None, - systemview_config: "SystemViewConfig" = None, enable_eot: bool = False, + ## Interval for retrying automatic RTT discovery, in seconds. + _RTT_DISCOVERY_INTERVAL = 0.010 + ## RTT channel polling interval used by the run thread, in seconds. + _RTT_POLL_INTERVAL = 0.001 + ## Target state polling interval used by the run thread, in seconds. + _TARGET_STATE_INTERVAL = 0.010 + + def __init__(self, session: Session, core: Optional[int] = None, rtt_config: Optional[RTTConfig] = None, + systemview_config: Optional[SystemViewConfig] = None, enable_eot: bool = False, shutdown_event: Optional[threading.Event] = None): super().__init__(daemon=True) self._session = session - self.eot_flag = False self._board = session.board if core is None: self.core = 0 @@ -236,6 +236,7 @@ def __init__(self, session: Session, core: Optional[int] = None, rtt_config: "RT self._shutdown_event = shutdown_event or threading.Event() self._enable_eot = enable_eot + self._is_halted = False self.name = "run-server-%d" % self.core @@ -253,100 +254,139 @@ def __init__(self, session: Session, core: Optional[int] = None, rtt_config: "RT semihost_console = semihost.ConsoleIOHandler(self._stdio_handler) self._semihost = semihost.SemihostAgent(self._target_context, io_handler=semihost_io_handler, console=semihost_console) - # Start RTT server - self._rtt_server = None - try: - rtt_manager = RTTManager(session=session, core=core, rtt_config=rtt_config, systemview_config=systemview_config) - self._rtt_server = rtt_manager.start_server() - if self._rtt_server is not None: - rtt_manager.configure_channels(stdio_handler=self._stdio_handler) - except RuntimeError as e: - LOG.debug("RTT configuration failed for core %d: %s", self.core, e) + # Start RTT server and retain the manager for discovery retries. + self._rtt_server: Optional[RTTServer] = None + self._rtt_manager: Optional[RTTManager] = None + if rtt_config is None: + try: + rtt_config = RTTConfig(_session=session, _target=self._target, _core=self.core) + except Exception as error: + LOG.error("RTT configuration failed for core %d: %s", self.core, error, exc_info=self._session.log_tracebacks) - def run(self): - stdio_info = self._stdio_handler.info - node_name = self._session.board.target.cores[self.core].node_name - rtt_status = "enabled" if self._rtt_server else "disabled" - LOG.info("Run server started for %s (core %d); STDIO mode: %s; RTT: %s", - node_name, self.core, stdio_info, rtt_status) - - # Timeout used only if the target starts returning faults. The is_running property of this timeout - # also serves as a flag that a fault occurred and we're attempting to retry. - fault_retry_timeout = Timeout(self._session.options.get('debug.status_fault_retry_timeout')) - - state_halted = False - state_check_interval_counter = 0 - while fault_retry_timeout.check(): - if self._shutdown_event.is_set(): - # Exit the thread - LOG.debug("Exit Run server for core %d", self.core) - break - - # Check for EOT (0x04) - if self._stdio_handler and self._enable_eot: - try: - if self._stdio_handler.eot_seen: - # EOT received, terminate execution - LOG.info("EOT (0x04) character received for core %d; shutting down all Run servers", self.core) - self.eot_flag = True - self._shutdown_event.set() - continue - except Exception as e: - LOG.debug("Error while waiting for EOT (0x04): %s", e) + if rtt_config is not None and rtt_config.channels is not None: + try: + self._rtt_manager = RTTManager(session=session, core=self.core, rtt_config=rtt_config, + systemview_config=systemview_config or SystemViewConfig(_session=session)) + except Exception as error: + LOG.error("RTT configuration failed for core %d: %s", self.core, error, exc_info=self._session.log_tracebacks) - state_check_interval_counter += 1 + def _start_rtt(self) -> None: + """@brief Discover and configure RTT.""" + try: + rtt_server = self._rtt_manager.start_server() + if rtt_server is not None: + if self._shutdown_event.is_set(): + rtt_server.stop() + return + self._rtt_server = rtt_server + self._rtt_manager.configure_channels(stdio_handler=self._stdio_handler) + except Exception as error: + LOG.debug("RTT discovery failed for core %d: %s", self.core, error, exc_info=self._session.log_tracebacks) + + def _read_and_process_target_state(self) -> None: + """@brief Process the observed target state, handle semihosting, and report halt and resume transitions.""" + state = self._target.get_state() + if state == Target.State.HALTED: + # Handle semihosting + if self._enable_semihosting and self._semihost.check_and_handle_semihost_request(): + self._target.resume() + elif not self._is_halted: + self._is_halted = True + pc = self._target_context.read_core_register('pc') + LOG.warning("Target core %d unexpectedly halted at pc=0x%08x", self.core, pc) + else: + if self._is_halted: + self._is_halted = False + LOG.info("Target core %d resumed from halt", self.core) + def _check_eot(self) -> bool: + """@brief Stop all Run servers if EOT has been detected.""" + if self._stdio_handler and self._enable_eot: try: - if self._rtt_server: - self._rtt_server.poll() + if self._stdio_handler.eot_seen: + LOG.info("EOT (0x04) character received for core %d; shutting down all Run servers", self.core) + self._shutdown_event.set() + return True + except Exception as e: + LOG.debug("Error while waiting for EOT (0x04): %s", e) + return False - # Check target state and handle semihosting every 10ms (every 10 loop iterations) - # to avoid slowing down RTT polling - if state_check_interval_counter == 10: - state_check_interval_counter = 0 + def run(self): + stdio_info = self._stdio_handler.info + rtt_status = "enabled" if self._rtt_manager is not None else "disabled" + LOG.info("Run server started for core %d; STDIO mode: %s; RTT: %s", + self.core, stdio_info, rtt_status) - state = self._target.get_state() + eot_seen = False - # If we were able to successfully read the target state after previously receiving a fault, - # then clear the timeout. + fault_retry_timeout = Timeout(self._session.options.get('live.status_fault_retry_timeout')) + + now = monotonic() + if self._rtt_manager is not None and self._rtt_server is None: + rtt_discovery_time = now + else: + rtt_discovery_time = float('inf') + rtt_poll_time = float('inf') + target_state_time = now + + while not self._shutdown_event.is_set(): + now = monotonic() + + # Discover RTT control block + if now >= rtt_discovery_time: + rtt_discovery_time = now + self._RTT_DISCOVERY_INTERVAL + if self._rtt_manager is not None and self._rtt_server is None: + self._start_rtt() + if self._rtt_server is not None: + rtt_discovery_time = float('inf') + # Start polling RTT immediately after discovery. + rtt_poll_time = now + + # Poll RTT + if now >= rtt_poll_time: + rtt_poll_time = now + self._RTT_POLL_INTERVAL + try: + self._rtt_server.poll() + except Exception as error: + LOG.debug("RTT poll failed for core %d: %s", self.core, error, exc_info=self._session.log_tracebacks) + # Check for EOT after polling RTT. + if self._check_eot(): + break + + # Check target and handle target state + if now >= target_state_time: + target_state_time = now + self._TARGET_STATE_INTERVAL + if fault_retry_timeout.did_time_out: + # Transfer error persisted beyond the retry timeout. + # Shut down the Run server instance for this core. + LOG.error("Timeout expired after repeated transfer errors for core %d", self.core) + break + try: + self._read_and_process_target_state() if fault_retry_timeout.is_running: - LOG.debug("Target control re-established") + # Target state read succeeded after a transfer error; clear the fault retry timeout. + LOG.info("Recovered after transfer error for core %d.", self.core) fault_retry_timeout.clear() - - if state == Target.State.HALTED: - # Handle semihosting - if self._enable_semihosting: - was_semihost = self._semihost.check_and_handle_semihost_request() - if was_semihost: - self._target.resume() - continue - if not state_halted: - state_halted = True - pc = self._target_context.read_core_register('pc') - LOG.warning("Target core %d unexpectedly halted at pc=0x%08x", self.core, pc) - else: - if state_halted: - state_halted = False - LOG.info("Target core %d resumed from halt", self.core) - - except exceptions.TransferError as e: - # If we get any sort of transfer error or fault while checking target status, then start - # a timeout running. Upon a later successful status check, the timeout is cleared. In the event - # that the timeout expires, this loop is exited and an error raised. - if not fault_retry_timeout.is_running: - LOG.warning("Transfer error while checking target status; retrying: %s", e, - exc_info=self._session.log_tracebacks) - fault_retry_timeout.start() - except exceptions.Error as e: - LOG.error("Error while target core %d running: %s; exiting Run server for core %d", - self.core, e, self.core, exc_info=self._session.log_tracebacks) - break - finally: - sleep(0.001) - - # Check if we exited the above loop due to a timeout after a fault. - if fault_retry_timeout.did_time_out: - LOG.error("Timeout re-establishing target core %d control; exiting Run server for core %d", self.core, self.core) + except exceptions.TransferError as error: + if not fault_retry_timeout.is_running: + # Start the timeout on the first fault; the next scheduled poll retries. + LOG.warning("Transfer error detected for core %d", self.core) + fault_retry_timeout.start() + except Exception as error: + LOG.error("Unexpected error for core %d: %s", + self.core, error, exc_info=self._session.log_tracebacks) + break + finally: + # Check for EOT. + eot_seen = self._check_eot() + if eot_seen: + break + + wait_time = min(rtt_discovery_time, rtt_poll_time, target_state_time) - monotonic() + if wait_time > 0.0: + self._shutdown_event.wait(wait_time) + + LOG.debug("Exit Run server for core %d", self.core) # Cleanup resources for this RunServer. try: