Skip to content
Merged
Show file tree
Hide file tree
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
22 changes: 20 additions & 2 deletions docs/command_reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,16 @@ Show the target's current state.
Control thread awareness.
</td></tr>

<tr><td colspan="3"><b>Trace</b></td></tr>

<tr><td>
<a href="#ctrace"><tt>ctrace</tt></a>
</td><td>
reload
</td><td>
Reload and immediately apply the ctrace-run configuration.
</td></tr>

<tr><td colspan="3"><b>Utility</b></td></tr>

<tr><td>
Expand Down Expand Up @@ -1104,6 +1114,14 @@ Show the target's current state.
Control thread awareness.


### Trace

##### `ctrace`

**Usage**: ctrace reload \
Reload and immediately apply the ctrace-run configuration.


### Utility

##### `sleep`
Expand Down Expand Up @@ -1185,8 +1203,8 @@ The current HNONSEC attribute value used by the selected MEM-AP. This value cont
**Usage**: show hprot, set hprot VALUE \
The current memory transfer attributes value used by the selected MEM-AP. This integer value controls attributes of memory transfers. It is a direct mapping of the AHB
or AXI attribute settings, depending on the type of MEM-AP. For AHB-APs, the value is HPROT[4:0].
For AXI-APs, the value is {AxPROT[2:0}, AxCACHE[3:0]}, e.g. AxPROT in bits 6-4 and AxCACHE in
its 3-0. Not all MEM-AP implementations support all attributes. See the Arm Technical Reference
For AXI-APs, the value is {AxPROT[2:0], AxCACHE[3:0]}, e.g. AxPROT in bits 6-4 and AxCACHE in
bits 3-0. Not all MEM-AP implementations support all attributes. See the Arm Technical Reference
Manual for your device's MEM-AP for details.

##### `locked`
Expand Down
34 changes: 30 additions & 4 deletions pyocd/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@
from .options_manager import OptionsManager
from .target import Target
from ..utility.notification import Notifier
from ..target.pack.cbuild_run import CbuildRun
from ..target.csolution.cbuild_run import CbuildRun
from ..target.csolution.ctrace_run import CTraceRun

if TYPE_CHECKING:
from types import TracebackType
Expand Down Expand Up @@ -112,6 +113,10 @@ class Event(Enum):
"""Session notification events."""
## Sent after target-specific trace support has been reinitialized following a reset.
TRACE_RESTART = 1
## Sent before resuming target execution. Opens a new raw trace output file for capture.
TRACE_DATA_CAPTURE = 2
## Sent after target has been halted. Flushes buffered raw trace data to its output file.
TRACE_DATA_FLUSH = 3

