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: 11 additions & 11 deletions pyocd/core/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ class OptionInfo(NamedTuple):
"Duration in seconds that a failed target status check will be retried before an error is raised. "
"Only applies while the target is running after a resume operation in the debugger and pyOCD is waiting "
"for it to halt again."),
OptionInfo('gdbserver_port', int, 3333,
OptionInfo('gdbserver_port', (int, tuple), 3333,
"Base TCP port for the gdbserver."),
OptionInfo('persist', bool, False,
"If True, the GDB server will not exit after GDB disconnects."),
Expand Down Expand Up @@ -194,7 +194,7 @@ class OptionInfo(NamedTuple):
"Enable flag for the raw SWV stream server."),
OptionInfo('swv_raw_port', int, 3443,
"TCP port number for the raw SWV stream server."),
OptionInfo('telnet_port', int, 4444,
OptionInfo('telnet_port', (int, tuple), 4444,
"Base TCP port number for the semihosting telnet server."),
OptionInfo('vector_catch', str, 'h',
"Enable vector catch sources."),
Expand All @@ -205,20 +205,20 @@ class OptionInfo(NamedTuple):
"Replace software breakpoints with hardware breakpoints."),

# Internal cbuild-run session options
OptionInfo('cbuild_run.gdbserver_port', tuple, None,
"List of TCP ports for the gdbserver."),
OptionInfo('cbuild_run.telnet_port', tuple, None,
"List of TCP ports for telnet server."),
OptionInfo('cbuild_run.telnet_mode', tuple, None,
OptionInfo('telnet_mode', (str, tuple), None,
"List of telnet modes for each core."),
OptionInfo('cbuild_run.telnet_file_in', tuple, None,
OptionInfo('telnet_file_in', (str, tuple), None,
"List of telnet input file paths for each core."),
OptionInfo('cbuild_run.telnet_file_out', tuple, None,
OptionInfo('telnet_file_out', (str, tuple), None,
"List of telnet output file paths for each core."),
OptionInfo('rtt', tuple, None,
"List of RTT configurations for each core."),
OptionInfo('systemview', tuple, None,
"SystemView configuration."),
OptionInfo('systemview_file', str, None,
"SystemView output file path."),
OptionInfo('systemview_auto_start', bool, None,
"Enable automatic start of SystemView."),
OptionInfo('systemview_auto_stop', bool, None,
"Enable automatic stop of SystemView."),
]

## @brief The runtime dictionary of options.
Expand Down
23 changes: 9 additions & 14 deletions pyocd/core/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,22 +285,17 @@ def _get_cbuild_run_config(self, command: Optional[str]) -> Dict[str, Any]:
connect_mode = 'halt'
debugger_options['connect_mode'] = connect_mode

# Only set gdbserver_port if it wasn't already set in options (command line).
if not self.options.is_set('gdbserver_port'):
debugger_options['cbuild_run.gdbserver_port'] = self.cbuild_run.gdbserver_port

# Only set telnet_port if it wasn't already set in options (command line).
if not self.options.is_set('telnet_port'):
debugger_options['cbuild_run.telnet_port'] = self.cbuild_run.telnet_port

if not self.options.is_set('semihost_console_type'):
debugger_options['cbuild_run.telnet_mode'] = self.cbuild_run.telnet_mode
telnet_file = self.cbuild_run.telnet_file
debugger_options['cbuild_run.telnet_file_in'] = telnet_file.get('in')
debugger_options['cbuild_run.telnet_file_out'] = telnet_file.get('out')
debugger_options['gdbserver_port'] = self.cbuild_run.gdbserver_port
debugger_options['telnet_port'] = self.cbuild_run.telnet_port
debugger_options['telnet_mode'] = self.cbuild_run.telnet_mode
telnet_file = self.cbuild_run.telnet_file
debugger_options['telnet_file_in'] = telnet_file.get('in')
debugger_options['telnet_file_out'] = telnet_file.get('out')

debugger_options['rtt'] = self.cbuild_run.rtt
debugger_options['systemview'] = self.cbuild_run.systemview
debugger_options['systemview_file'] = self.cbuild_run.systemview_file
debugger_options['systemview_auto_start'] = self.cbuild_run.systemview_auto_start
debugger_options['systemview_auto_stop'] = self.cbuild_run.systemview_auto_stop

# Set reset types for load operations.
debugger_options['load.pre_reset'] = self.cbuild_run.pre_reset
Expand Down
12 changes: 6 additions & 6 deletions pyocd/gdbserver/gdbserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -301,13 +301,13 @@ def __init__(self, session, core=None):
self.target = self.board.target.cores[core]
self.name = "gdb-server-core%d" % self.core

if session.options.is_set('cbuild_run.gdbserver_port'):
# Per-core gdbserver ports configured.
_port = session.options.get('cbuild_run.gdbserver_port')[self.core]
# Check if no port was configured for this core, use 0 in that case.
self.port = _port if _port is not None else 0
_ports = session.options.get('gdbserver_port')
if isinstance(_ports, (list, tuple)):
if len(_ports) <= self.core:
raise ValueError(f"GDB server for core {self.core} requires a port number in the 'gdbserver_port' list")
self.port = _ports[self.core]
else:
self.port = session.options.get('gdbserver_port')
self.port = _ports
if self.port != 0:
self.port += self.core

Expand Down
32 changes: 24 additions & 8 deletions pyocd/target/pack/cbuild_run.py
Original file line number Diff line number Diff line change
Expand Up @@ -646,11 +646,10 @@ def telnet_mode(self) -> Tuple:
"""@brief Telnet server mode assignments from debugger section.
The method will not be called frequently, so performance is not critical.
"""
SUPPORTED_MODES = { 'off', 'telnet', 'file', 'console' }
SUPPORTED_MODES = { 'off', 'server', 'file', 'console' }
MODE_ALIASES = { False: 'off',
'monitor': 'telnet',
'server': 'telnet'
}
'monitor': 'server'
}
# Get telnet configuration from debugger section
telnet_config = self.debugger.get('telnet') or []
valid_config = any('mode' in t for t in telnet_config)
Expand Down Expand Up @@ -753,15 +752,32 @@ def rtt(self) -> Optional[Tuple]:
return tuple(rtt) if rtt else None

@property
def systemview(self) -> Optional[Tuple]:
def systemview_file(self) -> Optional[str]:
rtt = self.debugger.get('rtt') or []
if not rtt:
return None
sv = self.debugger.get('systemview') or {}
if sv.get('file') is None:
file = sv.get('file')
if file is None:
# Set default systemview file name if not provided
sv['file'] = self._cbuild_run_path.split('.cbuild-run')[0] + '.SVDat'
return sv if sv else None
file = self._cbuild_run_path.split('.cbuild-run')[0] + '.SVDat'
return file if file else None

@property
def systemview_auto_start(self) -> Optional[bool]:
rtt = self.debugger.get('rtt') or []
if not rtt:
return None
sv = self.debugger.get('systemview') or {}
return sv.get('auto-start')

@property
def systemview_auto_stop(self) -> Optional[bool]:
rtt = self.debugger.get('rtt') or []
if not rtt:
return None
sv = self.debugger.get('systemview') or {}
return sv.get('auto-stop')

def populate_target(self, target: Optional[str] = None) -> None:
"""@brief Generates and populates the target defined by the .cbuild-run.yml file."""
Expand Down
64 changes: 58 additions & 6 deletions pyocd/utility/cmdline.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@
import logging
from typing import (Any, Callable, Dict, Iterable, List, Optional, Tuple, Union, cast)

import yaml

from ..core.target import Target
from ..core.options import OPTIONS_INFO
from ..utility.compatibility import to_str_safe
Expand Down Expand Up @@ -184,11 +186,27 @@ def convert_one_session_option(name: str, value: Optional[str]) -> Tuple[str, An
# Default result; unset option value.
result = None

# Extract the option's type. If its type is a tuple of types, then take the first type.
# Multi-type options: use YAML to auto-detect the value type, then validate
# against all accepted types.
if isinstance(info.type, tuple):
option_type = cast(tuple, info.type)[0]
else:
option_type = info.type
if value is None:
LOG.warning("non-boolean option '%s' requires a value", name)
return name, None
try:
parsed = yaml.safe_load(value)
except yaml.YAMLError as e:
LOG.warning("invalid value for option '%s': %s", name, e)
return name, None
# YAML produces list; normalize to tuple if tuple is an accepted type.
if isinstance(parsed, list) and tuple in info.type:
parsed = tuple(parsed)
# Validate against all accepted types.
if isinstance(parsed, info.type):
return name, parsed
LOG.warning("invalid value for option '%s'", name)
return name, None

option_type = info.type

# Handle bool options without a value specially.
if value is None:
Expand Down Expand Up @@ -216,22 +234,56 @@ def convert_one_session_option(name: str, value: Optional[str]) -> Tuple[str, An
result = float(value)
except ValueError:
LOG.warning("invalid value for option '%s'", name)
elif issubclass(option_type, (tuple, list)):
# Container-type options (e.g. 'rtt', 'systemview') accept YAML flow sequences.
try:
parsed = yaml.safe_load(value)
except yaml.YAMLError as e:
LOG.warning("invalid value for option '%s': %s", name, e)
else:
if not isinstance(parsed, (list, tuple)):
LOG.warning("expected a sequence value for option '%s'", name)
else:
result = tuple(parsed) if issubclass(option_type, tuple) else list(parsed)
elif issubclass(option_type, dict):
# Dict-type options accept a YAML flow mapping
try:
parsed = yaml.safe_load(value)
except yaml.YAMLError as e:
LOG.warning("invalid value for option '%s': %s", name, e)
else:
if not isinstance(parsed, dict):
LOG.warning("expected a mapping value for option '%s'", name)
else:
result = parsed
else:
result = value

return name, result

def convert_session_options(option_list: Iterable[str]) -> Dict[str, Any]:
"""@brief Convert a list of session option settings to a dictionary."""
"""@brief Convert a list of session option settings to a dictionary.

Each entry in ``option_list`` is a string in one of the following formats:

- ``KEY=VALUE`` — Standard key/value pair: ``target=stm32f103rc``
- ``KEY`` — Boolean flag (value defaults to ``True``): ``persist``
- ``no-KEY`` — Boolean flag negation (value defaults to ``False``): ``no-persist``

For container-typed options (``tuple``, ``list``, ``dict``) the value is parsed
as YAML, which accepts both YAML flow style and standard JSON. This allows
hex integers (``0x...``), unquoted strings, and booleans without quoting.
"""
options = {}
if option_list is not None:
for o in option_list:
stripped = o.strip()
if '=' in o:
name, value = o.split('=', 1)
name = name.strip().lower()
value = value.strip()
else:
name = o.strip().lower()
name = stripped.lower()
value = None

name, value = convert_one_session_option(name, value)
Expand Down
72 changes: 50 additions & 22 deletions pyocd/utility/stdio.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,14 +58,13 @@ class StdioTelnet(StdioBase):
"""STDIO backend that uses a telnet server for reading from and writing to stdin/stdout."""

def __init__(self, session: Session, core: int = 0) -> None:
if session.options.is_set('cbuild_run.telnet_port'):
# Per-core telnet ports configured.
telnet_port = session.options.get('cbuild_run.telnet_port')[core]
if telnet_port is None:
LOG.info("Telnet port for core %d is not specified and will be auto-assigned", core)
telnet_port = 0
_telnet_ports = session.options.get('telnet_port')
if isinstance(_telnet_ports, (list, tuple)):
if len(_telnet_ports) <= core or _telnet_ports[core] is None:
raise ValueError(f"STDIO: telnet for core {core} requires a port number in the 'telnet_port' list")
telnet_port = _telnet_ports[core]
else:
telnet_port = session.options.get('telnet_port')
telnet_port = _telnet_ports
if telnet_port != 0:
telnet_port += core
serve_local_only = session.options.get('serve_local_only')
Expand Down Expand Up @@ -112,24 +111,47 @@ def shutdown(self) -> None:

@property
def info(self) -> str:
return f"telnet (port: {self._server.port})"
return f"server (port: {self._server.port})"

class StdioFile(StdioBase):
"""STDIO backend that reads from and writes to files."""

def __init__(self, session: Session, core: int = 0) -> None:

is_multi_core = len(session.board.target.cores) > 1
# Get file paths from session options
if session.options.is_set('cbuild_run.telnet_file_out'):
telnet_file_out = session.options.get('cbuild_run.telnet_file_out')[core]
if telnet_file_out is None:
raise ValueError(f"STDIO file for core {core} requires a valid output file path")
if session.options.is_set('telnet_file_out'):
_telnet_file_out = session.options.get('telnet_file_out')
if isinstance(_telnet_file_out, (list, tuple)):
if len(_telnet_file_out) <= core or _telnet_file_out[core] is None:
raise ValueError(f"STDIO file for core {core} requires a valid output file path")
telnet_file_out = _telnet_file_out[core]
else:
if is_multi_core:
root, ext = os.path.splitext(_telnet_file_out)
_telnet_file_out = root + f"_{core}" + ext
telnet_file_out = _telnet_file_out
else:
raise ValueError(f"STDIO file for core {core} requires a valid output file path")

if session.options.is_set('cbuild_run.telnet_file_in'):
telnet_file_in = session.options.get('cbuild_run.telnet_file_in')[core]
# Default
target_type = session.board.target_type
telnet_file_out = f"{target_type}_{core}.out" if is_multi_core else f"{target_type}.out"

telnet_file_in = None
if session.options.is_set('telnet_file_in'):
_telnet_file_in = session.options.get('telnet_file_in')
if isinstance(_telnet_file_in, (list, tuple)):
if len(_telnet_file_in) <= core or _telnet_file_in[core] is None:
LOG.debug("No input file configured for core %d", core)
telnet_file_in = _telnet_file_in[core]
else:
if is_multi_core:
root, ext = os.path.splitext(_telnet_file_in)
_telnet_file_in = root + f"_{core}" + ext
telnet_file_in = _telnet_file_in
else:
telnet_file_in = None
# Default
target_type = session.board.target_type
telnet_file_in = f"{target_type}_{core}.in" if is_multi_core else f"{target_type}.in"

# Check if the folder exists for input/output files
dir_out = os.path.dirname(telnet_file_out)
Expand Down Expand Up @@ -271,8 +293,10 @@ def info(self) -> str:
return "console"

# Backend mapping
_BACKEND_CLASSES: Dict[str, Type[StdioBase]] = {
_BACKEND_CLASSES: Dict[str or bool, Type[StdioBase]] = {
False: StdioOff,
"off": StdioOff,
"server": StdioTelnet,
"telnet": StdioTelnet,
"file": StdioFile,
"console": StdioConsole
Expand All @@ -284,10 +308,14 @@ class StdioHandler(StdioBase):
"""

def __init__(self, session: Session, core: int = 0, eot_enabled: bool = False) -> None:

if session.options.is_set('cbuild_run.telnet_mode'):
# Per-core telnet modes configured.
stdio_mode = session.options.get('cbuild_run.telnet_mode')[core]
if session.options.is_set('telnet_mode'):
_telnet_mode = session.options.get('telnet_mode')
if isinstance(_telnet_mode, (list, tuple)):
if len(_telnet_mode) <= core or _telnet_mode[core] is None:
raise ValueError(f"STDIO mode for core {core} requires a 'telnet_mode'")
stdio_mode = _telnet_mode[core]
else:
stdio_mode = _telnet_mode
else:
stdio_mode = session.options.get('semihost_console_type')

Expand Down
Loading
Loading