Skip to content

Commit 9ef2666

Browse files
Michael Eskowitzozersa
authored andcommitted
scripts: imgtool: Add script to combine multiple image as single
MCUBoot multi image support mode requires each image be procedded individually that requires multiple signature check during boot. If there be 4 image it will require 4 times signature validatation. This feature increase boot time and depend on the project long boot time may not be expected. To provide a solution for this case we propose to - Generate each image as regular - Concatanate them and re generate a main mcuboot image. ------------------------- | Header | ------------------------- | SubImage (optional) | | (Header+Data+Footer) | ------------------------- | SubImage (optional) | | (Header+Data+Footer) | ------------------------- | ..... | ------------------------- | Footer | ------------------------- During boot time if top level image be validated sub image can be just copied to the target location without to recheck them. To provide this feature there will be two commit 1- Provide a script that combine images 2- Update mcuboot source code to process subimages This commit is for #1, in this commit we are adding a script which called as combine_images.py. The usage of the script is: python combine_images.py --config combine_images.yaml --imgtool imgtool --output <outfolder> combine_images.yaml file is added as reference file it need to be updated as per of the project need. Signed-off-by: Sadik Ozer <sadik.ozer@analog.com> Signed-off-by: Michael Eskowitz <Michael.Eskowitz@analog.com>
1 parent 3c8e571 commit 9ef2666

File tree

2 files changed

+231
-0
lines changed

2 files changed

+231
-0
lines changed

scripts/combine_images.py

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
#! /usr/bin/env python3
2+
#
3+
# Copyright (C) 2025 Analog Devices, Inc.
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
import argparse
20+
import os
21+
import pathlib
22+
import subprocess
23+
import sys
24+
25+
import yaml
26+
import shutil
27+
28+
29+
# Function definitions
30+
31+
32+
def main():
33+
global img_tool
34+
global output_dir
35+
global config_path
36+
37+
parser = argparse.ArgumentParser(description="Create application package", allow_abbrev=False)
38+
parser.add_argument('--config', help="The path to config yaml file", required=True)
39+
parser.add_argument('--imgtool', help="The path to ImgTool", required=True)
40+
parser.add_argument('--output', help="Output directory", required=True)
41+
42+
args = parser.parse_args()
43+
44+
if not os.path.isfile(args.config):
45+
print(f"Error: The config file '{args.config}' does not exist")
46+
return
47+
48+
config_path = os.path.dirname(os.path.abspath(args.config))
49+
print(f"config_path: {config_path}")
50+
51+
with open(args.config) as config_file:
52+
config = yaml.safe_load(config_file)
53+
54+
img_tool = args.imgtool
55+
if img_tool.endswith('.py'):
56+
if not os.path.exists(img_tool): # Is python file exist?
57+
print(f"Error: The '{img_tool}' not found")
58+
return
59+
else:
60+
if not shutil.which(img_tool): # Is binary file exist?
61+
print(f"Error: The '{img_tool}' not found in the path")
62+
return
63+
64+
65+
output_dir = args.output
66+
os.makedirs(output_dir, exist_ok=True)
67+
68+
parse_app_pack(config, None)
69+
70+
print(f"\nCreated {package_name}")
71+
72+
73+
def verify_file_exists(filename):
74+
filepath = pathlib.Path(filename)
75+
76+
if not filepath.exists():
77+
print("ERROR: File " + filename + " not found")
78+
sys.exit(1)
79+
80+
81+
def parse_app_pack(app_pack, name):
82+
header = None
83+
image = []
84+
imgtype = 0
85+
global package_name
86+
87+
for key in app_pack:
88+
if key.endswith("_pack"):
89+
imagename, imagetype = parse_app_pack(app_pack[key], name)
90+
image += [imagename]
91+
if key.lower() == "header":
92+
header = app_pack[key]
93+
if key.lower() == "image":
94+
image_path = os.path.join(config_path, app_pack[key])
95+
verify_file_exists(image_path)
96+
image += [image_path]
97+
if key.lower() == "outputfile":
98+
name = app_pack[key]
99+
100+
# Exit if header or image are not specified
101+
if (header is None) or (image is None):
102+
return (None, 0)
103+
104+
operation = 0
105+
106+
cmd = []
107+
if img_tool.endswith('.py'):
108+
cmd += [sys.executable]
109+
110+
cmd += [img_tool]
111+
cmd += ["sign"]
112+
113+
for key in header:
114+
if key == "align":
115+
cmd += ["--align", str(header[key])]
116+
elif key == "aes-gcm-key":
117+
operation += 2
118+
gcm_key_path = os.path.join(config_path, header[key])
119+
verify_file_exists(gcm_key_path)
120+
cmd += ["--aes-gcm-key", gcm_key_path]
121+
elif key == "aes-kw-key":
122+
operation += 2
123+
kw_key_path = os.path.join(config_path, header[key])
124+
verify_file_exists(kw_key_path)
125+
cmd += ["--aes-kw-key", kw_key_path]
126+
elif key == "header-size":
127+
cmd += ["--header-size", hex(header[key])]
128+
elif key == "load-addr":
129+
cmd += ["--load-addr", hex(header[key])]
130+
elif key == "pad-header":
131+
if header[key] is True:
132+
cmd += ["--pad-header"]
133+
elif key == "private_signing_key":
134+
operation += 1
135+
private_key_path = os.path.join(config_path, header[key])
136+
verify_file_exists(private_key_path)
137+
cmd += ["--key", private_key_path]
138+
elif key == "public-key-format":
139+
cmd += ["--public-key-format", header[key]]
140+
elif key == "slot-size":
141+
cmd += ["--slot-size", hex(header[key])]
142+
elif key == "version":
143+
cmd += ["--version", header[key]]
144+
else:
145+
print("Unknown argument: " + key)
146+
147+
# If there are multiple input files they must be combined for signing or encryption
148+
if len(image) > 1:
149+
combined_images = os.path.join(output_dir, name + ".bin")
150+
151+
with open(combined_images, 'wb') as outfile:
152+
for fname in image:
153+
with open(fname, 'rb') as infile:
154+
outfile.write(infile.read())
155+
infile.close()
156+
outfile.close()
157+
158+
image_input = combined_images
159+
else:
160+
image_input = image[0]
161+
162+
if operation > 1:
163+
image_output = os.path.join(output_dir, name + "_signed_encrypted.bin")
164+
else:
165+
image_output = os.path.join(output_dir, name + "_signed.bin")
166+
167+
cmd += [image_input]
168+
cmd += [image_output]
169+
170+
print(f'Calling imgtool to generate file {image_output}')
171+
package_name = image_output
172+
subprocess.run(cmd)
173+
174+
return (image_output, imgtype)
175+
176+
177+
if __name__ == "__main__":
178+
main()

