Consolidating tools/tfm and tools/spm into tools/psa

pull/9653/head
Michael Schwarcz 2019-02-13 16:13:15 +02:00
parent 0ba963acfc
commit 516af50cb7
20 changed files with 357 additions and 1147 deletions

View File

@ -324,5 +324,5 @@ matrix:
- NAME=psa-autogen
script:
# Run SPM code generator and check that changes are not needed
- python tools/spm/generate_partition_code.py
- python tools/psa/generate_mbed_spm_partition_code.py
- git diff --exit-code

0
tools/psa/__init__.py Normal file
View File

View File

@ -0,0 +1,227 @@
#!/usr/bin/python
# Copyright (c) 2017-2018 ARM Limited
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import itertools
import os
import sys
from os.path import join as path_join
from jinja2 import Environment, FileSystemLoader, StrictUndefined
# Be sure that the tools directory is in the search path
ROOT = os.path.abspath(path_join(os.path.dirname(__file__), os.pardir, os.pardir))
sys.path.insert(0, ROOT)
from tools.psa.mbed_spm_tfm_common import Manifest, validate_partition_manifests, manifests_discovery
__version__ = '1.0'
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPLATES_DIR = path_join(SCRIPT_DIR, 'mbed_spm', 'templates')
MANIFEST_TEMPLATES = [filename for filename in
[os.path.join(dp, f) for dp, dn, fn in
os.walk(TEMPLATES_DIR) for f in fn if f.endswith('.tpl')]
if '_NAME_' in filename]
COMMON_TEMPLATES = [filename for filename in
[os.path.join(dp, f) for dp, dn, fn in
os.walk(TEMPLATES_DIR) for f in fn if f.endswith('.tpl')]
if '_NAME_' not in filename]
MANIFEST_FILE_PATTERN = '*_psa.json'
MBED_OS_ROOT = os.path.abspath(path_join(SCRIPT_DIR, os.pardir, os.pardir))
SPM_CORE_ROOT = path_join(MBED_OS_ROOT, 'components', 'TARGET_PSA', 'TARGET_MBED_SPM')
SPM_TESTS_ROOT = path_join(MBED_OS_ROOT, 'TESTS', 'psa')
def generate_source_files(
templates,
render_args,
output_folder,
extra_filters=None
):
"""
Generate SPM common C code from manifests using given templates
:param templates: Dictionary of template and their auto-generated products
:param render_args: Dictionary of arguments that should be passed to render
:param output_folder: Output directory for file generation
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: Path to generated folder containing common generated files
"""
rendered_files = []
templates_dirs = list(
set([os.path.dirname(path) for path in templates])
)
template_files = {os.path.basename(t): t for t in templates}
# Load templates for the code generation.
env = Environment(
loader=FileSystemLoader(templates_dirs),
lstrip_blocks=True,
trim_blocks=True,
undefined=StrictUndefined
)
if extra_filters:
env.filters.update(extra_filters)
for tf in template_files:
template = env.get_template(tf)
rendered_files.append(
(templates[template_files[tf]], template.render(**render_args)))
rendered_file_dir = os.path.dirname(templates[template_files[tf]])
if not os.path.exists(rendered_file_dir):
os.makedirs(rendered_file_dir)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for fname, data in rendered_files:
with open(fname, 'wt') as fh:
fh.write(data)
return output_folder
def generate_partitions_sources(manifest_files, extra_filters=None):
"""
Process all the given manifest files and generate C code from them
:param manifest_files: List of manifest files
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: List of paths to the generated files
"""
# Construct a list of all the manifests and sids.
manifests = []
for manifest_file in manifest_files:
manifest = Manifest.from_json(manifest_file)
manifests.append(manifest)
generated_folders = set()
for manifest in manifests:
manifest_output_folder = manifest.autogen_folder
render_args = {
'partition': manifest,
'dependent_partitions': manifest.find_dependencies(manifests),
'script_ver': __version__
}
manifest_output_folder = generate_source_files(
manifest.templates_to_files(MANIFEST_TEMPLATES,
TEMPLATES_DIR,
manifest_output_folder),
render_args,
manifest_output_folder,
extra_filters=extra_filters
)
generated_folders.add(manifest_output_folder)
return list(generated_folders)
def generate_psa_setup(manifest_files, output_dir, weak_setup,
extra_filters=None):
"""
Process all the given manifest files and generate C setup code from them
:param manifest_files: List of manifest files
:param output_dir: Output directory for the generated files
:param weak_setup: Is the functions/data in the setup file weak
(can be overridden by another setup file)
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: path to the setup generated files
"""
autogen_folder = output_dir
templates_dict = {
t: path_join(autogen_folder,
os.path.relpath(os.path.splitext(t)[0], TEMPLATES_DIR))
for t in COMMON_TEMPLATES
}
complete_source_list = list(templates_dict.values())
# Construct lists of all the manifests and mmio_regions.
region_list = []
manifests = []
for manifest_file in manifest_files:
manifest_obj = Manifest.from_json(manifest_file)
manifests.append(manifest_obj)
for region in manifest_obj.mmio_regions:
region_list.append(region)
complete_source_list.extend(
list(manifest_obj.templates_to_files(
MANIFEST_TEMPLATES,
TEMPLATES_DIR,
manifest_obj.autogen_folder).values())
)
# Validate the correctness of the manifest collection.
validate_partition_manifests(manifests)
render_args = {
'partitions': manifests,
'regions': region_list,
'region_pair_list': list(itertools.combinations(region_list, 2)),
'weak': weak_setup,
'script_ver': __version__
}
return generate_source_files(
templates_dict,
render_args,
autogen_folder,
extra_filters=extra_filters
)
def generate_psa_code():
# Find all manifest files in the mbed-os tree
manifest_files = manifests_discovery(MBED_OS_ROOT)
# Generate partition code for each manifest file
generate_partitions_sources(manifest_files)
test_manifest_files = sorted(
[path for path in manifest_files if 'TESTS' in path])
system_manifest_files = sorted(
list(set(manifest_files) - set(test_manifest_files)))
# Generate default system psa setup file (only system partitions)
generate_psa_setup(system_manifest_files, SPM_CORE_ROOT, weak_setup=True)
tests_dir_content = [path_join(SPM_TESTS_ROOT, f) for f in
os.listdir(SPM_TESTS_ROOT)]
spm_tests = [path for path in tests_dir_content if os.path.isdir(path)]
# Build a dictionary for test partition in the form of:
# { test_root: manifest_list }
# For each test generate specific psa setup file (system + test partitions)
tests_dict = {test_root: [] for test_root in spm_tests}
for test_root in spm_tests:
tests_dict[test_root] = [manifest_path for manifest_path in
test_manifest_files if
test_root in manifest_path]
if not tests_dict[test_root]:
continue
tests_dict[test_root] += system_manifest_files
generate_psa_setup(sorted(tests_dict[test_root]), test_root,
weak_setup=False)
if __name__ == '__main__':
generate_psa_code()

