1#-------------------------------------------------------------------------------
2# Copyright (c) 2018-2023, Arm Limited. All rights reserved.
3# Copyright (c) 2022 Cypress Semiconductor Corporation (an Infineon company)
4# or an affiliate of Cypress Semiconductor Corporation. All rights reserved.
5#
6# SPDX-License-Identifier: BSD-3-Clause
7#
8#-------------------------------------------------------------------------------
9
10import os
11import io
12import re
13import sys
14import argparse
15import logging
16from jinja2 import Environment, BaseLoader, select_autoescape, TemplateNotFound
17
18try:
19    import yaml
20except ImportError as e:
21    logging.error (str(e) + " To install it, type:")
22    logging.error ("pip install PyYAML")
23    exit(1)
24
25donotedit_warning = \
26                    '  WARNING: This is an auto-generated file. Do not edit!  '
27
28TFM_ROOT_DIR = os.path.join(sys.path[0], '..')
29OUT_DIR = None # The root directory that files are generated to
30
31# PID[0, TFM_PID_BASE - 1] are reserved for TF-M SPM and test usages
32TFM_PID_BASE = 256
33
34# variable for checking for duplicated sid
35sid_list = []
36
37# Summary of manifest attributes defined by FFM for use in the Secure Partition manifest file.
38ffm_manifest_attributes = ['psa_framework_version', 'name', 'type', 'priority', 'model', 'entry_point', \
39'stack_size', 'description', 'entry_init', 'heap_size', 'mmio_regions', 'services', 'irqs', 'dependencies']
40
41class TemplateLoader(BaseLoader):
42    """
43    Template loader class.
44
45    An instance of this class is passed to the template engine. It is
46    responsible for reading the template file
47    """
48    def __init__(self):
49        pass
50
51    def get_source(self, environment, template):
52        """
53        This function reads the template files.
54        For detailed documentation see:
55        http://jinja.pocoo.org/docs/2.10/api/#jinja2.BaseLoader.get_source
56
57        Please note that this function always return 'false' as 'uptodate'
58        value, so the output file will always be generated.
59        """
60        if not os.path.isfile(template):
61            raise TemplateNotFound(template)
62        with open(template) as f:
63            source = f.read()
64        return source, template, False
65
66def parse_configurations(file_paths):
67    """
68    Parses the given config files and return a dict whose key-values are build
69    configurations and their values.
70
71    Valid configurations should be in the format of:
72    "#define VAR [...]" in a single line.
73    The value of the config is optional.
74    """
75    configurations = {}
76
77    lines = []
78    for file in file_paths:
79        with open(file, 'r') as config_file:
80            lines += config_file.readlines()
81
82    for line in lines:
83        if not line.startswith('#define'):
84            continue
85
86        line = line.rstrip('\r\n')
87        line_items = line.split(maxsplit=2)
88        if len(line_items) == 3:
89            configurations[line_items[1]] = line_items[2]
90        elif len(line_items) == 2:
91            configurations[line_items[1]] = ''
92
93    logging.debug(configurations)
94
95    return configurations
96
97def manifest_validation(manifest, pid):
98    """
99    This function validates FF-M compliance for partition manifest, and sets
100    default values for optional attributes.
101    The validation is skipped for TF-M specific Partitions (PID < TFM_PID_BASE).
102    """
103
104    service_list = manifest.get('services', [])
105    irq_list     = manifest.get('irqs', [])
106
107    # "psa_framework_version" validation
108    if manifest['psa_framework_version'] not in [1.0, 1.1]:
109        raise Exception('Invalid psa_framework_version of {}'.format(manifest['name']))
110
111    # "type" validation
112    if manifest['type'] not in ['PSA-ROT', 'APPLICATION-ROT']:
113        raise Exception('Invalid type of {}'.format(manifest['name']))
114
115    # "priority" validation
116    if manifest['priority'] not in ['HIGH', 'NORMAL', 'LOW']:
117        raise Exception('Invalid priority of {}'.format(manifest['name']))
118
119    if 'ns_agent' not in manifest:
120        manifest['ns_agent'] = False
121
122    # Every PSA Partition must have at least either a secure service or an IRQ
123    if (pid == None or pid >= TFM_PID_BASE) \
124       and len(service_list) == 0 and len(irq_list) == 0:
125        raise Exception('{} must declare at least either a secure service or an IRQ!'
126                        .format(manifest['name']))
127
128    if manifest['psa_framework_version'] == 1.0:
129        # For 1.0 Partition, the model is IPC
130        manifest['model'] = 'IPC'
131
132    # "model" validation:
133    model = manifest.get('model', None)
134    if model == None:
135        raise Exception('{} is missing the "model" attribute'.format(manifest['name']))
136
137    # Assign a unified 'entry' for templates
138    if model == 'IPC':
139        # entry_point is mandatory for IPC Partitions
140        if 'entry_point' not in manifest.keys():
141            raise Exception('{} is missing the "entry_point" attribute'.format(manifest['name']))
142        manifest['entry'] = manifest['entry_point']
143    elif model == 'SFN':
144        if 'entry_init' in manifest.keys():
145            manifest['entry'] = manifest['entry_init']
146        else:
147            manifest['entry'] = 0
148    else:
149        raise Exception('Invalid "model" of {}'.format(manifest['name']))
150
151    # Service FF-M manifest validation
152    for service in service_list:
153        if manifest['psa_framework_version'] == 1.0:
154            service['connection_based'] = True
155        elif 'connection_based' not in service:
156            raise Exception("'connection_based' is mandatory in FF-M 1.1 service!")
157
158        if 'version' not in service.keys():
159            service['version'] = 1
160        if 'version_policy' not in service.keys():
161            service['version_policy'] = 'STRICT'
162
163        # SID duplication check
164        if service['sid'] in sid_list:
165            raise Exception('Service ID: {} has duplications!'.format(service['sid']))
166        else:
167            sid_list.append(service['sid'])
168
169    return manifest
170
171def check_circular_dependency(partitions, service_partition_map):
172    """
173    This function detects if there is any circular partition dependency chain.
174    If a circular dependency is detected, the script exits with error.
175
176    Inputs:
177        - partitions:            dict of partition manifests
178        - service_partition_map: map between services and their owner Partitions
179    """
180
181    dependency_table = {}
182    for partition in partitions:
183        manifest = partition['manifest']
184        dependencies = manifest['dependencies'].copy() \
185                       if 'dependencies' in manifest else []
186        dependencies += manifest['weak_dependencies'].copy() \
187                        if 'weak_dependencies' in manifest else []
188        dependency_table[manifest['name']] = {
189            'dependencies': [service_partition_map[dependency]
190                             for dependency in dependencies
191                             if dependency in service_partition_map],
192            'validated': False
193        }
194
195    for partition in dependency_table.keys():
196        validate_dependency_chain(partition, dependency_table, [])
197
198def validate_dependency_chain(partition,
199                              dependency_table,
200                              dependency_chain):
201    """
202    Recursively validate if the given partition and its dependencies
203    have a circular dependency with the given dependency_chain.
204    Exit with error code once any circular is detected.
205
206    Inputs:
207        - partition:        next partition to be checked
208        - dependency_table: dict of partitions and their dependencies
209        - dependency_chain: list of dependencies in current chain
210    """
211
212    dependency_chain.append(partition)
213    if partition in dependency_chain[:-1]:
214        logging.error(
215            'Circular dependency exists in chain: {}'.format(
216                ', '.join(dependency_chain)))
217        exit(1)
218    for dependency in dependency_table[partition]['dependencies']:
219        if dependency_table[dependency]['validated']:
220            continue
221        validate_dependency_chain(dependency, dependency_table, dependency_chain)
222    dependency_table[partition]['validated'] = True
223
224def manifest_attribute_check(manifest, manifest_item):
225    """
226    Check whether Non-FF-M compliant attributes are explicitly registered in manifest lists.
227
228    Inputs:
229        - manifest:        next manifest to be checked
230        - manifest_item:   the manifest items in manifest lists
231    """
232    allowed_attributes = ffm_manifest_attributes + manifest_item.get('non_ffm_attributes', [])
233    for keyword in manifest.keys():
234        if keyword not in allowed_attributes:
235            logging.error('The Non-FFM attribute {} is used by {} without registration.'.format(keyword, manifest['name']))
236            exit(1)
237
238def process_partition_manifests(manifest_lists, configs):
239    """
240    Parse the input manifest lists, check if manifest settings are valid,
241    generate the data base for generated files
242    and generate manifest header files.
243
244    Parameters
245    ----------
246    manifest_lists:
247        A list of Secure Partition manifest lists
248
249    Returns
250    -------
251    The manifest data base.
252    """
253
254    context = {}
255
256    partition_list = []
257    all_manifests = []
258    pid_list = []
259    no_pid_manifest_idx = []
260    service_partition_map = {}
261    partition_statistics = {
262        'connection_based_srv_num': 0,
263        'ipc_partitions': [],
264        'mmio_region_num': 0,
265        'flih_num': 0,
266        'slih_num': 0
267    }
268    config_impl = {
269        'CONFIG_TFM_SPM_BACKEND_SFN'              : '0',
270        'CONFIG_TFM_SPM_BACKEND_IPC'              : '0',
271        'CONFIG_TFM_CONNECTION_BASED_SERVICE_API' : '0',
272        'CONFIG_TFM_MMIO_REGION_ENABLE'           : '0',
273        'CONFIG_TFM_FLIH_API'                     : '0',
274        'CONFIG_TFM_SLIH_API'                     : '0'
275    }
276    priority_map = {
277        'LOWEST'              : '00',
278        'LOW'                 : '01',
279        'NORMAL'              : '02',
280        'HIGH'                : '03',
281        'HIGHEST'             : '04'
282    }
283
284    isolation_level = int(configs['TFM_ISOLATION_LEVEL'], base = 10)
285    backend = configs['CONFIG_TFM_SPM_BACKEND']
286
287    # Get all the manifests information as a dictionary
288    for i, item in enumerate(manifest_lists):
289        if not os.path.isfile(item):
290            logging.error('Manifest list item [{}] must be a file'.format(i))
291            exit(1)
292
293        # The manifest list file generated by configure_file()
294        with open(item) as manifest_list_yaml_file:
295            manifest_dic = yaml.safe_load(manifest_list_yaml_file)['manifest_list']
296            for dict in manifest_dic:
297                # Replace environment variables in the manifest path.
298                expanded_path = os.path.expandvars(dict['manifest']).replace('\\', '/')
299
300                # If the manifest exists relative to the manifest list, then use
301                # that. Else, either interpret it as an absolute path or one
302                # relative to the current working directory
303                path_relative_to_manifest_list = os.path.join(os.path.dirname(item), # path of manifest list
304                                                              expanded_path)
305                if os.path.isfile(path_relative_to_manifest_list):
306                    manifest_path = path_relative_to_manifest_list
307                else:
308                    manifest_path = expanded_path
309                dict['manifest'] = manifest_path
310                all_manifests.append(dict)
311
312    logging.info("------------ Display partition configuration - start ------------")
313
314    # Parse the manifests
315    for i, manifest_item in enumerate(all_manifests):
316        valid_enabled_conditions  = ['1', 'on',  'true',  'enabled']
317        valid_disabled_conditions = ['0', 'off', 'false', 'disabled', '']
318        is_enabled = ''
319
320        if 'conditional' in manifest_item.keys():
321            if manifest_item['conditional'] not in configs.keys():
322                logging.error('Configuration "{}" is not defined!'.format(manifest_item['conditional']))
323                exit(1)
324            is_enabled = configs[manifest_item['conditional']].lower()
325        else:
326            # Partitions without 'conditional' is always on
327            is_enabled = '1'
328
329        if is_enabled in valid_disabled_conditions:
330            logging.info("   {:40s}  OFF".format(manifest_item['description']))
331            continue
332        elif is_enabled in valid_enabled_conditions:
333            logging.info("   {:40s}  ON".format(manifest_item['description']))
334        else:
335            raise Exception('Invalid "conditional" attribute: "{}" for {}. '
336                            'Please set to one of {} or {}, case-insensitive.'\
337                            .format(manifest_item['conditional'],
338                                    manifest_item['description'],
339                                    valid_enabled_conditions, valid_disabled_conditions))
340
341        # Check if partition ID is manually set
342        if 'pid' not in manifest_item.keys():
343            no_pid_manifest_idx.append(i)
344            pid = None
345        else:
346            pid = manifest_item['pid']
347
348            # Check if partition ID is duplicated
349            if pid in pid_list:
350                raise Exception('PID No. {} has already been used!'.format(pid))
351            else:
352                pid_list.append(pid)
353
354        manifest_path = manifest_item['manifest']
355        with open(manifest_path) as manifest_file:
356            manifest = yaml.safe_load(manifest_file)
357            # Check manifest attribute validity
358            manifest_attribute_check(manifest, manifest_item)
359
360            if manifest.get('model', None) == 'dual':
361                # If a Partition supports both models, it can set the "model" to "backend".
362                # The actual model used follows the backend being used.
363                manifest['model'] = backend
364            manifest = manifest_validation(manifest, pid)
365
366            # Priority mapping
367            numbered_priority = priority_map[manifest['priority']]
368
369        if pid == None or pid >= TFM_PID_BASE:
370            # Count the number of IPC/SFN partitions
371            if manifest['model'] == 'IPC':
372                partition_statistics['ipc_partitions'].append(manifest['name'])
373
374        # Set initial value to -1 to make (srv_idx + 1) reflect the correct
375        # number (0) when there are no services.
376        srv_idx = -1
377        for srv_idx, service in enumerate(manifest.get('services', [])):
378            service_partition_map[service['name']] = manifest['name']
379            if manifest['model'] == 'IPC':
380                # Assign signal value, the first 4 bits are reserved by FF-M
381                service['signal_value'] = (1 << (srv_idx + 4))
382            else:
383                # Signals of SFN Partitions are SPM internal only, does not
384                # need to reserve 4 bits.
385                service['signal_value'] = (1 << srv_idx)
386            if service['connection_based']:
387                partition_statistics['connection_based_srv_num'] += 1
388        logging.debug('{} has {} services'.format(manifest['name'], srv_idx +1))
389
390        # Calculate the number of mmio region
391        mmio_region_list = manifest.get('mmio_regions', [])
392        partition_statistics['mmio_region_num'] += len(mmio_region_list)
393
394        # Set initial value to -1 to make (irq + 1) reflect the correct
395        # number (0) when there are no irqs.
396        irq_idx = -1
397        for irq_idx, irq in enumerate(manifest.get('irqs', [])):
398            # Assign signal value, from the most significant bit
399            irq['signal_value'] = (1 << (31 - irq_idx))
400            if irq.get('handling', None) == 'FLIH':
401                partition_statistics['flih_num'] += 1
402            else:
403                partition_statistics['slih_num'] += 1
404        logging.debug('{} has {} IRQS'.format(manifest['name'], irq_idx +1))
405
406        if ((srv_idx + 1) + (irq_idx + 1)) > 28:
407            raise Exception('Total number of Services and IRQs of {} exceeds the limit (28)'
408                            .format(manifest['name']))
409
410        manifest_out_basename = os.path.splitext(os.path.basename(manifest_path))[0]
411
412        if 'output_path' in manifest_item:
413            output_path = os.path.expandvars(manifest_item['output_path'])
414        else:
415            output_path = ''
416
417        manifest_head_file = os.path.join(OUT_DIR, output_path, 'psa_manifest',
418                                          '{}.h'.format(manifest_out_basename))\
419                                              .replace('\\', '/')
420        intermedia_file    = os.path.join(OUT_DIR, output_path, 'auto_generated',
421                                          'intermedia_{}.c'.format(manifest_out_basename))\
422                                              .replace('\\', '/')
423        load_info_file     = os.path.join(OUT_DIR, output_path, 'auto_generated',
424                                          'load_info_{}.c'.format(manifest_out_basename))\
425                                              .replace('\\', '/')
426        output_dir         = os.path.join(OUT_DIR, output_path).replace('\\', '/')
427
428        partition_list.append({'manifest': manifest, 'attr': manifest_item,
429                               'manifest_out_basename': manifest_out_basename,
430                               'header_file': manifest_head_file,
431                               'intermedia_file': intermedia_file,
432                               'loadinfo_file': load_info_file,
433                               'output_dir': output_dir,
434                               'numbered_priority': numbered_priority})
435
436    logging.info("------------ Display partition configuration - end ------------")
437
438    check_circular_dependency(partition_list, service_partition_map)
439
440    # Automatically assign PIDs for partitions without 'pid' attribute
441    pid = max(pid_list, default = TFM_PID_BASE - 1)
442    for idx in no_pid_manifest_idx:
443        pid += 1
444        all_manifests[idx]['pid'] = pid
445        pid_list.append(pid)
446
447    # Set up configurations
448    if backend == 'SFN':
449        if len(partition_statistics['ipc_partitions']) > 0:
450            logging.error('SFN backend does not support IPC Partitions:')
451            logging.error(partition_statistics['ipc_partitions'])
452            exit(1)
453
454        if isolation_level > 1:
455            logging.error('SFN backend does not support high isolation levels.')
456            exit(1)
457
458        config_impl['CONFIG_TFM_SPM_BACKEND_SFN'] = '1'
459    elif backend == 'IPC':
460        config_impl['CONFIG_TFM_SPM_BACKEND_IPC'] = '1'
461
462    if partition_statistics['connection_based_srv_num'] > 0:
463        config_impl['CONFIG_TFM_CONNECTION_BASED_SERVICE_API'] = 1
464
465    if partition_statistics['mmio_region_num'] > 0:
466        config_impl['CONFIG_TFM_MMIO_REGION_ENABLE'] = 1
467
468    if partition_statistics['flih_num'] > 0:
469        config_impl['CONFIG_TFM_FLIH_API'] = 1
470    if partition_statistics['slih_num'] > 0:
471        config_impl['CONFIG_TFM_SLIH_API'] = 1
472
473    context['partitions'] = partition_list
474    context['config_impl'] = config_impl
475    context['stateless_services'] = process_stateless_services(partition_list)
476
477    return context
478
479def gen_per_partition_files(context):
480    """
481    Generate per-partition files
482
483    Parameters
484    ----------
485    context:
486        context contains partition infos
487    """
488
489    partition_context = {}
490    partition_context['utilities'] = context['utilities']
491    partition_context['config_impl'] = context['config_impl']
492
493    manifesttemplate = ENV.get_template(os.path.join(sys.path[0], 'templates/manifestfilename.template'))
494    memorytemplate = ENV.get_template(os.path.join(sys.path[0], 'templates/partition_intermedia.template'))
495    infotemplate = ENV.get_template(os.path.join(sys.path[0], 'templates/partition_load_info.template'))
496
497    logging.info ("Start to generate partition files:")
498
499    for one_partition in context['partitions']:
500        partition_context['manifest'] = one_partition['manifest']
501        partition_context['attr'] = one_partition['attr']
502        partition_context['manifest_out_basename'] = one_partition['manifest_out_basename']
503        partition_context['numbered_priority'] = one_partition['numbered_priority']
504
505        logging.info ('Generating {} in {}'.format(one_partition['attr']['description'],
506                                            one_partition['output_dir']))
507        outfile_path = os.path.dirname(one_partition['header_file'])
508        if not os.path.exists(outfile_path):
509            os.makedirs(outfile_path)
510
511        headerfile = io.open(one_partition['header_file'], 'w', newline=None)
512        headerfile.write(manifesttemplate.render(partition_context))
513        headerfile.close()
514
515        intermediafile_path = os.path.dirname(one_partition['intermedia_file'])
516        if not os.path.exists(intermediafile_path):
517            os.makedirs(intermediafile_path)
518        intermediafile = io.open(one_partition['intermedia_file'], 'w', newline=None)
519        intermediafile.write(memorytemplate.render(partition_context))
520        intermediafile.close()
521
522        infofile_path = os.path.dirname(one_partition['loadinfo_file'])
523        if not os.path.exists(infofile_path):
524            os.makedirs(infofile_path)
525        infooutfile = io.open(one_partition['loadinfo_file'], 'w', newline=None)
526        infooutfile.write(infotemplate.render(partition_context))
527        infooutfile.close()
528
529    logging.info ("Per-partition files done:")
530
531def gen_summary_files(context, gen_file_lists):
532    """
533    Generate files according to the gen_file_list
534
535    Parameters
536    ----------
537    gen_file_lists:
538        The lists of files to generate
539    """
540    file_list = []
541
542    for f in gen_file_lists:
543        with open(f) as file_list_yaml_file:
544            file_list_yaml = yaml.safe_load(file_list_yaml_file)
545            file_list.extend(file_list_yaml['file_list'])
546
547    for file in file_list:
548        # Replace environment variables in the output filepath
549        manifest_out_file = os.path.expandvars(file['output'])
550        # Replace environment variables in the template filepath
551        templatefile_name = os.path.expandvars(file['template'])
552
553        manifest_out_file = os.path.join(OUT_DIR, manifest_out_file)
554
555        outfile_path = os.path.dirname(manifest_out_file)
556        if not os.path.exists(outfile_path):
557            os.makedirs(outfile_path)
558
559        template = ENV.get_template(templatefile_name)
560
561        outfile = io.open(manifest_out_file, 'w', newline=None)
562        outfile.write(template.render(context))
563        outfile.close()
564
565def process_stateless_services(partitions):
566    """
567    This function collects all stateless services together, and allocates
568    stateless handles for them.
569    Valid stateless handle in service will be converted to an index. If the
570    stateless handle is set as "auto", or not set, framework will allocate a
571    valid index for the service.
572    Framework puts each service into a reordered stateless service list at
573    position of "index". Other unused positions are left None.
574
575    Keep the variable names start with upper case 'STATIC_HANDLE_' the same
576    as the preprocessors in C sources. This could easier the upcomping
577    modification when developer searches these definitions for modification.
578    """
579
580    collected_stateless_services = []
581    STATIC_HANDLE_NUM_LIMIT = 32
582
583    # Collect all stateless services first.
584    for partition in partitions:
585        # Skip the FF-M 1.0 partitions
586        if partition['manifest']['psa_framework_version'] < 1.1:
587            continue
588
589        service_list = partition['manifest'].get('services', [])
590
591        for service in service_list:
592            if service['connection_based'] is False:
593                collected_stateless_services.append(service)
594
595    if len(collected_stateless_services) == 0:
596        return []
597
598    if len(collected_stateless_services) > STATIC_HANDLE_NUM_LIMIT:
599        raise Exception('Stateless service numbers range exceed {number}.'.format(number=STATIC_HANDLE_NUM_LIMIT))
600
601    """
602    Allocate an empty stateless service list to store services.
603    Use "handle - 1" as the index for service, since handle value starts from
604    1 and list index starts from 0.
605    """
606    reordered_stateless_services = [None] * STATIC_HANDLE_NUM_LIMIT
607    auto_alloc_services = []
608
609    for service in collected_stateless_services:
610        # If not set, it is "auto" by default
611        if 'stateless_handle' not in service:
612            auto_alloc_services.append(service)
613            continue
614
615        service_handle = service['stateless_handle']
616
617        # Fill in service list with specified stateless handle, otherwise skip
618        if isinstance(service_handle, int):
619            if service_handle < 1 or service_handle > STATIC_HANDLE_NUM_LIMIT:
620                raise Exception('Invalid stateless_handle setting: {handle}.'.format(handle=service['stateless_handle']))
621            # Convert handle index to reordered service list index
622            service_handle = service_handle - 1
623
624            if reordered_stateless_services[service_handle] is not None:
625                raise Exception('Duplicated stateless_handle setting: {handle}.'.format(handle=service['stateless_handle']))
626            reordered_stateless_services[service_handle] = service
627        elif service_handle == 'auto':
628            auto_alloc_services.append(service)
629        else:
630            raise Exception('Invalid stateless_handle setting: {handle}.'.format(handle=service['stateless_handle']))
631
632    STATIC_HANDLE_IDX_BIT_WIDTH = 5
633    STATIC_HANDLE_IDX_MASK = (1 << STATIC_HANDLE_IDX_BIT_WIDTH) - 1
634    STATIC_HANDLE_INDICATOR_OFFSET = 30
635    STATIC_HANDLE_VER_OFFSET = 8
636    STATIC_HANDLE_VER_BIT_WIDTH = 8
637    STATIC_HANDLE_VER_MASK = (1 << STATIC_HANDLE_VER_BIT_WIDTH) - 1
638
639    # Auto-allocate stateless handle and encode the stateless handle
640    for i in range(0, STATIC_HANDLE_NUM_LIMIT):
641        service = reordered_stateless_services[i]
642
643        if service == None and len(auto_alloc_services) > 0:
644            service = auto_alloc_services.pop(0)
645
646        """
647        Encode stateless flag and version into stateless handle
648        """
649        stateless_handle_value = 0
650        if service != None:
651            stateless_index = (i & STATIC_HANDLE_IDX_MASK)
652            stateless_handle_value |= stateless_index
653            stateless_handle_value |= (1 << STATIC_HANDLE_INDICATOR_OFFSET)
654            stateless_version = (service['version'] & STATIC_HANDLE_VER_MASK) << STATIC_HANDLE_VER_OFFSET
655            stateless_handle_value |= stateless_version
656            service['stateless_handle_value'] = '0x{0:08x}'.format(stateless_handle_value)
657            service['stateless_handle_index'] = stateless_index
658
659        reordered_stateless_services[i] = service
660
661    return reordered_stateless_services
662
663def parse_args():
664    parser = argparse.ArgumentParser(description='Parse secure partition manifest list and generate files listed by the file list',
665                                     epilog='Note that environment variables in template files will be replaced with their values')
666
667    parser.add_argument('-o', '--outdir'
668                        , dest='outdir'
669                        , required=True
670                        , metavar='out_dir'
671                        , help='The root directory for generated files')
672
673    parser.add_argument('-m', '--manifest-lists'
674                        , nargs='+'
675                        , dest='manifest_lists'
676                        , required=True
677                        , metavar='manifest list'
678                        , help='A list of Secure Partition manifest lists and their original paths.\n\
679                                The manifest lists might be processed by CMake and\n\
680                                the path might be different to the original one\n\
681                                The format must be [list A, orignal path A, list B, orignal path B, ...]')
682
683    parser.add_argument('-f', '--file-list'
684                        , nargs='+'
685                        , dest='gen_file_args'
686                        , required=True
687                        , metavar='file-list'
688                        , help='These files describe the file list to generate')
689
690    parser.add_argument('-c', '--config-files'
691                        , nargs='+'
692                        , dest='config_files'
693                        , required=True
694                        , metavar='config-files'
695                        , help='A header file contains build configurations')
696
697    parser.add_argument('-q', '--quiet'
698                        , dest='quiet'
699                        , required=False
700                        , default=False
701                        , action='store_true'
702                        , help='Reduce log messages')
703
704    args = parser.parse_args()
705
706    return args
707
708ENV = Environment(
709        loader = TemplateLoader(),
710        autoescape = select_autoescape(['html', 'xml']),
711        lstrip_blocks = True,
712        trim_blocks = True,
713        keep_trailing_newline = True
714    )
715
716def main():
717    """
718    The entry point of the script.
719
720    Generates the output files based on the templates and the manifests.
721    """
722
723    global OUT_DIR
724
725    args = parse_args()
726
727    logging.basicConfig(format='%(message)s'
728                        , level=logging.WARNING if args.quiet else logging.INFO)
729
730    OUT_DIR = os.path.abspath(args.outdir)
731
732    manifest_lists = [os.path.abspath(x) for x in args.manifest_lists]
733    gen_file_lists = [os.path.abspath(x) for x in args.gen_file_args]
734
735    """
736    Relative path to TF-M root folder is supported in the manifests
737    and default value of manifest list and generated file list are relative to TF-M root folder as well,
738    so first change directory to TF-M root folder.
739    By doing this, the script can be executed anywhere
740    The script is located in <TF-M root folder>/tools, so sys.path[0]<location of the script>/.. is TF-M root folder.
741    """
742    os.chdir(os.path.join(sys.path[0], '..'))
743
744    context = process_partition_manifests(manifest_lists,
745                                          parse_configurations(args.config_files))
746
747    utilities = {}
748    utilities['donotedit_warning'] = donotedit_warning
749
750    context['utilities'] = utilities
751
752    gen_per_partition_files(context)
753    gen_summary_files(context, gen_file_lists)
754
755if __name__ == '__main__':
756    main()
757