Skip to content

Commit 4fc878e

Browse files
romkuz01Michael Schwarcz
authored andcommitted
Add circular call dependency check between partitions
1 parent aed1e72 commit 4fc878e

File tree

3 files changed

+304
-8
lines changed

3 files changed

+304
-8
lines changed

tools/spm/generate_partition_code.py

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
#!/usr/bin/python
2-
import fnmatch
32
import glob
43
import itertools
54
import json
@@ -56,7 +55,7 @@ def __init__(
5655
Secure Function C'tor (Aligned with json schema)
5756
5857
:param name: Secure function identifier (available to user)
59-
:param identifier: Secure function numeric enumaration.
58+
:param identifier: Secure function numeric enumeration.
6059
:param signal: Secure Function identifier inside the partition
6160
:param non_secure_clients: True to allow connections from non-secure
6261
partitions
@@ -286,6 +285,7 @@ def from_json(cls, manifest_file, skip_src=False):
286285
Load a partition manifest file
287286
288287
:param manifest_file: Manifest file path
288+
:param skip_src: Ignore the `source_files` entry
289289
:return: Manifest object
290290
"""
291291

@@ -402,6 +402,48 @@ def templates_to_files(self, templates, output_dir):
402402
return generated_files
403403

404404

405+
def check_circular_call_dependencies(manifests):
406+
"""
407+
Check if there is a circular dependency between the partitions described by the manifests.
408+
A circular dependency might happen if there is a scenario in which a partition calls a secure function in another
409+
partition which than calls another secure function which resides in the originating partition.
410+
For example: Partition A has a secure function A1 and extern sfid B1, partition B has a secure function B1 and
411+
extern sfid A1.
412+
413+
:param manifests: List of the partition manifests.
414+
:return: True if a circular dependency exists, false otherwise.
415+
"""
416+
417+
# Construct a call graph.
418+
call_graph = {}
419+
for manifest in manifests:
420+
call_graph[manifest.name] = {
421+
'calls': manifest.find_dependencies(manifests),
422+
'called_by': set()
423+
}
424+
for manifest_name in call_graph:
425+
for called in call_graph[manifest_name]['calls']:
426+
call_graph[called]['called_by'].add(manifest_name)
427+
428+
# Run topological sort on the call graph.
429+
while len(call_graph) > 0:
430+
# Find all the nodes that aren't called by anyone and
431+
# therefore can be removed.
432+
nodes_to_remove = filter(lambda x: len(call_graph[x]['called_by']) == 0, call_graph.keys())
433+
434+
# If no node can be removed we have a circle.
435+
if not nodes_to_remove:
436+
return True
437+
438+
# Remove the nodes.
439+
for node in nodes_to_remove:
440+
for called in call_graph[node]['calls']:
441+
call_graph[called]['called_by'].remove(node)
442+
call_graph.pop(node)
443+
444+
return False
445+
446+
405447
def validate_partition_manifests(manifests):
406448
"""
407449
Check the correctness of the manifests list
@@ -514,6 +556,9 @@ def validate_partition_manifests(manifests):
514556
', '.join(missing_sfids), manifest.file)
515557
)
516558

559+
if check_circular_call_dependencies(manifests):
560+
raise ValueError("Detected a circular call dependency between the partitions.")
561+
517562

