Skip to content

Commit c8395b2

Browse files
authored
cbuild-run: add telnet node processing (#1877)
- mode: by default select mode 'off' for all cores, if no 'pname' node is present the configuration is global - replace start_pname property with debugger node access - update _get_server_ports method
1 parent e41ab11 commit c8395b2

File tree

3 files changed

+148
-38
lines changed

3 files changed

+148
-38
lines changed

pyocd/core/options.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,12 @@ class OptionInfo(NamedTuple):
209209
"List of TCP ports for the gdbserver."),
210210
OptionInfo('cbuild_run.telnet_ports', tuple, None,
211211
"List of TCP ports for telnet server."),
212+
OptionInfo('cbuild_run.telnet_modes', tuple, None,
213+
"List of telnet modes for each core."),
214+
OptionInfo('cbuild_run.telnet_files_in', tuple, None,
215+
"List of telnet input file paths for each core."),
216+
OptionInfo('cbuild_run.telnet_files_out', tuple, None,
217+
"List of telnet output file paths for each core."),
212218
]
213219

214220
## @brief The runtime dictionary of options.

pyocd/core/session.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -287,19 +287,17 @@ def _get_cbuild_run_config(self, command: Optional[str]) -> Dict[str, Any]:
287287

288288
# Only set gdbserver_port if it wasn't already set in options (command line).
289289
if not self.options.is_set('gdbserver_port'):
290-
gbdserver_ports = self.cbuild_run.gdbserver_port
291-
if isinstance(gbdserver_ports, int):
292-
debugger_options['gdbserver_port'] = gbdserver_ports
293-
else:
294-
debugger_options['cbuild_run.gdbserver_ports'] = gbdserver_ports
290+
debugger_options['cbuild_run.gdbserver_ports'] = self.cbuild_run.gdbserver_ports
295291

296292
# Only set telnet_port if it wasn't already set in options (command line).
297293
if not self.options.is_set('telnet_port'):
298-
telnet_ports = self.cbuild_run.telnet_port
299-
if isinstance(telnet_ports, int):
300-
debugger_options['telnet_port'] = telnet_ports
301-
else:
302-
debugger_options['cbuild_run.telnet_ports'] = telnet_ports
294+
debugger_options['cbuild_run.telnet_ports'] = self.cbuild_run.telnet_ports
295+
296+
if not self.options.is_set('semihost_console_type'):
297+
debugger_options['cbuild_run.telnet_modes'] = self.cbuild_run.telnet_modes
298+
telnet_files = self.cbuild_run.telnet_files
299+
debugger_options['cbuild_run.telnet_files_in'] = telnet_files.get('in')
300+
debugger_options['cbuild_run.telnet_files_out'] = telnet_files.get('out')
303301

304302
# Set reset types for load operations.
305303
debugger_options['load.pre_reset'] = self.cbuild_run.pre_reset

pyocd/target/pack/cbuild_run.py

Lines changed: 134 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ class CbuildRun:
190190
def __init__(self, yml_path: str) -> None:
191191
"""@brief Reads a .cbuild-run.yml file and validates its content."""
192192
self._data: Dict[str, Any] = {}
193+
self._cbuild_name: str = ""
193194
self._device: Optional[str] = None
194195
self._vendor: Optional[str] = None
195196
self._vars: Optional[Dict[str, str]] = None
@@ -204,6 +205,7 @@ def __init__(self, yml_path: str) -> None:
204205
self._built_apid_map: bool = False
205206
self._processors_map: Dict[str, ProcessorInfo] = {}
206207
self._processors_ap_map: Dict[APAddressBase, ProcessorInfo] = {}
208+
self._sorted_processors: List[ProcessorInfo] = []
207209
self._use_default_memory_map: bool = True
208210
self._system_resources: Optional[Dict[str, list]] = None
209211
self._system_descriptions: Optional[List[dict]] = None
@@ -217,6 +219,7 @@ def __init__(self, yml_path: str) -> None:
217219
yml_data = yaml.safe_load(yml_file)
218220
if 'cbuild-run' in yml_data:
219221
self._data = yml_data['cbuild-run']
222+
self._cbuild_name = yml_file_path.stem.split('.cbuild-run')[0]
220223
# Ensure CMSIS_PACK_ROOT is set
221224
self._cmsis_pack_root()
222225
else:
@@ -456,6 +459,13 @@ def processors_ap_map(self) -> Dict[APAddressBase, ProcessorInfo]:
456459
}
457460
return self._processors_ap_map
458461

