1# This file includes the common operations with eFuses for chips 2# 3# SPDX-FileCopyrightText: 2020-2022 Espressif Systems (Shanghai) CO LTD 4# 5# SPDX-License-Identifier: GPL-2.0-or-later 6 7import argparse 8import json 9import sys 10 11from bitstring import BitStream 12 13import esptool 14 15from . import base_fields 16from . import util 17 18 19def add_common_commands(subparsers, efuses): 20 class ActionEfuseValuePair(argparse.Action): 21 def __init__(self, option_strings, dest, nargs=None, **kwargs): 22 self._nargs = nargs 23 self._choices = kwargs.get("efuse_choices") 24 self.efuses = kwargs.get("efuses") 25 del kwargs["efuse_choices"] 26 del kwargs["efuses"] 27 super(ActionEfuseValuePair, self).__init__( 28 option_strings, dest, nargs=nargs, **kwargs 29 ) 30 31 def __call__(self, parser, namespace, values, option_string=None): 32 def check_efuse_name(efuse_name, efuse_list): 33 if efuse_name not in self._choices: 34 raise esptool.FatalError( 35 "Invalid the efuse name '{}'. " 36 "Available the efuse names: {}".format( 37 efuse_name, self._choices 38 ) 39 ) 40 41 efuse_value_pairs = {} 42 if len(values) > 1: 43 if len(values) % 2: 44 raise esptool.FatalError( 45 "The list does not have a valid pair (name value) {}".format( 46 values 47 ) 48 ) 49 for i in range(0, len(values), 2): 50 efuse_name, new_value = values[i : i + 2 :] 51 check_efuse_name(efuse_name, self._choices) 52 check_arg = base_fields.CheckArgValue(self.efuses, efuse_name) 53 efuse_value_pairs[efuse_name] = check_arg(new_value) 54 else: 55 # For the case of compatibility, when only the efuse_name is given 56 # Fields with 'bitcount' and 'bool' types can be without new_value arg 57 efuse_name = values[0] 58 check_efuse_name(efuse_name, self._choices) 59 check_arg = base_fields.CheckArgValue(self.efuses, efuse_name) 60 efuse_value_pairs[efuse_name] = check_arg(None) 61 setattr(namespace, self.dest, efuse_value_pairs) 62 63 burn = subparsers.add_parser( 64 "burn_efuse", help="Burn the efuse with the specified name" 65 ) 66 burn.add_argument( 67 "name_value_pairs", 68 help="Name of efuse register and New value pairs to burn", 69 action=ActionEfuseValuePair, 70 nargs="+", 71 metavar="[EFUSE_NAME VALUE] [{} VALUE".format( 72 " VALUE] [".join([e.name for e in efuses.efuses]) 73 ), 74 efuse_choices=[e.name for e in efuses.efuses], 75 efuses=efuses, 76 ) 77 78 read_protect_efuse = subparsers.add_parser( 79 "read_protect_efuse", 80 help="Disable readback for the efuse with the specified name", 81 ) 82 read_protect_efuse.add_argument( 83 "efuse_name", 84 help="Name of efuse register to burn", 85 nargs="+", 86 choices=[e.name for e in efuses.efuses if e.read_disable_bit is not None], 87 ) 88 89 write_protect_efuse = subparsers.add_parser( 90 "write_protect_efuse", 91 help="Disable writing to the efuse with the specified name", 92 ) 93 write_protect_efuse.add_argument( 94 "efuse_name", 95 help="Name of efuse register to burn", 96 nargs="+", 97 choices=[e.name for e in efuses.efuses if e.write_disable_bit is not None], 98 ) 99 100 burn_block_data = subparsers.add_parser( 101 "burn_block_data", 102 help="Burn non-key data to EFUSE blocks. " 103 "(Don't use this command to burn key data for Flash Encryption or " 104 "ESP32 Secure Boot V1, as the byte order of keys is swapped (use burn_key)).", 105 ) 106 add_force_write_always(burn_block_data) 107 burn_block_data.add_argument( 108 "--offset", "-o", help="Byte offset in the efuse block", type=int, default=0 109 ) 110 burn_block_data.add_argument( 111 "block", 112 help="Efuse block to burn.", 113 action="append", 114 choices=efuses.BURN_BLOCK_DATA_NAMES, 115 ) 116 burn_block_data.add_argument( 117 "datafile", 118 help="File containing data to burn into the efuse block", 119 action="append", 120 type=argparse.FileType("rb"), 121 ) 122 for _ in range(0, len(efuses.BURN_BLOCK_DATA_NAMES)): 123 burn_block_data.add_argument( 124 "block", 125 help="Efuse block to burn.", 126 metavar="BLOCK", 127 nargs="?", 128 action="append", 129 choices=efuses.BURN_BLOCK_DATA_NAMES, 130 ) 131 burn_block_data.add_argument( 132 "datafile", 133 nargs="?", 134 help="File containing data to burn into the efuse block", 135 metavar="DATAFILE", 136 action="append", 137 type=argparse.FileType("rb"), 138 ) 139 140 set_bit_cmd = subparsers.add_parser("burn_bit", help="Burn bit in the efuse block.") 141 add_force_write_always(set_bit_cmd) 142 set_bit_cmd.add_argument( 143 "block", help="Efuse block to burn.", choices=efuses.BURN_BLOCK_DATA_NAMES 144 ) 145 set_bit_cmd.add_argument( 146 "bit_number", 147 help="Bit number in the efuse block [0..BLK_LEN-1]", 148 nargs="+", 149 type=int, 150 ) 151 152 subparsers.add_parser( 153 "adc_info", 154 help="Display information about ADC calibration data stored in efuse.", 155 ) 156 157 dump_cmd = subparsers.add_parser("dump", help="Dump raw hex values of all efuses") 158 dump_cmd.add_argument( 159 "--file_name", 160 help="Saves dump for each block into separate file. Provide the common " 161 "path name /path/blk.bin, it will create: blk0.bin, blk1.bin ... blkN.bin. " 162 "Use burn_block_data to write it back to another chip.", 163 ) 164 165 summary_cmd = subparsers.add_parser( 166 "summary", help="Print human-readable summary of efuse values" 167 ) 168 summary_cmd.add_argument( 169 "--format", 170 help="Select the summary format", 171 choices=["summary", "json"], 172 default="summary", 173 ) 174 summary_cmd.add_argument( 175 "--file", 176 help="File to save the efuse summary", 177 type=argparse.FileType("w"), 178 default=sys.stdout, 179 ) 180 181 execute_scripts = subparsers.add_parser( 182 "execute_scripts", help="Executes scripts to burn at one time." 183 ) 184 execute_scripts.add_argument( 185 "scripts", 186 help="The special format of python scripts.", 187 nargs="+", 188 type=argparse.FileType("r"), 189 ) 190 execute_scripts.add_argument( 191 "--index", 192 help="integer index. " 193 "It allows to retrieve unique data per chip from configfiles " 194 "and then burn them (ex. CUSTOM_MAC, UNIQUE_ID).", 195 type=int, 196 ) 197 execute_scripts.add_argument( 198 "--configfiles", 199 help="List of configfiles with data", 200 nargs="?", 201 action="append", 202 type=argparse.FileType("r"), 203 ) 204 205 check_error_cmd = subparsers.add_parser("check_error", help="Checks eFuse errors") 206 check_error_cmd.add_argument( 207 "--recovery", 208 help="Recovery of BLOCKs after encoding errors", 209 action="store_true", 210 ) 211 212 213def add_force_write_always(p): 214 p.add_argument( 215 "--force-write-always", 216 help="Write the efuse even if it looks like it's already been written, " 217 "or is write protected. Note that this option can't disable write protection, " 218 "or clear any bit which has already been set.", 219 action="store_true", 220 ) 221 222 223def summary(esp, efuses, args): 224 """Print a human-readable summary of efuse contents""" 225 ROW_FORMAT = "%-50s %-50s%s = %s %s %s" 226 human_output = args.format == "summary" 227 json_efuse = {} 228 if args.file != sys.stdout: 229 print("Saving efuse values to " + args.file.name) 230 if human_output: 231 print( 232 ROW_FORMAT.replace("-50", "-12") 233 % ( 234 "EFUSE_NAME (Block)", 235 "Description", 236 "", 237 "[Meaningful Value]", 238 "[Readable/Writeable]", 239 "(Hex Value)", 240 ), 241 file=args.file, 242 ) 243 print("-" * 88, file=args.file) 244 for category in sorted(set(e.category for e in efuses), key=lambda c: c.title()): 245 if human_output: 246 print("%s fuses:" % category.title(), file=args.file) 247 for e in (e for e in efuses if e.category == category): 248 if e.efuse_type.startswith("bytes"): 249 raw = "" 250 else: 251 raw = "({})".format(e.get_bitstring()) 252 (readable, writeable) = (e.is_readable(), e.is_writeable()) 253 if readable and writeable: 254 perms = "R/W" 255 elif readable: 256 perms = "R/-" 257 elif writeable: 258 perms = "-/W" 259 else: 260 perms = "-/-" 261 base_value = e.get_meaning() 262 value = str(base_value) 263 if not readable: 264 value = value.replace("0", "?") 265 if human_output: 266 print( 267 ROW_FORMAT 268 % ( 269 e.get_info(), 270 e.description[:50], 271 "\n " if len(value) > 20 else "", 272 value, 273 perms, 274 raw, 275 ), 276 file=args.file, 277 ) 278 desc_len = len(e.description[50:]) 279 if desc_len: 280 desc_len += 50 281 for i in range(50, desc_len, 50): 282 print( 283 "%-50s %-50s" % ("", e.description[i : (50 + i)]), 284 file=args.file, 285 ) 286 if args.format == "json": 287 json_efuse[e.name] = { 288 "name": e.name, 289 "value": base_value if readable else value, 290 "readable": readable, 291 "writeable": writeable, 292 "description": e.description, 293 "category": e.category, 294 "block": e.block, 295 "word": e.word, 296 "pos": e.pos, 297 "efuse_type": e.efuse_type, 298 "bit_len": e.bit_len, 299 } 300 if human_output: 301 print("", file=args.file) 302 if human_output: 303 print(efuses.summary(), file=args.file) 304 warnings = efuses.get_coding_scheme_warnings() 305 if warnings: 306 print( 307 "WARNING: Coding scheme has encoding bit error warnings", file=args.file 308 ) 309 if args.file != sys.stdout: 310 args.file.close() 311 print("Done") 312 if args.format == "json": 313 json.dump(json_efuse, args.file, sort_keys=True, indent=4) 314 print("") 315 316 317def dump(esp, efuses, args): 318 """Dump raw efuse data registers""" 319 # Using --debug option allows to print dump. 320 # Nothing to do here. The log will be printed 321 # during EspEfuses.__init__() in self.read_blocks() 322 if args.file_name: 323 # save dump to the file 324 for block in efuses.blocks: 325 file_dump_name = args.file_name 326 place_for_index = file_dump_name.find(".bin") 327 file_dump_name = ( 328 file_dump_name[:place_for_index] 329 + str(block.id) 330 + file_dump_name[place_for_index:] 331 ) 332 print(file_dump_name) 333 with open(file_dump_name, "wb") as f: 334 block.get_bitstring().byteswap() 335 block.get_bitstring().tofile(f) 336 337 338def burn_efuse(esp, efuses, args): 339 def print_attention(blocked_efuses_after_burn): 340 if len(blocked_efuses_after_burn): 341 print( 342 " ATTENTION! This BLOCK uses NOT the NONE coding scheme " 343 "and after 'BURN', these efuses can not be burned in the feature:" 344 ) 345 for i in range(0, len(blocked_efuses_after_burn), 5): 346 print( 347 " ", 348 "".join("{}".format(blocked_efuses_after_burn[i : i + 5 :])), 349 ) 350 351 efuse_name_list = [name for name in args.name_value_pairs.keys()] 352 burn_efuses_list = [efuses[name] for name in efuse_name_list] 353 old_value_list = [efuses[name].get_raw() for name in efuse_name_list] 354 new_value_list = [value for value in args.name_value_pairs.values()] 355 util.check_duplicate_name_in_list(efuse_name_list) 356 357 attention = "" 358 print("The efuses to burn:") 359 for block in efuses.blocks: 360 burn_list_a_block = [e for e in burn_efuses_list if e.block == block.id] 361 if len(burn_list_a_block): 362 print(" from BLOCK%d" % (block.id)) 363 for field in burn_list_a_block: 364 print(" - %s" % (field.name)) 365 if ( 366 efuses.blocks[field.block].get_coding_scheme() 367 != efuses.REGS.CODING_SCHEME_NONE 368 ): 369 using_the_same_block_names = [ 370 e.name for e in efuses if e.block == field.block 371 ] 372 wr_names = [e.name for e in burn_list_a_block] 373 blocked_efuses_after_burn = [ 374 name 375 for name in using_the_same_block_names 376 if name not in wr_names 377 ] 378 attention = " (see 'ATTENTION!' above)" 379 if attention: 380 print_attention(blocked_efuses_after_burn) 381 382 print("\nBurning efuses{}:".format(attention)) 383 for efuse, new_value in zip(burn_efuses_list, new_value_list): 384 print( 385 "\n - '{}' ({}) {} -> {}".format( 386 efuse.name, 387 efuse.description, 388 efuse.get_bitstring(), 389 efuse.convert_to_bitstring(new_value), 390 ) 391 ) 392 efuse.save(new_value) 393 394 print() 395 if "ENABLE_SECURITY_DOWNLOAD" in efuse_name_list: 396 print( 397 "ENABLE_SECURITY_DOWNLOAD -> 1: eFuses will not be read back " 398 "for confirmation because this mode disables " 399 "any SRAM and register operations." 400 ) 401 print(" espefuse will not work.") 402 print(" esptool can read/write only flash.") 403 404 if "DIS_DOWNLOAD_MODE" in efuse_name_list: 405 print( 406 "DIS_DOWNLOAD_MODE -> 1: eFuses will not be read back for " 407 "confirmation because this mode disables any communication with the chip." 408 ) 409 print( 410 " espefuse/esptool will not work because " 411 "they will not be able to connect to the chip." 412 ) 413 414 if ( 415 esp.CHIP_NAME == "ESP32" 416 and esp.get_chip_revision() >= 300 417 and "UART_DOWNLOAD_DIS" in efuse_name_list 418 ): 419 print( 420 "UART_DOWNLOAD_DIS -> 1: eFuses will be read for confirmation, " 421 "but after that connection to the chip will become impossible." 422 ) 423 print(" espefuse/esptool will not work.") 424 425 if not efuses.burn_all(check_batch_mode=True): 426 return 427 428 print("Checking efuses...") 429 raise_error = False 430 for efuse, old_value, new_value in zip( 431 burn_efuses_list, old_value_list, new_value_list 432 ): 433 if not efuse.is_readable(): 434 print( 435 "Efuse %s is read-protected. Read back the burn value is not possible." 436 % efuse.name 437 ) 438 else: 439 new_value = efuse.convert_to_bitstring(new_value) 440 burned_value = efuse.get_bitstring() 441 if burned_value != new_value: 442 print( 443 burned_value, 444 "->", 445 new_value, 446 "Efuse %s failed to burn. Protected?" % efuse.name, 447 ) 448 raise_error = True 449 if raise_error: 450 raise esptool.FatalError("The burn was not successful.") 451 else: 452 print("Successful") 453 454 455def read_protect_efuse(esp, efuses, args): 456 util.check_duplicate_name_in_list(args.efuse_name) 457 458 for efuse_name in args.efuse_name: 459 efuse = efuses[efuse_name] 460 if not efuse.is_readable(): 461 print("Efuse %s is already read protected" % efuse.name) 462 else: 463 if esp.CHIP_NAME == "ESP32": 464 if ( 465 efuse_name == "BLOCK2" 466 and not efuses["ABS_DONE_0"].get() 467 and esp.get_chip_revision() >= 300 468 ): 469 if efuses["ABS_DONE_1"].get(): 470 raise esptool.FatalError( 471 "Secure Boot V2 is on (ABS_DONE_1 = True), " 472 "BLOCK2 must be readable, stop this operation!" 473 ) 474 else: 475 print( 476 "If Secure Boot V2 is used, BLOCK2 must be readable, " 477 "please stop this operation!" 478 ) 479 elif esp.CHIP_NAME == "ESP32-C2": 480 error = ( 481 not efuses["XTS_KEY_LENGTH_256"].get() 482 and efuse_name == "BLOCK_KEY0" 483 ) 484 error |= efuses["SECURE_BOOT_EN"].get() and efuse_name in [ 485 "BLOCK_KEY0", 486 "BLOCK_KEY0_HI_128", 487 ] 488 if error: 489 raise esptool.FatalError( 490 "%s must be readable, stop this operation!" % efuse_name 491 ) 492 else: 493 for block in efuses.Blocks.BLOCKS: 494 block = efuses.Blocks.get(block) 495 if block.name == efuse_name and block.key_purpose is not None: 496 if not efuses[block.key_purpose].need_rd_protect( 497 efuses[block.key_purpose].get() 498 ): 499 raise esptool.FatalError( 500 "%s must be readable, stop this operation!" % efuse_name 501 ) 502 break 503 # make full list of which efuses will be disabled 504 # (ie share a read disable bit) 505 all_disabling = [ 506 e for e in efuses if e.read_disable_bit == efuse.read_disable_bit 507 ] 508 names = ", ".join(e.name for e in all_disabling) 509 print( 510 "Permanently read-disabling efuse%s %s" 511 % ("s" if len(all_disabling) > 1 else "", names) 512 ) 513 efuse.disable_read() 514 515 if not efuses.burn_all(check_batch_mode=True): 516 return 517 518 print("Checking efuses...") 519 raise_error = False 520 for efuse_name in args.efuse_name: 521 efuse = efuses[efuse_name] 522 if efuse.is_readable(): 523 print("Efuse %s is not read-protected." % efuse.name) 524 raise_error = True 525 if raise_error: 526 raise esptool.FatalError("The burn was not successful.") 527 else: 528 print("Successful") 529 530 531def write_protect_efuse(esp, efuses, args): 532 util.check_duplicate_name_in_list(args.efuse_name) 533 for efuse_name in args.efuse_name: 534 efuse = efuses[efuse_name] 535 if not efuse.is_writeable(): 536 print("Efuse %s is already write protected" % efuse.name) 537 else: 538 # make full list of which efuses will be disabled 539 # (ie share a write disable bit) 540 all_disabling = [ 541 e for e in efuses if e.write_disable_bit == efuse.write_disable_bit 542 ] 543 names = ", ".join(e.name for e in all_disabling) 544 print( 545 "Permanently write-disabling efuse%s %s" 546 % ("s" if len(all_disabling) > 1 else "", names) 547 ) 548 efuse.disable_write() 549 550 if not efuses.burn_all(check_batch_mode=True): 551 return 552 553 print("Checking efuses...") 554 raise_error = False 555 for efuse_name in args.efuse_name: 556 efuse = efuses[efuse_name] 557 if efuse.is_writeable(): 558 print("Efuse %s is not write-protected." % efuse.name) 559 raise_error = True 560 if raise_error: 561 raise esptool.FatalError("The burn was not successful.") 562 else: 563 print("Successful") 564 565 566def burn_block_data(esp, efuses, args): 567 block_name_list = args.block[ 568 0 : len([name for name in args.block if name is not None]) : 569 ] 570 datafile_list = args.datafile[ 571 0 : len([name for name in args.datafile if name is not None]) : 572 ] 573 efuses.force_write_always = args.force_write_always 574 575 util.check_duplicate_name_in_list(block_name_list) 576 if args.offset and len(block_name_list) > 1: 577 raise esptool.FatalError( 578 "The 'offset' option is not applicable when a few blocks are passed. " 579 "With 'offset', should only one block be used." 580 ) 581 else: 582 offset = args.offset 583 if offset: 584 num_block = efuses.get_index_block_by_name(block_name_list[0]) 585 block = efuses.blocks[num_block] 586 num_bytes = block.get_block_len() 587 if offset >= num_bytes: 588 raise esptool.FatalError( 589 "Invalid offset: the block%d only holds %d bytes." 590 % (block.id, num_bytes) 591 ) 592 if len(block_name_list) != len(datafile_list): 593 raise esptool.FatalError( 594 "The number of block_name (%d) and datafile (%d) should be the same." 595 % (len(block_name_list), len(datafile_list)) 596 ) 597 598 for block_name, datafile in zip(block_name_list, datafile_list): 599 num_block = efuses.get_index_block_by_name(block_name) 600 block = efuses.blocks[num_block] 601 data = datafile.read() 602 num_bytes = block.get_block_len() 603 if offset != 0: 604 data = (b"\x00" * offset) + data 605 data = data + (b"\x00" * (num_bytes - len(data))) 606 if len(data) != num_bytes: 607 raise esptool.FatalError( 608 "Data does not fit: the block%d size is %d bytes, " 609 "data file is %d bytes, offset %d" 610 % (block.id, num_bytes, len(data), offset) 611 ) 612 print( 613 "[{:02}] {:20} size={:02} bytes, offset={:02} - > [{}].".format( 614 block.id, block.name, len(data), offset, util.hexify(data, " ") 615 ) 616 ) 617 block.save(data) 618 619 if not efuses.burn_all(check_batch_mode=True): 620 return 621 print("Successful") 622 623 624def burn_bit(esp, efuses, args): 625 efuses.force_write_always = args.force_write_always 626 num_block = efuses.get_index_block_by_name(args.block) 627 block = efuses.blocks[num_block] 628 data_block = BitStream(block.get_block_len() * 8) 629 data_block.set(0) 630 try: 631 data_block.set(True, args.bit_number) 632 except IndexError: 633 raise esptool.FatalError( 634 "%s has bit_number in [0..%d]" % (args.block, data_block.len - 1) 635 ) 636 data_block.reverse() 637 print( 638 "bit_number: " 639 "[%-03d]........................................................[0]" 640 % (data_block.len - 1) 641 ) 642 print("BLOCK%-2d :" % block.id, data_block) 643 block.print_block(data_block, "regs_to_write", debug=True) 644 block.save(data_block.bytes[::-1]) 645 646 if not efuses.burn_all(check_batch_mode=True): 647 return 648 print("Successful") 649 650 651def check_error(esp, efuses, args): 652 error_in_blocks = efuses.get_coding_scheme_warnings() 653 if args.recovery: 654 if error_in_blocks: 655 confirmed = False 656 for block in reversed(efuses.blocks): 657 if block.fail or block.num_errors > 0: 658 if not block.get_bitstring().all(False): 659 block.save(block.get_bitstring().bytes[::-1]) 660 if not confirmed: 661 confirmed = True 662 efuses.confirm( 663 "Recovery of block coding errors", args.do_not_confirm 664 ) 665 block.burn() 666 # Reset the recovery flag to run check_error() without it, 667 # just to check the new state of eFuse blocks. 668 args.recovery = False 669 check_error(esp, efuses, args) 670 else: 671 if error_in_blocks: 672 raise esptool.FatalError("Error(s) were detected in eFuses") 673 print("No errors detected") 674