@@ -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