View File

@ -0,0 +1,102 @@
#!/usr/bin/python
# Copyright (c) 2017-2018 ARM Limited
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import json
import os
import sys
from os.path import join as path_join
from jinja2 import Environment, FileSystemLoader, StrictUndefined
# Be sure that the tools directory is in the search path
ROOT = os.path.abspath(path_join(os.path.dirname(__file__), os.pardir, os.pardir))
sys.path.insert(0, ROOT)
from tools.psa.mbed_spm_tfm_common import Manifest, validate_partition_manifests
__version__ = '1.0'
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
MBED_OS_ROOT = os.path.abspath(path_join(SCRIPT_DIR, os.pardir, os.pardir))
TEMPLATES_LIST_FILE = path_join(SCRIPT_DIR, 'tfm', 'tfm_generated_file_list.json')
SERVICES_DIR = os.path.join(MBED_OS_ROOT, "components", "TARGET_PSA", "services")
SERVICES_MANIFESTS = [
path_join(SERVICES_DIR, 'psa_prot_internal_storage', 'pits_psa.json'),
path_join(SERVICES_DIR, 'platform', 'platform_psa.json'),
path_join(SERVICES_DIR, 'crypto', 'crypto_partition_psa.json')
]
def generate_partition_source_files(manifest_files, extra_filters=None):
"""
Process all the given manifest files and generate C code from them.
:param manifest_files: List of manifest files
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: path to the setup generated files
"""
# Construct lists of all the manifests and mmio_regions.
region_list = []
manifests = []
for manifest_file in manifest_files:
manifest_obj = Manifest.from_json(manifest_file, psa_type='TFM')
manifests.append(manifest_obj)
for region in manifest_obj.mmio_regions:
region_list.append(region)
# Validate the correctness of the manifest collection.
validate_partition_manifests(manifests)
render_args = {
'partitions': manifests,
}
# Load templates for the code generation.
with open(TEMPLATES_LIST_FILE, 'r') as fh:
templates_data = json.load(fh)
env = Environment(
loader=FileSystemLoader(MBED_OS_ROOT),
lstrip_blocks=True,
trim_blocks=True,
undefined=StrictUndefined
)
if extra_filters:
env.filters.update(extra_filters)
# Generate code for each template
for tpl in templates_data:
template = env.get_template(tpl['template'])
data = template.render(**render_args)
output_path = os.path.join(MBED_OS_ROOT, tpl['output'])
output_folder = os.path.dirname(output_path)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
with open(output_path, 'wt') as fh:
fh.write(data)
def generate_tfm_code():
generate_partition_source_files(SERVICES_MANIFESTS)
if __name__ == '__main__':
generate_tfm_code()

View File