462+
@property
463+
def sorted_processors(self) -> List[ProcessorInfo]:
464+
"""@brief List of processors sorted by AP address."""
465+
if not self._sorted_processors:
466+
self._sorted_processors = sorted(self.processors_ap_map.values(), key=lambda p: p.ap_address.address)
467+
return self._sorted_processors
468+
459469
@property
460470
def programming(self) -> List[dict]:
461471
"""@brief Programming section of cbuild-run."""
@@ -488,14 +498,6 @@ def debugger_protocol(self) -> Optional[str]:
488498
LOG.debug("Debugger protocol: %s", _debugger_protocol)
489499
return _debugger_protocol
490500

491-
@property
492-
def start_pname(self) -> Optional[str]:
493-
"""@brief Selected start processor name."""
494-
_start_pname = self.debugger.get('start-pname')
495-
if _start_pname is not None:
496-
LOG.info("start-pname: %s", _start_pname)
497-
return _start_pname
498-
499501
@property
500502
def system_resources(self) -> Dict[str, list]:
501503
"""@brief System Resources section of cbuild-run."""
@@ -533,12 +535,12 @@ def dormant(self) -> bool:
533535
@property
534536
def primary_core(self) -> Optional[int]:
535537
"""@brief Primary core number from debugger settings."""
536-
_primary_core = None
537-
_start_pname = self.start_pname
538-
for i, proc_info in enumerate(sorted(self.processors_ap_map.values(), key=lambda p: p.ap_address.address)):
539-
if proc_info.name == _start_pname:
540-
_primary_core = i
541-
return _primary_core
538+
start_pname = self.debugger.get('start-pname')
539+
if start_pname is not None:
540+
LOG.info("start-pname: %s", start_pname)
541+
return next((i for i, proc_info in enumerate(self.sorted_processors)
542+
if proc_info.name == start_pname), None)
543+
return None
542544

543545
@property
544546
def pre_load_halt(self) -> bool:
@@ -575,19 +577,115 @@ def connect_mode(self) -> str:
575577
return connect
576578

577579
@property
578-
def gdbserver_port(self) -> Optional[Union[int, Tuple]]:
580+
def gdbserver_ports(self) -> Optional[Tuple]:
579581
"""@brief GDB server port assignments from debugger section.
580582
The method will not be called frequently, so performance is not critical.
581583
"""
582584
return self._get_server_ports('gdbserver')
583585

584586
@property
585-
def telnet_port(self) -> Optional[Union[int, Tuple]]:
587+
def telnet_ports(self) -> Optional[Tuple]:
586588
"""@brief Telnet server port assignments from debugger section.
587589
The method will not be called frequently, so performance is not critical.
588590
"""
589591
return self._get_server_ports('telnet')
590592

