Skip to content
Merged
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
270 changes: 155 additions & 115 deletions pyocd/subcommands/run_cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -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__)

Expand Down Expand Up @@ -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()
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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:
Expand Down
Loading