518563
def get_latest_timestamp(*files):
519564
"""

tools/test/spm/test_data.py

Lines changed: 173 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -98,11 +98,177 @@
9898
'irqs': [
9999
{"line_num": 22, "signal": "ISR22"},
100100
{"line_num": 23, "signal": "ISR23"}
101+
]
102+
}
103+
]
104+
105+
manifests_for_circular_call_dependency_checks = [
106+
{
107+
'name': 'PARTITION1',
108+
'id': '0x7FFFFFFF',
109+
'type': 'SECURE',
110+
'priority': 'NORMAL',
111+
'entry_point': 'test_main',
112+
'stack_size': 512,
113+
'heap_size': 2048,
114+
'source_files': ['src1.cpp'],
115+
'secure_functions': [
116+
{
117+
'name': 'SFID1',
118+
'identifier': '0x00000001',
119+
'signal': 'SFID1',
120+
'non_secure_clients': False
121+
},
122+
{
123+
'name': 'SFID2',
124+
'identifier': '0x00000002',
125+
'signal': 'SFID2',
126+
'non_secure_clients': False
127+
}
128+
],
129+
'extern_sfids': ['SFID3', 'SFID4']
130+
},
131+
{
132+
'name': 'PARTITION2',
133+
'id': '0x7FFFFFFE',
134+
'type': 'SECURE',
135+
'priority': 'NORMAL',
136+
'entry_point': 'test_main',
137+
'stack_size': 512,
138+
'heap_size': 2048,
139+
'source_files': ['src2.cpp'],
140+
'secure_functions': [
141+
{
142+
'name': 'SFID3',
143+
'identifier': '0x00000003',
144+
'signal': 'SFID3',
145+
'non_secure_clients': False
146+
},
147+
{
148+
'name': 'SFID4',
149+
'identifier': '0x00000004',
150+
'signal': 'SFID4',
151+
'non_secure_clients': False
152+
}
101153
],
102154
'extern_sfids': ['SFID1', 'SFID2']
155+
},
156+
{
157+
'name': 'PARTITION3',
158+
'id': '0x7FFFFFFD',
159+
'type': 'SECURE',
160+
'priority': 'NORMAL',
161+
'entry_point': 'test_main',
162+
'stack_size': 512,
163+
'heap_size': 2048,
164+
'source_files': ['src3.cpp'],
165+
'secure_functions': [
166+
{
167+
'name': 'SFID5',
168+
'identifier': '0x00000005',
169+
'signal': 'SFID5',
170+
'non_secure_clients': False
171+
}
172+
],
173+
'extern_sfids': ['SFID7']
174+
},
175+
{
176+
'name': 'PARTITION4',
177+
'id': '0x7FFFFFFC',
178+
'type': 'SECURE',
179+
'priority': 'NORMAL',
180+
'entry_point': 'test_main',
181+
'stack_size': 512,
182+
'heap_size': 2048,
183+
'source_files': ['src4.cpp'],
184+
'secure_functions': [
185+
{
186+
'name': 'SFID6',
187+
'identifier': '0x00000006',
188+
'signal': 'SFID6',
189+
'non_secure_clients': False
190+
},
191+
{
192+
'name': 'SFID7',
193+
'identifier': '0x00000007',
194+
'signal': 'SFID7',
195+
'non_secure_clients': False
196+
},
197+
],
198+
'extern_sfids': ['SFID9']
199+
},
200+
{
201+
'name': 'PARTITION5',
202+
'id': '0x7FFFFFFB',
203+
'type': 'SECURE',
204+
'priority': 'NORMAL',
205+
'entry_point': 'test_main',
206+
'stack_size': 512,
207+
'heap_size': 2048,
208+
'source_files': ['src5.cpp'],
209+
'secure_functions': [
210+
{
211+
'name': 'SFID8',
212+
'identifier': '0x00000008',
213+
'signal': 'SFID8',
214+
'non_secure_clients': False
215+
},
216+
{
217+
'name': 'SFID9',
218+
'identifier': '0x00000009',
219+
'signal': 'SFID9',
220+
'non_secure_clients': False
221+
}
222+
],
223+
'extern_sfids': ['SFID5']
224+
},
225+
{
226+
'name': 'PARTITION6',
227+
'id': '0x7FFFFFFA',
228+
'type': 'SECURE',
229+
'priority': 'NORMAL',
230+
'entry_point': 'test_main',
231+
'stack_size': 512,
232+
'heap_size': 2048,
233+
'source_files': ['src6.cpp'],
234+
'secure_functions': [
235+
{
236+
'name': 'SFID10',
237+
'identifier': '0x0000000A',
238+
'signal': 'SFID10',
239+
'non_secure_clients': False
240+
},
241+
{
242+
'name': 'SFID11',
243+
'identifier': '0x0000000B',
244+
'signal': 'SFID11',
245+
'non_secure_clients': False
246+
}
247+
],
248+
'extern_sfids': ['SFID7', 'SFID5']
249+
},
250+
{
251+
'name': 'PARTITION7',
252+
'id': '0x7FFFFFF9',
253+
'type': 'SECURE',
254+
'priority': 'NORMAL',
255+
'entry_point': 'test_main',
256+
'stack_size': 512,
257+
'heap_size': 2048,
258+
'source_files': ['src6.cpp'],
259+
'secure_functions': [
260+
{
261+
'name': 'SFID12',
262+
'identifier': '0x0000000C',
263+
'signal': 'SFID12',
264+
'non_secure_clients': False
265+
}
266+
]
103267
}
104268
]
105269

270+
271+
106272
invalid_minor_version_policy_sf = [
107273
{
108274
'name': 'SFID1',
@@ -286,6 +452,13 @@
286452
{{"}" if loop.last else "},"}}
287453
{% endfor %}
288454
],
455+
{% if partition.extern_sfids %}
456+
"extern_sfids": [
457+
{% for ext_sfid in partition.extern_sfids %}
458+
"{{ext_sfid}}"{{"" if loop.last else ","}}
459+
{% endfor %}
460+
],
461+
{% endif %}
289462
"source_files": [
290463
{% for src in partition.source_files %}
291464
"{{src|basename}}"{{"" if loop.last else ","}}
@@ -297,11 +470,6 @@
297470
"line_num": {{irq.line_num}},
298471
"signal": "{{irq.signal}}"
299472
{{"}" if loop.last else "},"}}
300-
{% endfor %}
301-
],
302-
"extern_sfids": [
303-
{% for ext_sfid in partition.extern_sfids %}
304-
"{{ext_sfid}}"{{"" if loop.last else ","}}
305473
{% endfor %}
306474
]
307475
}

tools/test/spm/test_generate_partition_code.py

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,14 @@ def test_valid_json(temp_test_data):
317317
r'External SFID\(s\) .* can\'t be found in any partition manifest.'
318318
),
319319
id='orphan_extern_ids'
320+
),
321+
pytest.param(
322+
dict(manifests[1], extern_sfids=[manifests[0]['secure_functions'][0]['name']]),
323+
(
324+
ValueError,
325+
r'Detected a circular call dependency between the partitions.'
326+
),
327+
id='circular_call_dependency'
320328
)
321329
]
322330
)
@@ -707,7 +715,7 @@ def test_generate_partitions_sources(monkeypatch, test_template_setup):
707715
still exist and that modified times of the generated files didn't
708716
change.
709717
710-
:param monkeypatch: The 'monkeypath' fixture
718+
:param monkeypatch: The 'monkeypatch' fixture
711719
(https://docs.pytest.org/en/latest/monkeypatch.html).
712720
:param test_template_setup: The 'test_template_setup' fixture.
713721
:return:
@@ -763,3 +771,78 @@ def test_generate_partitions_sources(monkeypatch, test_template_setup):
763771

764772
for f in os.listdir(spm_output_dir):
765773
assert autogen_files[f] == os.path.getmtime(os.path.join(spm_output_dir, f))
774+
775+
776+
circular_call_dependency_params = {
777+
'no manifests': {
778+
'manifests': [],
779+
'result': False
780+
},
781+
'one manifest': {
782+
'manifests': ['PARTITION1'],
783+
'result': False
784+
},
785+
'2 manifests with dependency': {
786+
'manifests': ['PARTITION1', 'PARTITION2'],
787+
'result': True
788+
},
789+
'2 manifests without dependency': {
790+
'manifests': ['PARTITION1', 'PARTITION3'],
791+
'result': False
792+
},
793+
'5 manifests with dependency': {
794+
'manifests': ['PARTITION1', 'PARTITION3', 'PARTITION4', 'PARTITION5', 'PARTITION6'],
795+
'result': True
796+
},
797+
'5 manifests without dependency': {
798+
'manifests': ['PARTITION1', 'PARTITION3', 'PARTITION4', 'PARTITION6', 'PARTITION7'],
799+
'result': False
800+
}
801+
}
802+
803+
804+
@pytest.fixture(params=circular_call_dependency_params.values(),
805+
ids=circular_call_dependency_params.keys())
806+
def circular_dependencies(request, tmpdir_factory):
807+
"""
808+
Fixture (https://docs.pytest.org/en/latest/fixture.html) function to be
809+
used by the tests.
810+
This fixture function Creates a JSON manifest file from a given partition
811+
dictionary and save it
812+
to a temporary directory.
813+
This fixture uses the 'temp_test_data' fixture.
814+
This fixture is a parametrized fixture
815+
(https://docs.pytest.org/en/latest/fixture.html#parametrizing-fixtures).
816+
The scope of this fixture is a specific test.
817+
818+
:param request: Request object which contain the current parameter from
819+
'circular_call_dependency_params'.
820+
:param temp_test_data: The 'temp_test_data' fixture.
821+
:return: A Dictionary containing these values:
822+
- files - list of manifest filesgenerated
823+
- The expected result from check_circular_call_dependencies
824+
"""
825+
test_dir = tmpdir_factory.mktemp('test_data')
826+
827+
test_manifests = filter(lambda x: x['name'] in request.param['manifests'],
828+
manifests_for_circular_call_dependency_checks)
829+
manifest_files = [
830+
dump_manifest_to_json(manifest, manifest['name'], test_dir) for
831+
manifest in test_manifests]
832+
833+
return {'files': manifest_files, 'result': request.param['result']}
834+
835+
836+
def test_check_circular_call_dependencies(circular_dependencies):
837+
"""
838+
Test detection of circular call dependencies between the partitions.
839+
The test performs the circular call dependency check in a few
840+
predefined partition topologies and compares the result with the expected value.
841+
842+
:param circular_dependencies: the 'circular_dependencies' fixture
843+
:return:
844+
"""
845+
846+
objects = [Manifest.from_json(_file) for _file in circular_dependencies['files']]
847+
848+
assert check_circular_call_dependencies(objects) == circular_dependencies['result']

0 commit comments

Comments
 (0)