593+
@property
594+
def telnet_modes(self) -> Tuple:
595+
"""@brief Telnet server mode assignments from debugger section.
596+
The method will not be called frequently, so performance is not critical.
597+
"""
598+
SUPPORTED_MODES = { 'off', 'telnet', 'file', 'console' }
599+
MODE_ALIASES = { False: 'off',
600+
'monitor': 'telnet',
601+
'server': 'telnet'
602+
}
603+
# Get telnet configuration from debugger section
604+
telnet_config = self.debugger.get('telnet', [])
605+
valid_config = any('mode' in t for t in telnet_config)
606+
# Determine global mode if specified, default to 'off' otherwise
607+
global_mode = next((t.get('mode') for t in telnet_config if 'pname' not in t), 'off')
608+
global_mode = MODE_ALIASES.get(global_mode, global_mode)
609+
# Build list of telnet modes for each core
610+
telnet_modes = []
611+
for core in self.sorted_processors:
612+
mode = next((t.get('mode') for t in telnet_config if t.get('pname') == core.name), global_mode)
613+
mode = MODE_ALIASES.get(mode, mode)
614+
if mode not in SUPPORTED_MODES:
615+
if valid_config:
616+
LOG.warning("Invalid telnet mode '%s' for core '%s' in cbuild-run, defaulting to '%s'",
617+
mode, core.name, global_mode)
618+
mode = global_mode
619+
telnet_modes.append(mode)
620+
621+
return tuple(telnet_modes)
622+
623+
@property
624+
def telnet_files(self) -> Dict[str, Optional[Tuple]]:
625+
"""@brief Telnet file path assignments from debugger section.
626+
The method will not be called frequently, so performance is not critical.
627+
"""
628+
# Get telnet configuration from debugger section
629+
telnet_config = self.debugger.get('telnet', [])
630+
telnet_modes = self.telnet_modes
631+
632+
if not any(mode == 'file' for mode in telnet_modes):
633+
# No telnet file paths needed
634+
return {'in': None, 'out': None}
635+
636+
def _resolve_path(file_path: Optional[str], strict: bool = False) -> Optional[str]:
637+
if file_path is None:
638+
return None
639+
try:
640+
return str(Path(os.path.expandvars(str(file_path))).expanduser().resolve(strict=strict))
641+
except FileNotFoundError:
642+
if strict:
643+
LOG.warning("Telnet file '%s' not found, using default value", file_path)
644+
return None
645+
except OSError:
646+
return None
647+
648+
in_files = []
649+
out_files = []
650+
651+
# Per pname configuration
652+
config_by_pname = {t['pname']: t for t in telnet_config if 'pname' in t}
653+
654+
if config_by_pname:
655+
# Build config per pname
656+
for proc_info, mode in zip(self.sorted_processors, telnet_modes):
657+
if mode != 'file':
658+
in_files.append(None)
659+
out_files.append(None)
660+
continue
661+
662+
config = config_by_pname.get(proc_info.name, {})
663+
in_files.append(_resolve_path(config.get('file-in'), strict=True))
664+
out_file = _resolve_path(config.get('file-out'))
665+
if out_file is None:
666+
out_file = f"{self._cbuild_name}.{proc_info.name}.out"
667+
out_files.append(out_file)
668+
else:
669+
config = next((t for t in telnet_config if t.get('mode') == 'file'), None)
670+
if config is not None:
671+
if len(self.sorted_processors) > 1:
672+
LOG.warning("Ignoring invalid telnet file configuration for multicore target in cbuild-run")
673+
for proc_info, mode in zip(self.sorted_processors, telnet_modes):
674+
in_files.append(None)
675+
if mode != 'file':
676+
out_files.append(None)
677+
else:
678+
out_files.append(f"{self._cbuild_name}.{proc_info.name}.out")
679+
else:
680+
in_files.append(_resolve_path(config.get('file-in'), strict=True))
681+
out_file = _resolve_path(config.get('file-out'))
682+
if out_file is None:
683+
out_file = f"{self._cbuild_name}.out"
684+
out_files.append(out_file)
685+
686+
return {'in': tuple(in_files) if any(in_files) else None,
687+
'out': tuple(out_files) if any(out_files) else None}
688+
591689
def populate_target(self, target: Optional[str] = None) -> None:
592690
"""@brief Generates and populates the target defined by the .cbuild-run.yml file."""
593691
if target is None:
@@ -612,20 +710,28 @@ def populate_target(self, target: Optional[str] = None) -> None:
612710
})
613711
TARGET[target] = tgt
614712

615-
def _get_server_ports(self, server_type: str) -> Optional[Union[int, Tuple]]:
713+
def _get_server_ports(self, server_type: str) -> Optional[Tuple]:
616714
"""@brief Generic method to get server port assignments from debugger section."""
617-
server_map = self.debugger.get(server_type, [])
618-
sorted_processors = sorted(self.processors_ap_map.values(), key=lambda p: p.ap_address.address)
619-
has_pname = any('pname' in server for server in server_map)
620-
621-
if has_pname:
622-
ports = tuple(
623-
next((s['port'] for s in server_map if s.get('pname') == proc_info.name), None)
624-
for proc_info in sorted_processors
625-
)
715+
server_config = self.debugger.get(server_type, [])
716+
if not server_config:
717+
# No server configuration provided.
718+
return None
719+
720+
ports = []
721+
if any('pname' in server for server in server_config):
722+
for proc_info in self.sorted_processors:
723+
ports.append(next((s.get('port') for s in server_config if s.get('pname') == proc_info.name), None))
626724
else:
627-
ports = next((s['port'] for s in server_map), None)
628-
return ports
725+
port = next((s.get('port') for s in server_config), None)
726+
if port is not None:
727+
if len(self.sorted_processors) > 1:
728+
LOG.warning("Ignoring invalid %s port configuration for multicore target in cbuild-run", server_type)
729+
return None
730+
ports.append(port)
731+
else:
732+
return None
733+
734+
return tuple(ports)
629735

630736
def _get_memory_to_process(self) -> List[dict]:
631737
DEFAULT_MEMORY_MAP = sorted([

0 commit comments

Comments
 (0)