Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 1 | """ |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 2 | Copyright 2024 Cypress Semiconductor Corporation (an Infineon company) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 3 | or an affiliate of Cypress Semiconductor Corporation. All rights reserved. |
| 4 | |
| 5 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | you may not use this file except in compliance with the License. |
| 7 | You may obtain a copy of the License at |
| 8 | |
| 9 | http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | |
| 11 | Unless required by applicable law or agreed to in writing, software |
| 12 | distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | See the License for the specific language governing permissions and |
| 15 | limitations under the License. |
| 16 | """ |
| 17 | |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 18 | import argparse |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 19 | import json |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 20 | import sys |
| 21 | import os |
| 22 | |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 23 | |
| 24 | def load_json(file_path): |
| 25 | """ |
| 26 | Loads JSON from file. |
| 27 | """ |
| 28 | |
| 29 | data_json = None |
| 30 | |
| 31 | try: |
| 32 | with open(file_path, encoding="utf-8") as file: |
| 33 | data_json = json.load(file) |
| 34 | |
| 35 | except FileNotFoundError: |
| 36 | print(f'\nERROR: Cannot find {file_path}') |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 37 | sys.exit(1) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 38 | |
| 39 | return data_json |
| 40 | |
| 41 | |
| 42 | class FieldsValidator: |
| 43 | """ |
| 44 | Validation of required fields and their cross-dependencies. |
| 45 | """ |
| 46 | |
| 47 | @staticmethod |
| 48 | def validate(feature_json, properties_json): |
| 49 | """ |
| 50 | Check 'target' and properties of a platform. |
| 51 | """ |
| 52 | p_target = properties_json.get('target') |
| 53 | if p_target is None: |
| 54 | raise AttributeError('Field "target" must be present in platform_properties.json') |
| 55 | |
| 56 | f_target = feature_json.get('target') |
| 57 | if f_target is None: |
| 58 | raise AttributeError('Field "target" must be present in a feature_config.json') |
| 59 | |
| 60 | if f_target not in p_target: |
| 61 | raise AttributeError('Target in feature config is not correct.' |
| 62 | ' It must be among the target list of platform_properties.json') |
| 63 | |
| 64 | f_security_setup = feature_json.get('security_setup') |
| 65 | p_security_setup = properties_json.get('security_setup') |
| 66 | |
| 67 | if f_security_setup: |
| 68 | |
| 69 | if p_security_setup is None: |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 70 | print("\nThis platform doesn't have any 'secure_setup' features") |
| 71 | sys.exit(1) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 72 | |
| 73 | if f_security_setup.get('hw_rollback_prot'): |
| 74 | if p_security_setup.get('hw_rollback_prot') is None: |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 75 | print("\nThis platform doesn't have HW anti roll-back counter") |
| 76 | sys.exit(1) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 77 | |
| 78 | if f_security_setup.get('hw_crypto_acceleration'): |
| 79 | if p_security_setup.get('hw_crypto_acceleration') is None: |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 80 | print("\nThe platform doesn't support HW crypto acceleration") |
| 81 | sys.exit(1) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 82 | |
| 83 | if f_security_setup.get('validate_upgrade').get('value') is False: |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 84 | if f_security_setup.get('validate_boot').get('value'): |
| 85 | print("\nERROR: Boot slot validation cannot be enabled if upgrade "\ |
| 86 | "slot validation is disabled") |
| 87 | sys.exit(1) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 88 | |
| 89 | |
| 90 | class FeatureProcessor: |
| 91 | |
| 92 | """ |
| 93 | The general handler of all needed fields and filling the new mk-file. |
| 94 | """ |
| 95 | |
| 96 | settings_dict = { |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 97 | 'validate_boot' : 'MCUBOOT_SKIP_BOOT_VALIDATION', |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 98 | 'validate_upgrade' : 'MCUBOOT_SKIP_UPGRADE_VALIDATION', |
| 99 | 'dependency_check' : 'MCUBOOT_DEPENDENCY_CHECK', |
| 100 | 'serial_logging' : 'MCUBOOT_LOG_LEVEL', |
Roman Okhrimenko | 883cb5b | 2024-03-28 17:22:33 +0200 | [diff] [blame] | 101 | 'watch_dog_timer' : 'USE_WDT', |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 102 | 'hw_rollback_prot' : 'USE_HW_ROLLBACK_PROT', |
| 103 | 'hw_crypto_acceleration' : "USE_CRYPTO_HW", |
| 104 | 'sw_downgrade_prev' : 'USE_SW_DOWNGRADE_PREV', |
| 105 | 'ram_app_staging' : 'USE_STAGE_RAM_APPS', |
| 106 | 'xip' : 'USE_XIP', |
| 107 | 'image_encryption' : 'ENC_IMG', |
| 108 | 'fault_injection_hardening' : 'FIH_PROFILE_LEVEL', |
| 109 | 'combine_hex' : 'COMBINE_HEX', |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 110 | 'hw_key' : 'USE_HW_KEY', |
| 111 | 'built_in_keys' : 'USE_BUILT_IN_KEYS' |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 112 | } |
| 113 | |
| 114 | debug_level_dict = { |
| 115 | 'off' : '_OFF', |
| 116 | 'error' : '_ERROR', |
| 117 | 'warning' : '_WARNING', |
| 118 | 'info' : '_INFO', |
| 119 | 'debug' : '_DEBUG' |
| 120 | } |
| 121 | |
| 122 | fih_level_dict = { |
| 123 | 'off' : 'OFF', |
| 124 | 'low' : 'LOW', |
| 125 | 'medium' : 'MEDIUM', |
| 126 | 'high' : 'HIGH' |
| 127 | } |
| 128 | |
| 129 | def __init__(self, output_name): |
| 130 | self.out_f = output_name |
| 131 | |
| 132 | @staticmethod |
| 133 | def generate_header_guard(): |
| 134 | """ |
| 135 | Print header line at the begining of a mk-file |
| 136 | """ |
| 137 | guard_lines = ('# AUTO-GENERATED FILE, DO NOT EDIT.' |
| 138 | ' ALL CHANGES WILL BE LOST! #\n\n') |
| 139 | |
| 140 | return guard_lines |
| 141 | |
| 142 | @staticmethod |
| 143 | def insert_res(val_to_check) -> str: |
| 144 | """ |
| 145 | Simlpe check result and return the string with value. |
| 146 | """ |
| 147 | return f' := {1 if val_to_check else 0}\n' |
| 148 | |
| 149 | @staticmethod |
| 150 | def insert_inverted_res(val_to_check) -> str: |
| 151 | """ |
| 152 | Simlpe check result and return the string with inverted value. |
| 153 | """ |
| 154 | return f' := {0 if val_to_check else 1}\n' |
| 155 | |
| 156 | def __prnt_dict_primitive_key(self, dict_feature_config, settings_dict_key, f_out): |
| 157 | """ |
| 158 | Print kyes of 'feature_config' with bool type of 'value' |
| 159 | """ |
| 160 | val = dict_feature_config.get(settings_dict_key).get('value') |
| 161 | |
| 162 | if isinstance(val, bool): |
| 163 | |
| 164 | # invert because variable use 'skip' command |
| 165 | need_invertion = set(("validate_boot", "validate_upgrade")) |
| 166 | |
| 167 | f_out.write(self.settings_dict[settings_dict_key]) |
| 168 | |
| 169 | if settings_dict_key not in need_invertion: |
| 170 | f_out.write(FeatureProcessor.insert_res(val)) |
| 171 | else: |
| 172 | f_out.write(FeatureProcessor.insert_inverted_res(val)) |
| 173 | |
| 174 | |
| 175 | def __gen_fih_level(self, fih_value): |
| 176 | """ |
| 177 | Print only FIH_ |
| 178 | """ |
| 179 | res = f"{self.settings_dict['fault_injection_hardening']} ?= "\ |
| 180 | f"{self.fih_level_dict[fih_value]}\n" |
| 181 | |
| 182 | return res |
| 183 | |
| 184 | def __gen_debug_level(self, logging_value): |
| 185 | """ |
| 186 | Print only MCUBOOT_LOG_LEVEL |
| 187 | """ |
| 188 | param_txt = self.settings_dict['serial_logging'] |
| 189 | res_str = f"{param_txt} ?= {param_txt}{self.debug_level_dict[logging_value]}\n" |
| 190 | |
| 191 | return res_str |
| 192 | |
| 193 | |
| 194 | def __handle_dictionary(self, f_dict, f_out): |
| 195 | """ |
| 196 | Handle any dictionary of 'feature_config' |
| 197 | """ |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 198 | dont_print_list = set(("validation_key", "encryption_key", "version", "description", "target")) |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 199 | |
| 200 | for k in f_dict: |
| 201 | |
| 202 | if k not in dont_print_list: |
| 203 | self.__prnt_dict_primitive_key(f_dict, k, f_out) |
| 204 | |
| 205 | if k == 'fault_injection_hardening': |
| 206 | f_out.write(self.__gen_fih_level(f_dict.get(k).get("value"))) |
| 207 | |
| 208 | if k == 'serial_logging': |
| 209 | f_out.write(self.__gen_debug_level(f_dict.get(k).get("value"))) |
| 210 | |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 211 | if k == "validation_key": |
| 212 | value = f_dict.get(k).get("value") |
| 213 | if value != '': |
| 214 | f_out.write(f'ECDSA_PUBLIC_KEY={value}\n') |
| 215 | |
| 216 | if k == "encryption_key": |
| 217 | value = f_dict.get(k).get("value") |
| 218 | if value != '': |
| 219 | f_out.write(f'ENC_PRIVATE_KEY={value}\n') |
| 220 | |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 221 | |
| 222 | def make_file_generate(self, feature_json): |
| 223 | """ |
| 224 | Processing all keys and creation of a mk-file |
| 225 | """ |
| 226 | |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 227 | out_dir = os.path.dirname(self.out_f) |
| 228 | |
| 229 | if not os.path.exists(out_dir): |
| 230 | os.mkdir(out_dir) |
| 231 | |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 232 | with open(self.out_f, "w", encoding='UTF-8') as f_out: |
| 233 | f_out.write(FeatureProcessor.generate_header_guard()) |
| 234 | |
| 235 | f_security_setup_dict = feature_json.get('security_setup') |
| 236 | |
| 237 | # handling of 'security_setup' section |
| 238 | if f_security_setup_dict: |
| 239 | self.__handle_dictionary(f_security_setup_dict, f_out) |
| 240 | |
| 241 | self.__handle_dictionary(feature_json, f_out) |
| 242 | |
| 243 | |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 244 | def cli(): |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 245 | parser = argparse.ArgumentParser(description='Feature config parser to run from CLI') |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 246 | |
INFINEON\DovhalA | 3b578f3 | 2024-11-29 01:06:04 +0200 | [diff] [blame^] | 247 | parser.add_argument('-f', '--feature_config', required=True, |
| 248 | help='Feature configuration file path') |
| 249 | parser.add_argument('-p', '--platform_properties', required=True, |
| 250 | help='Platform properties file path') |
| 251 | parser.add_argument('-n', '--output_name', required=True, |
| 252 | help='The name of the make file that will be generated') |
| 253 | parser.add_argument('other_args', nargs='?', |
| 254 | help='Ignore all other arguments, such as: run') |
| 255 | |
| 256 | args = parser.parse_args() |
| 257 | |
| 258 | run(args.feature_config, args.platform_properties, args.output_name) |
| 259 | |
| 260 | # run('C:/Work/mcuboot/mtb-example-bootloader-solution/platforms/PSC3/feature_config.json', |
| 261 | # 'C:/Work/mcuboot/mtb-example-bootloader-solution/mtb_shared/mcuboot/1.9.3-ifx-boy2-es10/boot/cypress/platforms/memory/PSC3/flashmap/platform_properties.json', |
| 262 | # 'C:/Work/mcuboot/mtb-example-bootloader-solution/platforms/PSC3/feature_config.mk') |
Roman Okhrimenko | dc0ca08 | 2023-06-21 20:49:51 +0300 | [diff] [blame] | 263 | |
| 264 | |
| 265 | def run(feature_config, platform_properties, output_name): |
| 266 | """ |
| 267 | The main CLI command to run mk-file generation |
| 268 | """ |
| 269 | |
| 270 | feature_config_json = load_json(feature_config) |
| 271 | platform_properties_json = load_json(platform_properties) |
| 272 | |
| 273 | FieldsValidator.validate(feature_config_json, platform_properties_json) |
| 274 | |
| 275 | fprocessor = FeatureProcessor(output_name) |
| 276 | fprocessor.make_file_generate(feature_config_json) |
| 277 | |
| 278 | |
| 279 | if __name__ == '__main__': |
| 280 | cli() |