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