@ -15,22 +15,15 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import fnmatch
import itertools
import json
import os
from os.path import join as path_join
import json
from jsonschema import validate
import fnmatch
from six import integer_types, string_types
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from jsonschema import validate
__version__ = '1.0'
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPLATES_LIST_FILE = path_join(SCRIPT_DIR, 'tfm_generated_file_list.json')
MANIFEST_FILE_PATTERN = '*_psa.json'
MBED_OS_ROOT = os.path.abspath(path_join(SCRIPT_DIR, os.pardir, os.pardir))
SERVICES_DIR = os.path.join(MBED_OS_ROOT, "components", "TARGET_PSA", "services")
def assert_int(num):
@ -171,7 +164,11 @@ class Irq(object):
class Manifest(object):
PRIORITY = ['LOW', 'NORMAL', 'HIGH']
PRIORITY = {
'LOW': 'osPriorityLow',
'NORMAL': 'osPriorityNormal',
'HIGH': 'osPriorityHigh'
}
PARTITION_TYPES = ['APPLICATION-ROT', 'PSA-ROT']
# The following signal bits cannot be used:
# bit[0-2] | Reserved
@ -182,6 +179,7 @@ class Manifest(object):
def __init__(
self,
manifest_file,
psa_type,
name,
partition_id,
partition_type,
@ -199,6 +197,7 @@ class Manifest(object):
Manifest C'tor (Aligned with json schema)
:param manifest_file: Path to json manifest
:param psa_type: PSA implementation type (TFM/MBED_SPM)
:param name: Partition unique name
:param partition_id: Partition identifier
:param partition_type: Whether the partition is unprivileged or part
@ -236,14 +235,18 @@ class Manifest(object):
assert isinstance(stack_size, int)
assert isinstance(entry_point, string_types)
assert partition_type in self.PARTITION_TYPES
assert priority in self.PRIORITY
assert partition_id > 0
assert psa_type in ['TFM', 'MBED_SPM']
self.file = manifest_file
self.name = name
self.psa_type = psa_type
self.id = partition_id
self.type = partition_type
self.priority = priority
if psa_type == 'TFM':
self.priority = priority
else:
self.priority = self.PRIORITY[priority]
self.heap_size = heap_size
self.stack_size = stack_size
self.entry_point = entry_point
@ -300,12 +303,13 @@ class Manifest(object):
)
@classmethod
def from_json(cls, manifest_file, skip_src=False):
def from_json(cls, manifest_file, skip_src=False, psa_type='MBED_SPM'):
"""
Load a partition manifest file
:param manifest_file: Manifest file path
:param skip_src: Ignore the `source_files` entry
:param psa_type: PSA implementation type (TFM/MBED_SPM)
:return: Manifest object
"""
@ -344,6 +348,7 @@ class Manifest(object):
return Manifest(
manifest_file=manifest_file,
psa_type=psa_type,
name=manifest['name'],
partition_id=assert_int(manifest['id']),
partition_type=manifest['type'],
@ -386,6 +391,7 @@ class Manifest(object):
Translates a list of partition templates to file names
:param templates: List of partition templates
:param templates_base: Base directory of the templates
:param output_dir: Output directory (Default is autogen folder property)
:return: Dictionary of template to output file translation
"""
@ -588,82 +594,6 @@ def validate_partition_manifests(manifests):
)
def generate_source_files(
templates_dict,
render_args,
extra_filters=None
):
"""
Generate SPM common C code from manifests using given templates
:param templates: Dictionary of template and their auto-generated products
:param render_args: Dictionary of arguments that should be passed to render
:param extra_filters: Dictionary of extra filters to use in the rendering
process
"""
# Load templates for the code generation.
env = Environment(
loader=FileSystemLoader(MBED_OS_ROOT),
lstrip_blocks=True,
trim_blocks=True,
undefined=StrictUndefined
)
if extra_filters:
env.filters.update(extra_filters)
for tpl in templates_dict:
template = env.get_template(tpl['template'])
data = template.render(**render_args)
output_path = os.path.join(MBED_OS_ROOT, tpl['output'])
output_folder = os.path.dirname(output_path)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
with open(output_path, 'wt') as fh:
fh.write(data)
def generate_tfm_setup(manifest_files, extra_filters=None):
"""
Process all the given manifest files and generate C setup code from them
:param manifest_files: List of manifest files
:param output_dir: Output directory for the generated files
:param weak_setup: Is the functions/data in the setup file weak
(can be overridden by another setup file)
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: path to the setup generated files
"""
# Construct lists of all the manifests and mmio_regions.
region_list = []
manifests = []
for manifest_file in manifest_files:
manifest_obj = Manifest.from_json(manifest_file)
manifests.append(manifest_obj)
for region in manifest_obj.mmio_regions:
region_list.append(region)
# Validate the correctness of the manifest collection.
validate_partition_manifests(manifests)
with open(TEMPLATES_LIST_FILE, 'r') as fh:
templates_data = json.load(fh)
render_args = {
'partitions': manifests,
}
return generate_source_files(
templates_data,
render_args,
extra_filters=extra_filters
)
def manifests_discovery(root_dir):
manifest_files = set()
@ -674,16 +604,3 @@ def manifests_discovery(root_dir):
manifest_files.update(to_add)
return list(manifest_files)
def generate_tfm_code():
# Find all manifest files in the mbed-os tree
manifest_files = manifests_discovery(SERVICES_DIR)
# Generate default system psa setup file (only system partitions)
generate_tfm_setup(manifest_files)
if __name__ == '__main__':
generate_tfm_code()

View File

@ -1,22 +1,22 @@
[
{
"name": "Secure Partition ID definitions",
"template": "tools/tfm/templates/tfm_partition_defs.inc.tpl",
"template": "tools/psa/tfm/templates/tfm_partition_defs.inc.tpl",
"output": "components/TARGET_PSA/TARGET_TFM/COMPONENT_SPE/autogen/tfm_partition_defs.inc"
},
{
"name": "Secure Partition declarations",
"template": "tools/tfm/templates/tfm_partition_list.inc.tpl",
"template": "tools/psa/tfm/templates/tfm_partition_list.inc.tpl",
"output": "components/TARGET_PSA/TARGET_TFM/COMPONENT_SPE/autogen/tfm_partition_list.inc"
},
{
"name": "Secure Service list",
"template": "tools/tfm/templates/tfm_service_list.inc.tpl",
"template": "tools/psa/tfm/templates/tfm_service_list.inc.tpl",
"output": "components/TARGET_PSA/TARGET_TFM/COMPONENT_SPE/autogen/tfm_service_list.inc"
},
{
"name": "Secure Service siganls list",
"template": "tools/tfm/templates/tfm_spm_signal_defs.h.tpl",
"template": "tools/psa/tfm/templates/tfm_spm_signal_defs.h.tpl",
"output": "components/TARGET_PSA/TARGET_TFM/COMPONENT_SPE/autogen/tfm_spm_signal_defs.h"
}
]

View File

@ -1,23 +0,0 @@
# Copyright (c) 2017-2018 ARM Limited
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .generate_partition_code import \
generate_partitions_sources, generate_psa_setup
__all__ = [
'generate_partitions_sources',
'generate_psa_setup',
]

View File

@ -1,795 +0,0 @@
#!/usr/bin/python
# Copyright (c) 2017-2018 ARM Limited
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import fnmatch
import itertools
import json
import os
from os.path import join as path_join
from six import integer_types, string_types
from jinja2 import Environment, FileSystemLoader, StrictUndefined
from jsonschema import validate
__version__ = '1.0'
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPLATES_DIR = path_join(SCRIPT_DIR, 'templates')
MANIFEST_TEMPLATES = [filename for filename in
[os.path.join(dp, f) for dp, dn, fn in
os.walk(TEMPLATES_DIR) for f in fn if f.endswith('.tpl')]
if '_NAME_' in filename]
COMMON_TEMPLATES = [filename for filename in
[os.path.join(dp, f) for dp, dn, fn in
os.walk(TEMPLATES_DIR) for f in fn if f.endswith('.tpl')]
if '_NAME_' not in filename]
MANIFEST_FILE_PATTERN = '*_psa.json'
MBED_OS_ROOT = os.path.abspath(path_join(SCRIPT_DIR, os.pardir, os.pardir))
SPM_CORE_ROOT = path_join(MBED_OS_ROOT, 'components', 'TARGET_PSA', 'TARGET_MBED_SPM')
SPM_TESTS_ROOT = path_join(MBED_OS_ROOT, 'TESTS', 'psa')
def assert_int(num):
"""
Tries to parse an integer num from a given string
:param num: Number in int/string type
:return: Numeric value
"""
if isinstance(num, int):
return num
num_str = str(num)
radix = 16 if num_str.lower().startswith('0x') else 10
res = int(num_str, radix)
# Python converts str to int as a signed integer
if res > 0x7FFFFFFF:
res -= 0x100000000
return res
class RotService(object):
MINOR_POLICIES = ['STRICT', 'RELAXED']
def __init__(
self,
name,
identifier,
signal,
non_secure_clients,
minor_version=1,
minor_policy='STRICT'
):
"""
Root of Trust Service C'tor (Aligned with json schema)
:param name: Root of Trust Service identifier (available to user)
:param identifier: Root of Trust Service numeric enumeration.
:param signal: Root of Trust Service identifier inside the partition
:param non_secure_clients: True to allow connections from non-secure
partitions
:param minor_version: Root of Trust Service version
:param minor_policy: Enforcement level of minor version
"""
self.name = name
self.id = identifier
self.signal = signal
assert assert_int(identifier)
assert isinstance(non_secure_clients, bool), \
'non_secure_clients parameter must be of boolean type'
self.nspe_callable = non_secure_clients
self.minor_version = assert_int(minor_version)
assert self.minor_version > 0, 'minor_version parameter is invalid'
assert minor_policy in self.MINOR_POLICIES, \
'minor_policy parameter is invalid'
self.minor_policy = minor_policy
@property
def numeric_id(self):
return assert_int(self.id)
def __eq__(self, other):
return (
(self.name == other.name) and
(self.id == other.id) and
(self.signal == other.signal) and
(self.nspe_callable == other.nspe_callable) and
(self.minor_version == other.minor_version) and
(self.minor_policy == other.minor_policy)
)
class MmioRegion(object):
MMIO_PERMISSIONS = {
'READ-ONLY': 'PSA_MMIO_PERM_READ_ONLY',
'READ-WRITE': 'PSA_MMIO_PERM_READ_WRITE'
}
def __init__(self, **kwargs):
"""
MMIO Region C'tor (Aligned with json schema)
Supports both named and numeric regions
In case of named region the acceptable params are name and permission
In case of numeric region the acceptable params are name, size and
permission
:param name: C definition name of the region (size will be
auto-generated)
:param base: C hex string defining a memory address (must be 32bit)
:param size: size of a region (Applicable only for numbered regions)
:param permission: Access permissions to the described region (R/RW)
"""
assert 'permission' in kwargs
self.permission = self.MMIO_PERMISSIONS[kwargs['permission']]
if 'name' in kwargs:
self.base = kwargs['name']
self.size = '(sizeof(*({})))'.format(kwargs['name'])
if 'base' in kwargs:
self.base = kwargs['base']
self.size = assert_int(kwargs['size'])
assert 'partition_id' in kwargs
self.partition_id = assert_int(kwargs['partition_id'])
assert hasattr(self, 'base')
assert hasattr(self, 'size')
assert hasattr(self, 'permission')
assert hasattr(self, 'partition_id')
def __eq__(self, other):
return (
(self.base == other.base) and
(self.size == other.size) and
(self.permission == other.permission)
)
class Irq(object):
def __init__(self, line_num, signal):
"""
IRQ line C'tor (Aligned with json schema)
:param line_num: number of interrupt used by the partition
:param signal: IRQ line identifier inside the partition
"""
self.line_num = assert_int(line_num)
assert isinstance(signal, string_types)
self.signal = signal
def __eq__(self, other):
return (self.line_num == other.line_num) and \
(self.signal == other.signal)
class Manifest(object):
PRIORITY = {
'LOW': 'osPriorityLow',
'NORMAL': 'osPriorityNormal',
'HIGH': 'osPriorityHigh'
}
PARTITION_TYPES = ['APPLICATION-ROT', 'PSA-ROT']
# The following signal bits cannot be used:
# bit[0-2] | Reserved
# bit[3] | PSA Doorbell
# bit[31] | RTX error bit
RESERVED_SIGNALS = 5
def __init__(
self,
manifest_file,
name,
partition_id,
partition_type,
priority,
entry_point,
heap_size,
stack_size,
source_files,
mmio_regions=None,
rot_services=None,
extern_sids=None,
irqs=None
):
"""
Manifest C'tor (Aligned with json schema)
:param manifest_file: Path to json manifest
:param name: Partition unique name
:param partition_id: Partition identifier
:param partition_type: Whether the partition is unprivileged or part
of the trusted computing base
:param priority: Priority of the partition's thread
:param entry_point: C symbol name of the partition's main function
:param heap_size: Size of heap required for the partition
:param stack_size: Size of stack required for the partition
:param source_files: List of files assembling the partition
(relative paths)
:param mmio_regions: List of MMIO regions used by the partition
:param rot_services: List of Root of Trust Services declared by the
partition
:param extern_sids: List of Root of Trust Services the partition can call
:param irqs: List of interrupts the partition can handle
"""
assert manifest_file is not None
assert name is not None
assert partition_id is not None
assert partition_type is not None
assert entry_point is not None
assert priority is not None
assert heap_size is not None
assert stack_size is not None
assert source_files is not None
mmio_regions = [] if mmio_regions is None else mmio_regions
rot_services = [] if rot_services is None else rot_services
extern_sids = [] if extern_sids is None else extern_sids
irqs = [] if irqs is None else irqs
assert os.path.isfile(manifest_file)
assert isinstance(partition_id, integer_types)
assert isinstance(heap_size, int)
assert isinstance(stack_size, int)
assert isinstance(entry_point, string_types)
assert partition_type in self.PARTITION_TYPES
assert partition_id > 0
self.file = manifest_file
self.name = name
self.id = partition_id
self.type = partition_type
self.priority = self.PRIORITY[priority]
self.heap_size = heap_size
self.stack_size = stack_size
self.entry_point = entry_point
if isinstance(source_files, list):
self.source_files = source_files
else:
self.source_files = [source_files]
self.mmio_regions = mmio_regions
self.rot_services = rot_services
self.extern_sids = extern_sids
self.irqs = irqs
for src_file in self.source_files:
assert os.path.isfile(src_file), \
"The source file {} mentioned in {} doesn't exist.".format(
src_file, self.file
)
for rot_srv in self.rot_services:
assert isinstance(rot_srv, RotService)
for extern_sid in self.extern_sids:
assert isinstance(extern_sid, string_types)
assert len(self.extern_sids) == len(set(self.extern_sids)), \
'Detected duplicates external SIDs in {}'.format(self.file)
for irq in self.irqs:
assert isinstance(irq, Irq)
total_signals = len(self.rot_services) + len(self.irqs)
assert total_signals <= 32 - self.RESERVED_SIGNALS, \
'Manifest {} - {} exceeds limit of RoT services and IRQs allowed ' \
'({}).'.format(
self.name, self.file, 32 - self.RESERVED_SIGNALS
)
def __eq__(self, other):
return (
(self.file == other.file) and
(self.name == other.name) and
(self.id == other.id) and
(self.type == other.type) and
(self.priority == other.priority) and
(self.heap_size == other.heap_size) and
(self.stack_size == other.stack_size) and
(self.entry_point == other.entry_point) and
(self.source_files == other.source_files) and
(self.mmio_regions == other.mmio_regions) and
(self.rot_services == other.rot_services) and
(self.extern_sids == other.extern_sids) and
(self.irqs == other.irqs)
)
@classmethod
def from_json(cls, manifest_file, skip_src=False):
"""
Load a partition manifest file
:param manifest_file: Manifest file path
:param skip_src: Ignore the `source_files` entry
:return: Manifest object
"""
partition_schema_path = path_join(
SCRIPT_DIR,
'partition_description_schema.json'
)
with open(partition_schema_path) as schema_fh:
partition_schema = json.load(schema_fh)
# Load partition manifest file.
with open(manifest_file) as fh:
manifest = json.load(fh)
validate(manifest, partition_schema)
manifest_dir = os.path.dirname(manifest_file)
source_files = []
if not skip_src:
for src_file in manifest['source_files']:
source_files.append(
os.path.normpath(path_join(manifest_dir, src_file)))
mmio_regions = []
for mmio_region in manifest.get('mmio_regions', []):
mmio_regions.append(
MmioRegion(partition_id=manifest['id'], **mmio_region))
rot_services = []
for rot_srv in manifest.get('services', []):
rot_services.append(RotService(**rot_srv))
irqs = []
for irq in manifest.get('irqs', []):
irqs.append(Irq(**irq))
return Manifest(
manifest_file=manifest_file,
name=manifest['name'],
partition_id=assert_int(manifest['id']),
partition_type=manifest['type'],
priority=manifest['priority'],
heap_size=assert_int(manifest['heap_size']),
stack_size=assert_int(manifest['stack_size']),
entry_point=manifest['entry_point'],
source_files=source_files,
mmio_regions=mmio_regions,
rot_services=rot_services,
extern_sids=manifest.get('extern_sids', []),
irqs=irqs
)
@property
def sids(self):
return [rot_srv.name for rot_srv in self.rot_services]
@property
def autogen_folder(self):
return os.path.abspath(os.path.dirname(self.file))
def find_dependencies(self, manifests):
"""
Find other manifests which holds Root of Trust Services that
are declared as extern in this manifest
:param manifests: list of manifests to filter
:return: list of manifest's names that holds current
extern Root of Trust Services
"""
manifests = [man for man in manifests if man != self]
extern_sids_set = set(self.extern_sids)
return [manifest.name for manifest in manifests
if extern_sids_set.intersection(set(manifest.sids))]
def templates_to_files(self, templates, templates_base, output_dir):
"""
Translates a list of partition templates to file names
:param templates: List of partition templates
:param output_dir: Output directory (Default is autogen folder property)
:return: Dictionary of template to output file translation
"""
generated_files = {}
for t in templates:
fname = os.path.relpath(t, templates_base)
_tpl = fname.replace('NAME', self.name.lower())
full_path = path_join(
output_dir,
os.path.splitext(_tpl)[0]
)
generated_files[t] = full_path
return generated_files
def check_circular_call_dependencies(manifests):
"""
Check if there is a circular dependency between the partitions
described by the manifests.
A circular dependency might happen if there is a scenario in which a
partition calls a Root of Trust Service in another partition which than
calls another Root of Trust Service which resides in the
originating partition.
For example: Partition A has a Root of Trust Service A1 and extern sid B1,
partition B has a Root of Trust Service B1 and extern sid A1.
:param manifests: List of the partition manifests.
:return: True if a circular dependency exists, false otherwise.
"""
# Construct a call graph.
call_graph = {}
for manifest in manifests:
call_graph[manifest.name] = {
'calls': manifest.find_dependencies(manifests),
'called_by': set()
}
for manifest_name in call_graph:
for called in call_graph[manifest_name]['calls']:
call_graph[called]['called_by'].add(manifest_name)
# Run topological sort on the call graph.
while len(call_graph) > 0:
# Find all the nodes that aren't called by anyone and
# therefore can be removed.
nodes_to_remove = [x for x in list(call_graph.keys()) if
len(call_graph[x]['called_by']) == 0]
# If no node can be removed we have a circle.
if not nodes_to_remove:
return True
# Remove the nodes.
for node in nodes_to_remove:
for called in call_graph[node]['calls']:
call_graph[called]['called_by'].remove(node)
call_graph.pop(node)
return False
def validate_partition_manifests(manifests):
"""
Check the correctness of the manifests list
(no conflicts, no missing elements, etc.)
:param manifests: List of the partition manifests
"""
for manifest in manifests:
assert isinstance(manifest, Manifest)
partitions_names = {}
partitions_ids = {}
rot_service_ids = {}
rot_service_names = {}
rot_service_signals = {}
irq_signals = {}
irq_numbers = {}
all_extern_sids = set()
spe_contained_manifests = []
for manifest in manifests:
# Make sure the partition names are unique.
if manifest.name in partitions_names:
raise ValueError(
'Partition name {} is not unique, '
'found in both {} and {}.'.format(
manifest.name,
partitions_names[manifest.name],
manifest.file
)
)
partitions_names[manifest.name] = manifest.file
# Make sure the partition ID's are unique.
if manifest.id in partitions_ids:
raise ValueError(
'Partition id {} is not unique, '
'found in both {} and {}.'.format(
manifest.id,
partitions_ids[manifest.id],
manifest.file
)
)
partitions_ids[manifest.id] = manifest.file
is_nspe_callabale = False
# Make sure all the Root of Trust Service IDs and signals are unique.
for rot_service in manifest.rot_services:
if rot_service.name in rot_service_names:
raise ValueError(
'Root of Trust Service name {} is found '
'in both {} and {}.'.format(
rot_service.name,
rot_service_names[rot_service.name],
manifest.file
)
)
rot_service_names[rot_service.name] = manifest.file
if rot_service.signal in rot_service_signals:
raise ValueError(
'Root of Trust Service signal {} is found '
'in both {} and {}.'.format(
rot_service.signal,
rot_service_signals[rot_service.signal],
manifest.file
)
)
rot_service_signals[rot_service.signal] = manifest.file
if rot_service.numeric_id in rot_service_ids:
raise ValueError(
'Root of Trust Service identifier {} is found '
'in both {} and {}.'.format(
rot_service.numeric_id,
rot_service_ids[rot_service.numeric_id],
manifest.file
)
)
rot_service_ids[rot_service.numeric_id] = manifest.file
is_nspe_callabale |= rot_service.nspe_callable
if not is_nspe_callabale:
spe_contained_manifests.append(manifest)
# Make sure all the IRQ signals and line-numbers are unique.
for irq in manifest.irqs:
if irq.signal in irq_signals:
raise ValueError(
'IRQ signal {} is found in both {} and {}.'.format(
irq.signal,
irq_signals[irq.signal],
manifest.file
)
)
irq_signals[irq.signal] = manifest.file
if irq.line_num in irq_numbers:
raise ValueError(
'IRQ line number {} is found in both {} and {}.'.format(
irq.line_num,
irq_numbers[irq.line_num],
manifest.file
)
)
irq_numbers[irq.line_num] = manifest.file
all_extern_sids.update(manifest.extern_sids)
# Check that all the external SIDs can be found.
declared_sids = set(rot_service_names.keys())
for manifest in manifests:
extern_sids = set(manifest.extern_sids)
if not extern_sids.issubset(declared_sids):
missing_sids = extern_sids.difference(declared_sids)
raise ValueError(
"External SID(s) {} required by {} can't be found in "
"any partition manifest.".format(
', '.join(missing_sids), manifest.file)
)
if check_circular_call_dependencies(manifests):
raise ValueError(
"Detected a circular call dependency between the partitions.")
for manifest in spe_contained_manifests:
rot_services = set([service.name for service in manifest.rot_services])
if not rot_services.intersection(all_extern_sids) and len(
manifest.irqs) == 0:
raise ValueError(
'Partition {} (defined by {}) is not accessible from NSPE '
'and not referenced by any other partition.'.format(
manifest.name,
manifest.file
)
)
def generate_source_files(
templates,
render_args,
output_folder,
extra_filters=None
):
"""
Generate SPM common C code from manifests using given templates
:param templates: Dictionary of template and their auto-generated products
:param render_args: Dictionary of arguments that should be passed to render
:param output_folder: Output directory for file generation
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: Path to generated folder containing common generated files
"""
rendered_files = []
templates_dirs = list(
set([os.path.dirname(path) for path in templates])
)
template_files = {os.path.basename(t): t for t in templates}
# Load templates for the code generation.
env = Environment(
loader=FileSystemLoader(templates_dirs),
lstrip_blocks=True,
trim_blocks=True,
undefined=StrictUndefined
)
if extra_filters:
env.filters.update(extra_filters)
for tf in template_files:
template = env.get_template(tf)
rendered_files.append(
(templates[template_files[tf]], template.render(**render_args)))
rendered_file_dir = os.path.dirname(templates[template_files[tf]])
if not os.path.exists(rendered_file_dir):
os.makedirs(rendered_file_dir)
if not os.path.exists(output_folder):
os.makedirs(output_folder)
for fname, data in rendered_files:
with open(fname, 'wt') as fh:
fh.write(data)
return output_folder
def generate_partitions_sources(manifest_files, extra_filters=None):
"""
Process all the given manifest files and generate C code from them
:param manifest_files: List of manifest files
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: List of paths to the generated files
"""
# Construct a list of all the manifests and sids.
manifests = []
for manifest_file in manifest_files:
manifest = Manifest.from_json(manifest_file)
manifests.append(manifest)
generated_folders = set()
for manifest in manifests:
manifest_output_folder = manifest.autogen_folder
render_args = {
'partition': manifest,
'dependent_partitions': manifest.find_dependencies(manifests),
'script_ver': __version__
}
manifest_output_folder = generate_source_files(
manifest.templates_to_files(MANIFEST_TEMPLATES,
TEMPLATES_DIR,
manifest_output_folder),
render_args,
manifest_output_folder,
extra_filters=extra_filters
)
generated_folders.add(manifest_output_folder)
return list(generated_folders)
def generate_psa_setup(manifest_files, output_dir, weak_setup,
extra_filters=None):
"""
Process all the given manifest files and generate C setup code from them
:param manifest_files: List of manifest files
:param output_dir: Output directory for the generated files
:param weak_setup: Is the functions/data in the setup file weak
(can be overridden by another setup file)
:param extra_filters: Dictionary of extra filters to use in the rendering
process
:return: path to the setup generated files
"""
autogen_folder = output_dir
templates_dict = {
t: path_join(autogen_folder,
os.path.relpath(os.path.splitext(t)[0], TEMPLATES_DIR))
for t in COMMON_TEMPLATES
}
complete_source_list = list(templates_dict.values())
# Construct lists of all the manifests and mmio_regions.
region_list = []
manifests = []
for manifest_file in manifest_files:
manifest_obj = Manifest.from_json(manifest_file)
manifests.append(manifest_obj)
for region in manifest_obj.mmio_regions:
region_list.append(region)
complete_source_list.extend(
list(manifest_obj.templates_to_files(
MANIFEST_TEMPLATES,
TEMPLATES_DIR,
manifest_obj.autogen_folder).values())
)
# Validate the correctness of the manifest collection.
validate_partition_manifests(manifests)
render_args = {
'partitions': manifests,
'regions': region_list,
'region_pair_list': list(itertools.combinations(region_list, 2)),
'weak': weak_setup,
'script_ver': __version__
}
return generate_source_files(
templates_dict,
render_args,
autogen_folder,
extra_filters=extra_filters
)
def manifests_discovery(root_dir):
manifest_files = set()
for root, dirs, files in os.walk(root_dir):
to_add = [path_join(root, f) for f in
fnmatch.filter(files, MANIFEST_FILE_PATTERN) if
'TARGET_IGNORE' not in root]
manifest_files.update(to_add)
return list(manifest_files)
def generate_psa_code():
# Find all manifest files in the mbed-os tree
manifest_files = manifests_discovery(MBED_OS_ROOT)
# Generate partition code for each manifest file
generate_partitions_sources(manifest_files)
test_manifest_files = sorted(
[path for path in manifest_files if 'TESTS' in path])
system_manifest_files = sorted(
list(set(manifest_files) - set(test_manifest_files)))
# Generate default system psa setup file (only system partitions)
generate_psa_setup(system_manifest_files, SPM_CORE_ROOT, weak_setup=True)
tests_dir_content = [path_join(SPM_TESTS_ROOT, f) for f in
os.listdir(SPM_TESTS_ROOT)]
spm_tests = [path for path in tests_dir_content if os.path.isdir(path)]
# Build a dictionary for test partition in the form of:
# { test_root: manifest_list }
# For each test generate specific psa setup file (system + test partitions)
tests_dict = {test_root: [] for test_root in spm_tests}
for test_root in spm_tests:
tests_dict[test_root] = [manifest_path for manifest_path in
test_manifest_files if
test_root in manifest_path]
if not tests_dict[test_root]:
continue
tests_dict[test_root] += system_manifest_files
generate_psa_setup(sorted(tests_dict[test_root]), test_root,
weak_setup=False)
if __name__ == '__main__':
generate_psa_code()

View File

@ -24,10 +24,11 @@ import pytest
from jinja2.defaults import DEFAULT_FILTERS
from .test_data import *
from tools.spm.generate_partition_code import *
from tools.psa.mbed_spm_tfm_common import *
from tools.psa.generate_mbed_spm_partition_code import *
# Imported again as a module for monkey-patching
import tools.spm.generate_partition_code as generate_partition_code
import tools.psa.generate_mbed_spm_partition_code as generate_partition_code
SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))

View File

@ -1,23 +0,0 @@
# Copyright (c) 2017-2018 ARM Limited
#
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from .generate_partition_code import \
generate_partitions_sources, generate_psa_setup
__all__ = [
'generate_partitions_sources',
'generate_psa_setup',
]

View File

@ -1,196 +0,0 @@
{
"$schema": "http://json-schema.org/draft-04/schema#",
"description": "schema for a partition description.",
"type": "object",
"required": ["name", "type", "priority", "id", "entry_point", "stack_size", "heap_size", "source_files"],
"anyOf": [
{"required" : ["services"]},
{"required" : ["irqs"]}
],
"properties": {
"name": {
"description": "Alphanumeric C macro for referring to a partition. (all capital)",
"$ref": "#/definitions/c_macro"
},
"type": {
"description": "Whether the partition is unprivileged or part of the trusted computing base.",
"enum": ["APPLICATION-ROT", "PSA-ROT"]
},
"priority": {
"description": "Partition task priority.",
"enum": ["LOW", "NORMAL", "HIGH"]
},
"id": {
"description": "Partition numeric unique positive identifier. (must be a positive 8 bytes hex string)",
"type": "string",
"pattern": "^0x[0-7][0-9a-fA-F]{7}$"
},
"entry_point": {
"description": "C symbol name of the partition's entry point. (unmangled, use extern C if needed)",
"$ref": "#/definitions/c_symbol"
},
"stack_size": {
"description": "Partition's task stack size in bytes.",
"$ref": "#/definitions/positive_integer_or_hex_string"
},
"heap_size": {
"description": "Partition's task heap size in bytes.",
"$ref": "#/definitions/positive_integer_or_hex_string"
},
"mmio_regions": {
"description": "List of Memory-Mapped IO region objects which the partition has access to.",
"type": "array",
"items": {
"anyOf": [{
"$ref": "#/definitions/named_region"
},
{
"$ref": "#/definitions/numbered_region"
}
]
},
"uniqueItems": true
},
"services": {
"description": "List of RoT Service objects which the partition implements.",
"type": "array",
"items": {
"$ref": "#/definitions/service"
},
"uniqueItems": true
},
"extern_sids": {
"description": "List of SID which the partition code depends on and allowed to access.",
"type": "array",
"items": {
"$ref": "#/definitions/c_macro"
},
"uniqueItems": true
},
"source_files": {
"description": "List of source files relative to PSA Manifest file. A Secure Partition is built from explicit file list.",
"type": "array",
"items": {
"type": "string",
"pattern": "^[a-zA-Z0-9-_./]+$"
},
"minItems": 1,
"uniqueItems": true
},
"irqs": {
"description": "List of IRQ objects which the partition implements.",
"type": "array",
"items": {
"$ref": "#/definitions/irq"
},
"uniqueItems": true
}
},
"definitions": {
"c_macro": {
"type": "string",
"pattern": "^[A-Z_][A-Z0-9_]*$"
},
"c_symbol": {
"type": "string",
"pattern": "^[a-zA-Z_][a-zA-Z0-9_]*$"
},
"hex_string": {
"type": "string",
"pattern": "^0x(0*[1-9a-fA-F][0-9a-fA-F]*)$",
"minLength": 3,
"maxLength": 10
},
"positive_integer": {
"type": "integer",
"exclusiveMinimum": true,
"minimum": 0
},
"positive_integer_or_hex_string": {
"oneOf": [{
"$ref": "#/definitions/positive_integer"
},
{
"$ref": "#/definitions/hex_string"
}
]
},
"named_region": {
"description": "MMIO region which is described by it's C macro name and access permissions.",
"required": ["name", "permission"],
"properties": {
"name": {
"description": "Alphanumeric C macro for referring to the region.",
"$ref": "#/definitions/c_macro"
},
"permission": {
"description": "Access permissions for the region.",
"enum": ["READ-ONLY", "READ-WRITE"]
}
}
},
"numbered_region": {
"description": "MMIO region which is described by it's base address, size and access permissions.",
"required": ["base", "size", "permission"],
"properties": {
"base": {
"description": "The base address of the region.",
"$ref": "#/definitions/hex_string"
},
"size": {
"description": "Size in bytes of the region.",
"$ref": "#/definitions/positive_integer_or_hex_string"
},
"permission": {
"description": "Access permissions for the region.",
"enum": ["READ-ONLY", "READ-WRITE"]
}
}
},
"service": {
"required": ["name", "identifier", "non_secure_clients", "signal"],
"properties": {
"name": {
"description": "Alphanumeric C macro for referring to a RoT Service from source code (all capital)",
"$ref": "#/definitions/c_macro"
},
"identifier": {
"description": "The integer value of the NAME field",
"$ref": "#/definitions/positive_integer_or_hex_string"
},
"non_secure_clients": {
"description": "Denote whether the RoT Service is exposed to non-secure clients.",
"type": "boolean"
},
"signal": {
"description": "Alphanumeric C macro for referring to the RoT Service's signal value. (all capital)",
"$ref": "#/definitions/c_macro"
},
"minor_version": {
"description": "Optional: Minor version number of the RoT Service's interface.",
"$ref": "#/definitions/positive_integer",
"default": 1
},
"minor_policy": {
"description": "Optional: Minor version policy to apply on connections to the RoT Service.",
"enum": ["STRICT", "RELAXED"],
"default": "STRICT"
}
}
},
"irq": {
"required": ["line_num", "signal"],
"properties": {
"line_num": {
"description": "Interrupt line number for registering to ISR table entry and enable/disable the specific IRQ once received.",
"type": "integer",
"minimum": 0
},
"signal": {
"description": "Alphanumeric C macro for referring to the IRQ's signal value. (all capital)",
"$ref": "#/definitions/c_macro"
}
}
}
}
}