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