1# This file includes the operations with eFuses for ESP32-H2 chip 2# 3# SPDX-FileCopyrightText: 2021-2022 Espressif Systems (Shanghai) CO LTD 4# 5# SPDX-License-Identifier: GPL-2.0-or-later 6 7import argparse 8import os # noqa: F401. It is used in IDF scripts 9import traceback 10 11import espsecure 12 13import esptool 14 15from . import fields 16from .. import util 17from ..base_operations import ( 18 add_common_commands, 19 add_force_write_always, 20 burn_bit, 21 burn_block_data, 22 burn_efuse, 23 check_error, 24 dump, 25 read_protect_efuse, 26 summary, 27 write_protect_efuse, 28) 29 30 31def protect_options(p): 32 p.add_argument( 33 "--no-write-protect", 34 help="Disable write-protecting of the key. The key remains writable. " 35 "(The keys use the RS coding scheme that does not support post-write " 36 "data changes. Forced write can damage RS encoding bits.) " 37 "The write-protecting of keypurposes does not depend on the option, " 38 "it will be set anyway.", 39 action="store_true", 40 ) 41 p.add_argument( 42 "--no-read-protect", 43 help="Disable read-protecting of the key. The key remains readable software." 44 "The key with keypurpose[USER, RESERVED and *_DIGEST] will remain " 45 "readable anyway. For the rest keypurposes the read-protection will be " 46 "defined the option (Read-protect by default).", 47 action="store_true", 48 ) 49 50 51def add_commands(subparsers, efuses): 52 add_common_commands(subparsers, efuses) 53 burn_key = subparsers.add_parser( 54 "burn_key", help="Burn the key block with the specified name" 55 ) 56 protect_options(burn_key) 57 add_force_write_always(burn_key) 58 burn_key.add_argument( 59 "block", 60 help="Key block to burn", 61 action="append", 62 choices=efuses.BLOCKS_FOR_KEYS, 63 ) 64 burn_key.add_argument( 65 "keyfile", 66 help="File containing 256 bits of binary key data", 67 action="append", 68 type=argparse.FileType("rb"), 69 ) 70 burn_key.add_argument( 71 "keypurpose", 72 help="Purpose to set.", 73 action="append", 74 choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME, 75 ) 76 for _ in efuses.BLOCKS_FOR_KEYS: 77 burn_key.add_argument( 78 "block", 79 help="Key block to burn", 80 nargs="?", 81 action="append", 82 metavar="BLOCK", 83 choices=efuses.BLOCKS_FOR_KEYS, 84 ) 85 burn_key.add_argument( 86 "keyfile", 87 help="File containing 256 bits of binary key data", 88 nargs="?", 89 action="append", 90 metavar="KEYFILE", 91 type=argparse.FileType("rb"), 92 ) 93 burn_key.add_argument( 94 "keypurpose", 95 help="Purpose to set.", 96 nargs="?", 97 action="append", 98 metavar="KEYPURPOSE", 99 choices=fields.EfuseKeyPurposeField.KEY_PURPOSES_NAME, 100 ) 101 102 burn_key_digest = subparsers.add_parser( 103 "burn_key_digest", 104 help="Parse a RSA public key and burn the digest to key efuse block", 105 ) 106 protect_options(burn_key_digest) 107 add_force_write_always(burn_key_digest) 108 burn_key_digest.add_argument( 109 "block", 110 help="Key block to burn", 111 action="append", 112 choices=efuses.BLOCKS_FOR_KEYS, 113 ) 114 burn_key_digest.add_argument( 115 "keyfile", 116 help="Key file to digest (PEM format)", 117 action="append", 118 type=argparse.FileType("rb"), 119 ) 120 burn_key_digest.add_argument( 121 "keypurpose", 122 help="Purpose to set.", 123 action="append", 124 choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES, 125 ) 126 for _ in efuses.BLOCKS_FOR_KEYS: 127 burn_key_digest.add_argument( 128 "block", 129 help="Key block to burn", 130 nargs="?", 131 action="append", 132 metavar="BLOCK", 133 choices=efuses.BLOCKS_FOR_KEYS, 134 ) 135 burn_key_digest.add_argument( 136 "keyfile", 137 help="Key file to digest (PEM format)", 138 nargs="?", 139 action="append", 140 metavar="KEYFILE", 141 type=argparse.FileType("rb"), 142 ) 143 burn_key_digest.add_argument( 144 "keypurpose", 145 help="Purpose to set.", 146 nargs="?", 147 action="append", 148 metavar="KEYPURPOSE", 149 choices=fields.EfuseKeyPurposeField.DIGEST_KEY_PURPOSES, 150 ) 151 152 p = subparsers.add_parser( 153 "set_flash_voltage", 154 help="Permanently set the internal flash voltage regulator " 155 "to either 1.8V, 3.3V or OFF. This means GPIO45 can be high or low " 156 "at reset without changing the flash voltage.", 157 ) 158 p.add_argument("voltage", help="Voltage selection", choices=["1.8V", "3.3V", "OFF"]) 159 160 p = subparsers.add_parser( 161 "burn_custom_mac", help="Burn a 48-bit Custom MAC Address to EFUSE BLOCK3." 162 ) 163 p.add_argument( 164 "mac", 165 help="Custom MAC Address to burn given in hexadecimal format with bytes " 166 "separated by colons (e.g. AA:CD:EF:01:02:03). " 167 "Final CUSTOM_MAC = CUSTOM_MAC[48] + MAC_EXT[16]", 168 type=fields.base_fields.CheckArgValue(efuses, "CUSTOM_MAC"), 169 ) 170 add_force_write_always(p) 171 172 p = subparsers.add_parser("get_custom_mac", help="Prints the Custom MAC Address.") 173 174 175def burn_custom_mac(esp, efuses, args): 176 efuses["CUSTOM_MAC"].save(args.mac) 177 if not efuses.burn_all(check_batch_mode=True): 178 return 179 get_custom_mac(esp, efuses, args) 180 print("Successful") 181 182 183def get_custom_mac(esp, efuses, args): 184 print("Custom MAC Address: {}".format(efuses["CUSTOM_MAC"].get())) 185 186 187def set_flash_voltage(esp, efuses, args): 188 raise esptool.FatalError("set_flash_voltage is not supported!") 189 190 191def adc_info(esp, efuses, args): 192 print("") 193 # fmt: off 194 if efuses["BLOCK2_VERSION"].get() == 1: 195 print("Temperature Sensor Calibration = {}C".format(efuses["TEMP_SENSOR_CAL"].get())) 196 197 print("") 198 print("ADC1 readings stored in efuse BLOCK2:") 199 print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC1_MODE0_D1"].get())) 200 print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC1_MODE0_D2"].get())) 201 202 print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC1_MODE1_D1"].get())) 203 print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC1_MODE1_D2"].get())) 204 205 print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC1_MODE2_D1"].get())) 206 print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC1_MODE2_D2"].get())) 207 208 print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC1_MODE3_D1"].get())) 209 print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC1_MODE3_D2"].get())) 210 211 print("") 212 print("ADC2 readings stored in efuse BLOCK2:") 213 print(" MODE0 D1 reading (250mV): {}".format(efuses["ADC2_MODE0_D1"].get())) 214 print(" MODE0 D2 reading (600mV): {}".format(efuses["ADC2_MODE0_D2"].get())) 215 216 print(" MODE1 D1 reading (250mV): {}".format(efuses["ADC2_MODE1_D1"].get())) 217 print(" MODE1 D2 reading (800mV): {}".format(efuses["ADC2_MODE1_D2"].get())) 218 219 print(" MODE2 D1 reading (250mV): {}".format(efuses["ADC2_MODE2_D1"].get())) 220 print(" MODE2 D2 reading (1000mV): {}".format(efuses["ADC2_MODE2_D2"].get())) 221 222 print(" MODE3 D1 reading (250mV): {}".format(efuses["ADC2_MODE3_D1"].get())) 223 print(" MODE3 D2 reading (2000mV): {}".format(efuses["ADC2_MODE3_D2"].get())) 224 else: 225 print("BLOCK2_VERSION = {}".format(efuses["BLOCK2_VERSION"].get_meaning())) 226 # fmt: on 227 228 229def burn_key(esp, efuses, args, digest=None): 230 if digest is None: 231 datafile_list = args.keyfile[ 232 0 : len([name for name in args.keyfile if name is not None]) : 233 ] 234 else: 235 datafile_list = digest[0 : len([name for name in digest if name is not None]) :] 236 efuses.force_write_always = args.force_write_always 237 block_name_list = args.block[ 238 0 : len([name for name in args.block if name is not None]) : 239 ] 240 keypurpose_list = args.keypurpose[ 241 0 : len([name for name in args.keypurpose if name is not None]) : 242 ] 243 244 util.check_duplicate_name_in_list(block_name_list) 245 if len(block_name_list) != len(datafile_list) or len(block_name_list) != len( 246 keypurpose_list 247 ): 248 raise esptool.FatalError( 249 "The number of blocks (%d), datafile (%d) and keypurpose (%d) " 250 "should be the same." 251 % (len(block_name_list), len(datafile_list), len(keypurpose_list)) 252 ) 253 254 print("Burn keys to blocks:") 255 for block_name, datafile, keypurpose in zip( 256 block_name_list, datafile_list, keypurpose_list 257 ): 258 efuse = None 259 for block in efuses.blocks: 260 if block_name == block.name or block_name in block.alias: 261 efuse = efuses[block.name] 262 if efuse is None: 263 raise esptool.FatalError("Unknown block name - %s" % (block_name)) 264 num_bytes = efuse.bit_len // 8 265 266 block_num = efuses.get_index_block_by_name(block_name) 267 block = efuses.blocks[block_num] 268 269 if digest is None: 270 data = datafile.read() 271 else: 272 data = datafile 273 274 print(" - %s" % (efuse.name), end=" ") 275 revers_msg = None 276 if efuses[block.key_purpose_name].need_reverse(keypurpose): 277 revers_msg = "\tReversing byte order for AES-XTS hardware peripheral" 278 data = data[::-1] 279 print("-> [%s]" % (util.hexify(data, " "))) 280 if revers_msg: 281 print(revers_msg) 282 if len(data) != num_bytes: 283 raise esptool.FatalError( 284 "Incorrect key file size %d. Key file must be %d bytes (%d bits) " 285 "of raw binary key data." % (len(data), num_bytes, num_bytes * 8) 286 ) 287 288 if efuses[block.key_purpose_name].need_rd_protect(keypurpose): 289 read_protect = False if args.no_read_protect else True 290 else: 291 read_protect = False 292 write_protect = not args.no_write_protect 293 294 # using efuse instead of a block gives the advantage of checking it as the whole field. 295 efuse.save(data) 296 297 disable_wr_protect_key_purpose = False 298 if efuses[block.key_purpose_name].get() != keypurpose: 299 if efuses[block.key_purpose_name].is_writeable(): 300 print( 301 "\t'%s': '%s' -> '%s'." 302 % ( 303 block.key_purpose_name, 304 efuses[block.key_purpose_name].get(), 305 keypurpose, 306 ) 307 ) 308 efuses[block.key_purpose_name].save(keypurpose) 309 disable_wr_protect_key_purpose = True 310 else: 311 raise esptool.FatalError( 312 "It is not possible to change '%s' to '%s' because write " 313 "protection bit is set." % (block.key_purpose_name, keypurpose) 314 ) 315 else: 316 print("\t'%s' is already '%s'." % (block.key_purpose_name, keypurpose)) 317 if efuses[block.key_purpose_name].is_writeable(): 318 disable_wr_protect_key_purpose = True 319 320 if disable_wr_protect_key_purpose: 321 print("\tDisabling write to '%s'." % block.key_purpose_name) 322 efuses[block.key_purpose_name].disable_write() 323 324 if read_protect: 325 print("\tDisabling read to key block") 326 efuse.disable_read() 327 328 if write_protect: 329 print("\tDisabling write to key block") 330 efuse.disable_write() 331 print("") 332 333 if not write_protect: 334 print("Keys will remain writeable (due to --no-write-protect)") 335 if args.no_read_protect: 336 print("Keys will remain readable (due to --no-read-protect)") 337 338 if not efuses.burn_all(check_batch_mode=True): 339 return 340 print("Successful") 341 342 343def burn_key_digest(esp, efuses, args): 344 digest_list = [] 345 datafile_list = args.keyfile[ 346 0 : len([name for name in args.keyfile if name is not None]) : 347 ] 348 block_list = args.block[ 349 0 : len([block for block in args.block if block is not None]) : 350 ] 351 for block_name, datafile in zip(block_list, datafile_list): 352 efuse = None 353 for block in efuses.blocks: 354 if block_name == block.name or block_name in block.alias: 355 efuse = efuses[block.name] 356 if efuse is None: 357 raise esptool.FatalError("Unknown block name - %s" % (block_name)) 358 num_bytes = efuse.bit_len // 8 359 digest = espsecure._digest_sbv2_public_key(datafile) 360 if len(digest) != num_bytes: 361 raise esptool.FatalError( 362 "Incorrect digest size %d. Digest must be %d bytes (%d bits) of raw " 363 "binary key data." % (len(digest), num_bytes, num_bytes * 8) 364 ) 365 digest_list.append(digest) 366 burn_key(esp, efuses, args, digest=digest_list) 367 368 369def espefuse(esp, efuses, args, command): 370 parser = argparse.ArgumentParser() 371 subparsers = parser.add_subparsers(dest="operation") 372 add_commands(subparsers, efuses) 373 try: 374 cmd_line_args = parser.parse_args(command.split()) 375 except SystemExit: 376 traceback.print_stack() 377 raise esptool.FatalError('"{}" - incorrect command'.format(command)) 378 if cmd_line_args.operation == "execute_scripts": 379 configfiles = cmd_line_args.configfiles 380 index = cmd_line_args.index 381 # copy arguments from args to cmd_line_args 382 vars(cmd_line_args).update(vars(args)) 383 if cmd_line_args.operation == "execute_scripts": 384 cmd_line_args.configfiles = configfiles 385 cmd_line_args.index = index 386 if cmd_line_args.operation is None: 387 parser.print_help() 388 parser.exit(1) 389 operation_func = globals()[cmd_line_args.operation] 390 # each 'operation' is a module-level function of the same name 391 operation_func(esp, efuses, cmd_line_args) 392 393 394def execute_scripts(esp, efuses, args): 395 efuses.batch_mode_cnt += 1 396 del args.operation 397 scripts = args.scripts 398 del args.scripts 399 400 for file in scripts: 401 with open(file.name, "r") as file: 402 exec(compile(file.read(), file.name, "exec")) 403 404 if args.debug: 405 for block in efuses.blocks: 406 data = block.get_bitstring(from_read=False) 407 block.print_block(data, "regs_for_burn", args.debug) 408 409 efuses.batch_mode_cnt -= 1 410 if not efuses.burn_all(check_batch_mode=True): 411 return 412 print("Successful") 413