@classmethod
def get_current(cls) -> Self:
Expand Down Expand Up @@ -187,6 +192,7 @@ def __init__(
self._probeserver: Optional[DebugProbeServer] = None
self._context_state = SimpleNamespace()
self._cbuild_run: Optional[CbuildRun] = None
self._ctrace_run: Optional[CTraceRun] = None
self._trace_started: bool = False

# Set this session on the probe, if we were given a probe.
Expand Down Expand Up @@ -215,6 +221,11 @@ def __init__(
self._cbuild_run = CbuildRun(self.options.get('cbuild_run'))
cbuild_run_config = self._get_cbuild_run_config(command)
self._options.add_back(cbuild_run_config)
if self.cbuild_run is not None and self.cbuild_run.trace.enabled:
try:
self._ctrace_run = CTraceRun(self)
except (exceptions.Error, OSError) as err:
LOG.error("Failed to initialize ctrace-run support: %s", err)

# Load options from the config file.
config = self._get_config()
Expand Down Expand Up @@ -306,6 +317,17 @@ def _get_cbuild_run_config(self, command: Optional[str]) -> Dict[str, Any]:
debugger_options['systemview_auto_start'] = self.cbuild_run.systemview_auto_start
debugger_options['systemview_auto_stop'] = self.cbuild_run.systemview_auto_stop

swo_uart = self.cbuild_run.trace.swo_uart
if swo_uart is not None and swo_uart.enabled:
debugger_options['enable_swv'] = True
debugger_options['swv_system_clock'] = swo_uart.input_clock
debugger_options['swv_clock'] = swo_uart.output_clock
debugger_options['swv_raw_enable'] = True
if swo_uart.mode == 'file':
debugger_options['swv_raw_file'] = swo_uart.file
elif swo_uart.mode == 'server':
debugger_options['swv_raw_port'] = swo_uart.server_port

# Set reset types for load operations.
debugger_options['load.pre_reset'] = self.cbuild_run.pre_reset
debugger_options['load.post_reset'] = self.cbuild_run.post_reset
Expand Down Expand Up @@ -478,6 +500,11 @@ def cbuild_run(self) -> Optional[CbuildRun]:
"""@brief The CbuildRun instance if a cbuild-run file was loaded."""
return self._cbuild_run

@property
def ctrace_run(self) -> Optional[CTraceRun]:
"""@brief The CTraceRun instance if trace was enabled by cbuild-run."""
return self._ctrace_run

def __enter__(self) -> "Session":
assert self._probe is not None
if self._auto_open:
Expand Down Expand Up @@ -604,7 +631,7 @@ def _trace_start(self) -> None:
if self._trace_started:
return

if self.target is not None:
if self.target is not None and self.target.trace_enabled:
self.target.trace_start()
self._trace_started = True
self.subscribe(self._reset_handler, Target.Event.POST_RESET)
Expand Down Expand Up @@ -661,8 +688,7 @@ def open(self, init_board: bool = True) -> None:
if init_board:
self._board.init()
self._inited = True
if self.options.get('enable_swv'):
self._trace_start()
self._trace_start()

def disconnect(self) -> None:
"""@brief Disconnect the session without closing the probe.
Expand Down
6 changes: 6 additions & 0 deletions pyocd/core/soc_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,12 @@ def get_target_context(self, core: Optional[int] = None) -> DebugContext:
core_obj = self.selected_core_or_raise
return core_obj.get_target_context()

@property
def trace_enabled(self) -> bool:
"""@brief Whether trace capture and flush handling is enabled."""
delegate = self.debug_sequence_delegate
return delegate is not None and delegate.trace_enabled

def trace_start(self):
self.call_delegate('trace_start', target=self, mode=0)

Expand Down
89 changes: 72 additions & 17 deletions pyocd/coresight/coresight_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,17 +26,19 @@
from ..flash.flash import Flash
from . import (dap, discovery)
from ..debug.svd.loader import SVDLoader
from ..debug.sequences.delegates import TraceSetup
from ..trace.sink import TraceBufferSinks
from ..utility.sequencer import CallSequence
from ..target.pack.flm_region_builder import FlmFlashRegionBuilder

if TYPE_CHECKING:
from ..core.session import Session
from ..core.memory_map import MemoryMap
from .ap import (APAddressBase, AccessPort)
from ..debug.svd.model import SVDDevice

LOG = logging.getLogger(__name__)


class CoreSightTarget(SoCTarget):
"""@brief Represents an SoC that uses CoreSight debug infrastructure.

Expand All @@ -55,6 +57,7 @@ def __init__(self, session: "Session", memory_map: Optional["MemoryMap"] = None)
self._svd_load_thread: Optional[SVDLoader] = None
self._irq_table: Optional[Dict[int, str]] = None
self._discoverer: Optional[Callable] = None
self._trace_buffer_sinks: Optional[TraceBufferSinks] = None

self.session.context_state.is_performing_pre_reset = False

Expand Down Expand Up @@ -375,26 +378,78 @@ def first_ap(self) -> Optional["AccessPort"]:
return None
return sorted(self.aps.values(), key=lambda v: v.address)[0]

@property
def trace_enabled(self) -> bool:
"""@brief Whether trace capture and flush handling is enabled."""
return self.session.options.get('enable_swv') or super().trace_enabled

def trace_start(self) -> None:
result = self.call_delegate('trace_start', target=self, mode=0)
if not result and self.has_debug_sequence('TraceStart', pname=self.selected_core_or_raise.node_name):
assert self.debug_sequence_delegate
self.debug_sequence_delegate.run_sequence('TraceStart',
pname=self.selected_core_or_raise.node_name)
result = True
return result
if not self.trace_enabled:
return

self._ensure_trace_buffer_sinks()
try:
result = self.call_delegate('trace_start', target=self, mode=0)
if not result:
self._run_trace_sequence('TraceStart')
except Exception:
self._shutdown_trace_buffer_sinks()
raise

def trace_stop(self) -> None:
result = self.call_delegate('trace_stop', target=self, mode=0)
if not result and self.has_debug_sequence('TraceStop', pname=self.selected_core_or_raise.node_name):
assert self.debug_sequence_delegate
self.debug_sequence_delegate.run_sequence('TraceStop',
pname=self.selected_core_or_raise.node_name)
result = True
return result
if not self.trace_enabled:
return

try:
result = self.call_delegate('trace_stop', target=self, mode=0)
if not result:
self._run_trace_sequence('TraceStop')
finally:
self._shutdown_trace_buffer_sinks()

def _run_trace_sequence(self, name: str) -> None:
delegate = self.debug_sequence_delegate
if delegate is None:
return

for seq in delegate.all_sequences:
if seq.name == name:
delegate.run_sequence(name, pname=seq.pname)

def trace_capture(self) -> None:
pass
changed = False
if self.session.ctrace_run is not None:
changed = self.session.ctrace_run.apply(self)

result = self.call_delegate('trace_capture', target=self, mode=0)
if not result and self.has_debug_sequence('TraceCapture'):
assert self.debug_sequence_delegate
if self.debug_sequence_delegate.trace_setup == TraceSetup.FULL:
self.debug_sequence_delegate.run_sequence('TraceCapture')
self.session.notify(self.session.Event.TRACE_DATA_CAPTURE, self.session, changed)

def trace_flush(self) -> None:
pass
result = self.call_delegate('trace_flush', target=self, mode=0)
if not result and self.has_debug_sequence('TraceFlush'):
assert self.debug_sequence_delegate
if self.debug_sequence_delegate.trace_setup == TraceSetup.FULL:
self.debug_sequence_delegate.run_sequence('TraceFlush')
self.session.notify(self.session.Event.TRACE_DATA_FLUSH, self.session)

def write_trace_buffer(self, name: str, data: bytes) -> int:
"""Write data to a configured trace buffer output."""
if self._trace_buffer_sinks is None:
raise ValueError("Trace buffer output is not initialized")
return self._trace_buffer_sinks.write(name, data)

def _ensure_trace_buffer_sinks(self) -> None:
delegate = self.debug_sequence_delegate
if self._trace_buffer_sinks is None and delegate is not None:
trace_buffers = delegate.trace_buffers
if trace_buffers:
self._trace_buffer_sinks = TraceBufferSinks(self.session, trace_buffers)

def _shutdown_trace_buffer_sinks(self) -> None:
if self._trace_buffer_sinks is not None:
self._trace_buffer_sinks.shutdown()
self._trace_buffer_sinks = None
24 changes: 23 additions & 1 deletion pyocd/debug/sequences/delegates.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
from __future__ import annotations

import logging
from typing import (TYPE_CHECKING, Optional, Set)
from enum import Enum
from typing import (Any, Mapping, TYPE_CHECKING, Optional, Set)

from .scope import Scope

Expand All @@ -28,6 +29,12 @@
from .sequences import (DebugSequence, DebugSequenceExecutionContext, FlashSequenceParams)
from ...target.pack.cmsis_pack import CmsisPackDevice

class TraceSetup(Enum):
"""@brief Trace setup modes for debug sequences."""
LEGACY = 'legacy'
FULL = 'full'


class DebugSequenceDelegate:
"""@brief Delegate interface for handling sequence operations."""

Expand All @@ -36,6 +43,21 @@ def all_sequences(self) -> Set[DebugSequence]:
"""@brief Returns a set containing all defined debug sequence objects."""
raise NotImplementedError()

@property
def trace_setup(self) -> TraceSetup:
"""@brief Returns the trace setup mode."""
return TraceSetup.LEGACY

@property
def trace_enabled(self) -> bool:
"""@brief Whether trace capture and flush handling is enabled."""
return False

@property
def trace_buffers(self) -> dict[str, Any]:
"""@brief Selected named trace buffers."""
return {}

@property
def cmsis_pack_device(self) -> CmsisPackDevice:
"""@brief Accessor for the pack device that contains the sequences."""
Expand Down
21 changes: 18 additions & 3 deletions pyocd/debug/sequences/functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ def ensure_capacity(buffer: _Buffer, size: int) -> None:
if size > len(buffer.data):
buffer.data.extend(b"\x00" * (size - len(buffer.data)))


# Disable warnings for the non-standard methods names we use to match the sequences function
# names, since introspection is used to look up functions.
# pylint: disable=invalid_name
Expand Down Expand Up @@ -819,14 +820,14 @@ def bufferstreamout(self, id: int, offset: int, length: int, path: str, mode: in
@param path: Constant string value representing the destination to which to stream data to. \
Refer to character sequences for path/file name place holders.
@param mode: Specifies how to treat the data source in the specified mode.
- Bit 0..3: Format of the data source: 0 - Binary File
- Bit 0..3: Format of the data source: 0 - Binary File 1 - Trace Buffer
- Bit 4..7: Communication options for data sink: 0 - Overwrite 1 - Append
@param timeout: Timeout in microseconds. If 0, then synchronously wait for the operation to finish.
@return: Number of bytes streamed to data sink.
"""
fmt = mode & 0xF
if fmt not in (0,):
raise DebugSequenceRuntimeError("Only binary file streams (mode 0) are supported")
if fmt not in (0, 1):
raise DebugSequenceRuntimeError("Unsupported buffer stream output format")

buffer = self._buffer_manager.get(id, create=False)
available = len(buffer.data) - offset
Expand All @@ -836,6 +837,12 @@ def bufferstreamout(self, id: int, offset: int, length: int, path: str, mode: in
write_len = min(length, available)
data = bytes(buffer.data[offset:offset + write_len])

if fmt == 1:
try:
return self.target.write_trace_buffer(path, data)
except ValueError as err:
raise DebugSequenceRuntimeError(str(err)) from err

file_path = self._expand_path(path)
append = (mode & 0x10) != 0

Expand All @@ -861,6 +868,14 @@ def _do_write() -> None:

return write_len

def tracebufferselected(self, name: str) -> int:
"""Return whether a configured trace buffer is selected by name."""
trace_buffers = self.context.delegate.trace_buffers
if not name and len(trace_buffers) > 1:
raise DebugSequenceRuntimeError("Empty trace buffer name is ambiguous")
sink = trace_buffers.get(name)
return int(sink is not None and sink.enabled)

def runapplication(self, path: str, args: str, workdir: str, timeout: int) -> int:
"""@brief Run an external application.

Expand Down
8 changes: 4 additions & 4 deletions pyocd/gdbserver/gdbserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -425,12 +425,12 @@ def __init__(self, session, core=None):
# pylint: enable=invalid-name

def trace_flush(self) -> None:
# TraceFlush stub
pass
if self.board.target.trace_enabled:
self.board.target.trace_flush()

def trace_capture(self) -> None:
# TraceCapture stub
pass
if self.board.target.trace_enabled:
self.board.target.trace_capture()

def _init_remote_commands(self):
"""@brief Initialize the remote command processor infrastructure."""
Expand Down
Loading
Loading