scripts/combine_images.yaml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
#! /usr/bin/env python3
2+
#
3+
# Copyright (C) 2025 Analog Devices, Inc.
4+
#
5+
# SPDX-License-Identifier: Apache-2.0
6+
#
7+
# Licensed under the Apache License, Version 2.0 (the "License");
8+
# you may not use this file except in compliance with the License.
9+
# You may obtain a copy of the License at
10+
#
11+
# http://www.apache.org/licenses/LICENSE-2.0
12+
#
13+
# Unless required by applicable law or agreed to in writing, software
14+
# distributed under the License is distributed on an "AS IS" BASIS,
15+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16+
# See the License for the specific language governing permissions and
17+
# limitations under the License.
18+
19+
combined_app_pack:
20+
outputfile: combined
21+
22+
header:
23+
private_signing_key: ../root-rsa-2048.pem
24+
header-size: 0x400
25+
align: 4
26+
load-addr: 0x20080000
27+
pad-header: yes
28+
version: 1.0.0
29+
slot-size: 0x40000
30+
31+
image1_pack:
32+
outputfile: image1
33+
header:
34+
private_signing_key: ../root-rsa-2048.pem
35+
header-size: 0x400
36+
align: 4
37+
load-addr: 0x20010000
38+
pad-header: yes
39+
version: 1.0.0
40+
slot-size: 0x10000
41+
image: image1.bin
42+
43+
image2_pack:
44+
outputfile: image2
45+
header:
46+
private_signing_key: ../root-rsa-2048.pem
47+
header-size: 0x400
48+
align: 4
49+
load-addr: 0x20020000
50+
pad-header: yes
51+
version: 1.0.0
52+
slot-size: 0x10000
53+
image: image2.bin

0 commit comments

Comments
 (0)