1#!/usr/bin/python3
2
3"""
4Copyright (c) 2020 Cypress Semiconductor Corporation
5Licensed under the Apache License, Version 2.0 (the "License");
6you may not use this file except in compliance with the License.
7You may obtain a copy of the License at
8    http://www.apache.org/licenses/LICENSE-2.0
9Unless required by applicable law or agreed to in writing, software
10distributed under the License is distributed on an "AS IS" BASIS,
11WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12See the License for the specific language governing permissions and
13limitations under the License.
14"""
15
16import os
17import platform
18import sys, argparse
19import subprocess
20import json
21import shutil
22from cysecuretools import CySecureTools
23from intelhex import IntelHex
24
25# This script signs an output hex file using cysecuretools based
26# on the input parameters
27# Example Usage:
28# psoc64_postbuild.py --core CM4 \
29# --secure-boot-stage single \
30# --policy policy_single_CM0_CM4 \
31# --target cyb06xxa \
32# --toolchain-path ModusToolbox/tools_2.0/gcc-7.2.1 \
33# --toolchain GCC_ARM \
34# --build-dir Hello_World/build/CY8CKIT-064B0S2-4343W/Debug \
35# --app-name mtb-example-psoc6-hello-world \
36# --cm0-app-path ./libs/psoc6cm0p/COMPONENT_CM0P_SECURE \
37# --cm0-app-name psoc6_02_cm0p_secure
38
39
40def myargs(argv):
41    parser = argparse.ArgumentParser(add_help=False)
42    parser.add_argument('-h', '--help',
43                        dest='show_help',
44                        action='help',
45                        help='Print this help message and exit')
46
47    parser.add_argument('--policy-path',
48                        dest='policy_path',
49                        action='store',
50                        type=str,
51                        help="Path to policy file",
52                        required=False)
53
54    parser.add_argument('-p', '--policy',
55                        dest='policy_file',
56                        action='store',
57                        type=str,
58                        help="Device policy file",
59                        required=True)
60
61    parser.add_argument('-t', '--target',
62                        dest='target_name',
63                        action='store',
64                        type=str,
65                        help="Target name according to cysecuretools",
66                        required=True)
67
68    parser.add_argument('--toolchain-path',
69                        dest='toolchain_path',
70                        action='store',
71                        type=str,
72                        help="Path to the toolchain tools to use for .hex file generation",
73                        required=True)
74
75    parser.add_argument('--toolchain',
76                        dest='toolchain',
77                        action='store',
78                        type=str,
79                        help="Actual toolchain that was used to build the application",
80                        required=True)
81
82    parser.add_argument('-b', '--build-dir',
83                        dest='build_dir',
84                        action='store',
85                        type=str,
86                        help="Path of build directory to find application hex files",
87                        required=True)
88
89    parser.add_argument('-a', '--app-name',
90                        dest='app_name',
91                        action='store',
92                        type=str,
93                        help="Name of application",
94                        required=True)
95
96    parser.add_argument('-c', '--cm0-app-path',
97                        dest='cm0_app_path',
98                        action='store',
99                        type=str,
100                        help="Path where CM0 hex file is located",
101                        required=False)
102
103    parser.add_argument('--cm0-app-name',
104                        dest='cm0_app_name',
105                        action='store',
106                        type=str,
107                        help="Name of CM0 hex file",
108                        required=False)
109
110    parser.add_argument('--core',
111                        dest='core',
112                        action='store',
113                        type=str,
114                        help="Building for CM0/CM4",
115                        required=True)
116
117    parser.add_argument('-s', '--secure-boot-stage',
118                        dest='secure_boot_stage',
119                        action='store',
120                        type=str,
121                        help="Single/Multi stage scheme",
122                        required=True)
123
124
125    options = parser.parse_args(argv)
126    return options
127
128def exec_shell_command(cmd):
129    print("Executing command: {}".format(' '.join(cmd)))
130    p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
131                              stderr=subprocess.STDOUT)
132    for line in iter(p.stdout.readline, b''):
133        print("{}".format(line.decode('utf-8')), end='')
134    p.stdout.close()
135    ret = p.wait()
136    print("Command completed (ret={})".format(ret))
137    return ret
138
139def main(argv):
140
141    options = myargs(argv)
142    print("options: {}".format(options))
143
144    if not options.policy_path:
145        options.policy_path = 'policy'
146
147    tools = CySecureTools(options.target_name, options.policy_path + "/" + options.policy_file +'.json')
148    if (options.toolchain == 'ARM'):
149        fromelf_cmd = options.toolchain_path + "/bin/fromelf"
150        app_elf_file = options.build_dir + "/" + options.app_name + ".elf"
151        fromelf_result_dir = options.build_dir + "/" + "fromelf_result"
152        # Check if gcc tools path is valid
153        if (os.path.isdir(options.toolchain_path) == False):
154            print("ERROR: 'ARM Compiler' tools folder not found in path: {}".format(options.toolchain_path))
155            exit(-1)
156
157        # Check if elf is valid
158        if (os.path.isfile(app_elf_file) == False):
159            print("ERROR: ELF file not found in path: {}\r\n".format(app_elf_file))
160            exit(-1)
161
162        # Split elf file into sections
163        shell_cmd = [ fromelf_cmd, '--i32', '--output=' + fromelf_result_dir, app_elf_file ]
164        ret = exec_shell_command(shell_cmd)
165        if (ret != 0):
166            exit(ret)
167
168        em_eeprom_hex = fromelf_result_dir + "/" + ".cy_em_eeprom"
169        app_hex_path = options.build_dir + '/' + options.app_name + '.hex'
170        if (os.path.isfile(em_eeprom_hex) == True):
171            sections_list = [f for f in os.listdir(fromelf_result_dir) if os.path.isfile(os.path.join(fromelf_result_dir, f))]
172            sections_list.remove('.cy_em_eeprom')
173            flash = IntelHex()
174
175            for section in sections_list:
176                sect = IntelHex(fromelf_result_dir + "/" + section)
177                flash.merge(sect, overlap='replace')
178
179            flash.write_hex_file(app_hex_path, False)
180
181        CM0_app_src_path = options.cm0_app_path + '/' + options.cm0_app_name + '.hex'
182        CM0_app_dst_path = options.build_dir + '/' + options.cm0_app_name + '.hex'
183
184        # CySecureTools Image ID for CM4 Applications is
185        # 1) 1 for single-stage,
186        # 2) 16 in case of multi-stage,
187        # Image ID for CM0 Applications is always 1
188        if (options.core == "CM4"):
189            if (options.secure_boot_stage == "single"):
190                # Sign CM4 image
191                tools.sign_image(app_hex_path, 1)
192            else:
193                # Sign CM4 image
194                tools.sign_image(app_hex_path, 16)
195                # Make a copy of CM0P app image in build folder
196                shutil.copy2(CM0_app_src_path, CM0_app_dst_path)
197
198                # Sign CM0 image
199                tools.sign_image(CM0_app_dst_path, 1)
200
201                # Merge CM0, CM4 into a single hex file
202                ihex = IntelHex()
203                ihex.padding = 0x00
204                ihex.loadfile(app_hex_path, 'hex'); \
205                        ihex.merge(IntelHex(CM0_app_dst_path), 'ignore'); \
206                        ihex.write_hex_file(app_hex_path, write_start_addr=False, byte_count=16)
207        else:
208            tools.sign_image(app_hex_path, 1)
209
210        if (os.path.isfile(em_eeprom_hex) == True):
211            # Add emulated EEPROM Section back
212            flash = IntelHex(app_hex_path)
213            eeprom = IntelHex(em_eeprom_hex)
214            flash.merge(eeprom)
215            flash.write_hex_file(app_hex_path, False)
216    else:
217        gcc_objcopy_eabi_cmd = options.toolchain_path + '/bin/arm-none-eabi-objcopy'
218        app_elf_file = options.build_dir + "/" + options.app_name +".elf"
219
220        # Check if gcc tools path is valid
221        if(os.path.isdir(options.toolchain_path)==False):
222            print("ERROR: GCC tools folder not found in path: {}".format(options.toolchain_path))
223            exit(-1)
224
225        # Check if elf is valid
226        if(os.path.isfile(app_elf_file) == False):
227            print("ERROR: ELF file not found in path: {}\r\n".format(app_elf_file))
228            exit(-1)
229
230        # Strip away emulated EEPROM section from hex file before signing
231        shell_cmd = [ gcc_objcopy_eabi_cmd, '-R', '.cy_em_eeprom', '-O', 'ihex', app_elf_file, options.build_dir + "/" + options.app_name + ".hex" ]
232        ret = exec_shell_command(shell_cmd)
233        if(ret != 0):
234            exit(ret)
235
236        # Store emulated eeprom section in a seperate hex file
237        shell_cmd = [ gcc_objcopy_eabi_cmd, '-j', '.cy_em_eeprom', '-O', 'ihex', options.build_dir + "/" + options.app_name + ".elf", options.build_dir + "/em_eeprom.hex" ]
238        ret = exec_shell_command(shell_cmd)
239        if(ret != 0):
240            exit(ret)
241
242        app_hex_path = options.build_dir + '/' + options.app_name + '.hex'
243        CM0_app_src_path = options.cm0_app_path + '/' + options.cm0_app_name +  '.hex'
244        CM0_app_dst_path = options.build_dir + '/' + options.cm0_app_name + '.hex'
245
246        # CySecureTools Image ID for CM4 Applications is
247        # 1) 1 for single-stage,
248        # 2) 16 in case of multi-stage,
249        # Image ID for CM0 Applications is always 1
250        if(options.core == "CM4"):
251            if(options.secure_boot_stage == "single"):
252                # Sign CM4 image
253                tools.sign_image(app_hex_path,1)
254            else:
255                # Sign CM4 image
256                tools.sign_image(app_hex_path,16)
257                # Make a copy of CM0P app image in build folder
258                shutil.copy2(CM0_app_src_path, CM0_app_dst_path)
259
260                # Sign CM0 image
261                tools.sign_image(CM0_app_dst_path,1)
262
263                # Merge CM0, CM4 into a single hex file
264                ihex = IntelHex()
265                ihex.padding = 0x00
266                ihex.loadfile(app_hex_path, 'hex'); \
267                ihex.merge(IntelHex(CM0_app_dst_path), 'ignore'); \
268                ihex.write_hex_file(app_hex_path, write_start_addr=False, byte_count=16)
269        else:
270            tools.sign_image(app_hex_path,1)
271
272        # Add emulated EEPROM Section back
273        flash = IntelHex(app_hex_path)
274        eeprom = IntelHex(options.build_dir + "/em_eeprom.hex")
275        flash.merge(eeprom)
276        flash.write_hex_file(app_hex_path, False)
277
278    exit(0)
279
280if __name__ == "__main__":
281    main(sys.argv[1:])
282