1#!/usr/bin/env python3 2 3# Copyright (c) 2023 Nordic Semiconductor ASA 4# SPDX-License-Identifier: Apache-2.0 5 6import argparse 7from dataclasses import dataclass 8from pathlib import Path, PurePath 9import pykwalify.core 10import sys 11from typing import List 12import yaml 13import re 14 15try: 16 from yaml import CSafeLoader as SafeLoader 17except ImportError: 18 from yaml import SafeLoader 19 20 21SOC_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'soc-schema.yml') 22with open(SOC_SCHEMA_PATH, 'r') as f: 23 soc_schema = yaml.load(f.read(), Loader=SafeLoader) 24 25ARCH_SCHEMA_PATH = str(Path(__file__).parent / 'schemas' / 'arch-schema.yml') 26with open(ARCH_SCHEMA_PATH, 'r') as f: 27 arch_schema = yaml.load(f.read(), Loader=SafeLoader) 28 29SOC_YML = 'soc.yml' 30ARCHS_YML_PATH = PurePath('arch/archs.yml') 31 32class Systems: 33 34 def __init__(self, folder='', soc_yaml=None): 35 self._socs = [] 36 self._series = [] 37 self._families = [] 38 39 if soc_yaml is None: 40 return 41 42 try: 43 data = yaml.load(soc_yaml, Loader=SafeLoader) 44 pykwalify.core.Core(source_data=data, 45 schema_data=soc_schema).validate() 46 except (yaml.YAMLError, pykwalify.errors.SchemaError) as e: 47 sys.exit(f'ERROR: Malformed yaml {soc_yaml.as_posix()}', e) 48 49 for f in data.get('family', []): 50 family = Family(f['name'], folder, [], []) 51 for s in f.get('series', []): 52 series = Series(s['name'], folder, f['name'], []) 53 socs = [(Soc(soc['name'], 54 [c['name'] for c in soc.get('cpuclusters', [])], 55 folder, s['name'], f['name'])) 56 for soc in s.get('socs', [])] 57 series.socs.extend(socs) 58 self._series.append(series) 59 self._socs.extend(socs) 60 family.series.append(series) 61 family.socs.extend(socs) 62 socs = [(Soc(soc['name'], 63 [c['name'] for c in soc.get('cpuclusters', [])], 64 folder, None, f['name'])) 65 for soc in f.get('socs', [])] 66 self._socs.extend(socs) 67 self._families.append(family) 68 69 for s in data.get('series', []): 70 series = Series(s['name'], folder, '', []) 71 socs = [(Soc(soc['name'], 72 [c['name'] for c in soc.get('cpuclusters', [])], 73 folder, s['name'], '')) 74 for soc in s.get('socs', [])] 75 series.socs.extend(socs) 76 self._series.append(series) 77 self._socs.extend(socs) 78 79 socs = [(Soc(soc['name'], 80 [c['name'] for c in soc.get('cpuclusters', [])], 81 folder, '', '')) 82 for soc in data.get('socs', [])] 83 self._socs.extend(socs) 84 85 # Ensure that any runner configuration matches socs and cpuclusters declared in the same 86 # soc.yml file 87 if 'runners' in data and 'run_once' in data['runners']: 88 for grp in data['runners']['run_once']: 89 for item_data in data['runners']['run_once'][grp]: 90 for group in item_data['groups']: 91 for qualifiers in group['qualifiers']: 92 soc_name, *components = qualifiers.split('/') 93 found_match = False 94 95 # Allow 'ns' as final qualifier until "virtual" CPUs are ported to soc.yml 96 # https://github.com/zephyrproject-rtos/zephyr/issues/70721 97 if components and components[-1] == 'ns': 98 components.pop() 99 100 for soc in self._socs: 101 if re.match(fr'^{soc_name}$', soc.name) is not None: 102 if soc.cpuclusters and components: 103 check_string = '/'.join(components) 104 for cpucluster in soc.cpuclusters: 105 if re.match(fr'^{check_string}$', cpucluster) is not None: 106 found_match = True 107 break 108 elif not soc.cpuclusters and not components: 109 found_match = True 110 break 111 112 if found_match is False: 113 sys.exit(f'ERROR: SoC qualifier match unresolved: {qualifiers}') 114 115 @staticmethod 116 def from_file(socs_file): 117 '''Load SoCs from a soc.yml file. 118 ''' 119 try: 120 with open(socs_file, 'r') as f: 121 socs_yaml = f.read() 122 except FileNotFoundError as e: 123 sys.exit(f'ERROR: socs.yml file not found: {socs_file.as_posix()}', e) 124 125 return Systems(str(socs_file.parent), socs_yaml) 126 127 @staticmethod 128 def from_yaml(socs_yaml): 129 '''Load socs from a string with YAML contents. 130 ''' 131 return Systems('', socs_yaml) 132 133 def extend(self, systems): 134 self._families.extend(systems.get_families()) 135 self._series.extend(systems.get_series()) 136 self._socs.extend(systems.get_socs()) 137 138 def get_families(self): 139 return self._families 140 141 def get_series(self): 142 return self._series 143 144 def get_socs(self): 145 return self._socs 146 147 def get_soc(self, name): 148 try: 149 return next(s for s in self._socs if s.name == name) 150 except StopIteration: 151 sys.exit(f"ERROR: SoC '{name}' is not found, please ensure that the SoC exists " 152 f"and that soc-root containing '{name}' has been correctly defined.") 153 154 155@dataclass 156class Soc: 157 name: str 158 cpuclusters: List[str] 159 folder: str 160 series: str = '' 161 family: str = '' 162 163 164@dataclass 165class Series: 166 name: str 167 folder: str 168 family: str 169 socs: List[Soc] 170 171 172@dataclass 173class Family: 174 name: str 175 folder: str 176 series: List[Series] 177 socs: List[Soc] 178 179 180def unique_paths(paths): 181 # Using dict keys ensures both uniqueness and a deterministic order. 182 yield from dict.fromkeys(map(Path.resolve, paths)).keys() 183 184 185def find_v2_archs(args): 186 ret = {'archs': []} 187 for root in unique_paths(args.arch_roots): 188 archs_yml = root / ARCHS_YML_PATH 189 190 if Path(archs_yml).is_file(): 191 with Path(archs_yml).open('r') as f: 192 archs = yaml.load(f.read(), Loader=SafeLoader) 193 194 try: 195 pykwalify.core.Core(source_data=archs, schema_data=arch_schema).validate() 196 except pykwalify.errors.SchemaError as e: 197 sys.exit('ERROR: Malformed "build" section in file: {}\n{}' 198 .format(archs_yml.as_posix(), e)) 199 200 if args.arch is not None: 201 archs = {'archs': list(filter( 202 lambda arch: arch.get('name') == args.arch, archs['archs']))} 203 for arch in archs['archs']: 204 arch.update({'path': root / 'arch' / arch['path']}) 205 arch.update({'hwm': 'v2'}) 206 arch.update({'type': 'arch'}) 207 208 ret['archs'].extend(archs['archs']) 209 210 return ret 211 212 213def find_v2_systems(args): 214 yml_files = [] 215 systems = Systems() 216 for root in unique_paths(args.soc_roots): 217 yml_files.extend(sorted((root / 'soc').rglob(SOC_YML))) 218 219 for soc_yml in yml_files: 220 if soc_yml.is_file(): 221 systems.extend(Systems.from_file(soc_yml)) 222 223 return systems 224 225 226def parse_args(): 227 parser = argparse.ArgumentParser(allow_abbrev=False) 228 add_args(parser) 229 return parser.parse_args() 230 231 232def add_args(parser): 233 default_fmt = '{name}' 234 235 parser.add_argument("--soc-root", dest='soc_roots', default=[], 236 type=Path, action='append', 237 help='add a SoC root, may be given more than once') 238 parser.add_argument("--soc", default=None, help='lookup the specific soc') 239 parser.add_argument("--soc-series", default=None, help='lookup the specific soc series') 240 parser.add_argument("--soc-family", default=None, help='lookup the specific family') 241 parser.add_argument("--socs", action='store_true', help='lookup all socs') 242 parser.add_argument("--arch-root", dest='arch_roots', default=[], 243 type=Path, action='append', 244 help='add a arch root, may be given more than once') 245 parser.add_argument("--arch", default=None, help='lookup the specific arch') 246 parser.add_argument("--archs", action='store_true', help='lookup all archs') 247 parser.add_argument("--format", default=default_fmt, 248 help='''Format string to use to list each soc.''') 249 parser.add_argument("--cmakeformat", default=None, 250 help='''CMake format string to use to list each arch/soc.''') 251 252 253def dump_v2_archs(args): 254 archs = find_v2_archs(args) 255 256 for arch in archs['archs']: 257 if args.cmakeformat is not None: 258 info = args.cmakeformat.format( 259 TYPE='TYPE;' + arch['type'], 260 NAME='NAME;' + arch['name'], 261 DIR='DIR;' + str(arch['path'].as_posix()), 262 HWM='HWM;' + arch['hwm'], 263 # Below is non exising for arch but is defined here to support 264 # common formatting string. 265 SERIES='', 266 FAMILY='', 267 ARCH='', 268 VENDOR='' 269 ) 270 else: 271 info = args.format.format( 272 type=arch.get('type'), 273 name=arch.get('name'), 274 dir=arch.get('path'), 275 hwm=arch.get('hwm'), 276 # Below is non exising for arch but is defined here to support 277 # common formatting string. 278 series='', 279 family='', 280 arch='', 281 vendor='' 282 ) 283 284 print(info) 285 286 287def dump_v2_system(args, type, system): 288 if args.cmakeformat is not None: 289 info = args.cmakeformat.format( 290 TYPE='TYPE;' + type, 291 NAME='NAME;' + system.name, 292 DIR='DIR;' + Path(system.folder).as_posix(), 293 HWM='HWM;' + 'v2' 294 ) 295 else: 296 info = args.format.format( 297 type=type, 298 name=system.name, 299 dir=system.folder, 300 hwm='v2' 301 ) 302 303 print(info) 304 305 306def dump_v2_systems(args): 307 systems = find_v2_systems(args) 308 309 for f in systems.get_families(): 310 dump_v2_system(args, 'family', f) 311 312 for s in systems.get_series(): 313 dump_v2_system(args, 'series', s) 314 315 for s in systems.get_socs(): 316 dump_v2_system(args, 'soc', s) 317 318 319if __name__ == '__main__': 320 args = parse_args() 321 if any([args.socs, args.soc, args.soc_series, args.soc_family]): 322 dump_v2_systems(args) 323 if args.archs or args.arch is not None: 324 dump_v2_archs(args) 325